> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inputapi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Gemini built-in tools

> Native search grounding, URL context, and upstream code execution with a runnable three-mode example.

## Choose the native route

Use `POST /v1beta/models/{model}:generateContent` with a native Gemini channel and a model supporting the selected tool. The gateway DTO represents `googleSearch`, `urlContext`, and `codeExecution`; the native response handler forwards the original JSON body. This preserves candidate metadata such as `urlContextMetadata` even when it is not a typed DTO field. It does not guarantee preservation through Chat or Responses conversion.

| Task | Native request tool | Result to inspect |
| - | - | - |
| Search public web sources | `tools: [{googleSearch: {}}]` | `groundingMetadata.groundingChunks` and `groundingSupports` |
| Read a supplied public URL | `tools: [{urlContext: {}}]` | `urlContextMetadata.urlMetadata` and each retrieval status |
| Calculate using upstream Python execution | `tools: [{codeExecution: {}}]` | `parts[].executableCode` and `codeExecutionResult` |

Use the camelCase wire fields above. Python SDK snake\_case attributes and newer Interactions tool formats are different representations. This gateway has no Interactions endpoint. Enable one tool first; combinations, model versions, provider availability, and deployment policy need separate verification. A model listed by `/v1/models` does not establish tool support.

This fills the search/fetch topic illustrated by [OpenRouter server tools](https://github.com/OpenRouterTeam/docs/blob/main/guides/features/server-tools/web-search.mdx). OpenRouter's `openrouter:web_search`, `openrouter:web_fetch`, plugins, and automatic search-provider fallback are not enabled by these native Gemini fields. For another protocol see [Responses web search](/en/guides/web-search); for application-executed tools see [function calling](/en/guides/tool-calling).

## Check evidence as well as completion

A successful HTTP response and `finishReason: STOP` do not establish that a tool ran. The example deliberately requires evidence for its selected task, rejecting a direct answer without it. It accepts one completed model candidate, hides thought/signature data, and retains original part indices in visible text. Errors produce one generic message, with no automatic retry.

Search mode preserves the association between source indices and original text parts. It validates UTF-8 byte boundaries and the optional segment text, rather than applying offsets to concatenated JavaScript strings. It displays citation data as JSON without opening links. It is a terminal diagnostic, not a production citation renderer: it omits search suggestions HTML. A user-facing search UI must follow the upstream display requirements for `searchEntryPoint` and sources. See [Google grounding](https://ai.google.dev/gemini-api/docs/generate-content/google-search) and the [Segment reference](https://ai.google.dev/api/generate-content#Segment).

URL mode sends one explicit HTTPS URL in the prompt. It requires successful metadata for that exact normalized URL; a redirect to a different URL stops this conservative example for review. Retrieval failure, paywalls, unsafe URLs, or missing metadata do not become successful summaries. The script does not fetch the page itself, send gateway credentials to it, or crawl nested links. Use public pages without embedded credentials or private signed URLs; URL syntax validation is not a public-network accessibility check. See [URL context](https://ai.google.dev/gemini-api/docs/generate-content/url-context).

Code mode asks the upstream to calculate a small fixed example. It accepts ordered Python code/result pairs with `OUTCOME_OK`, including empty stdout, and requires visible final text. A failed attempt, missing result, or no execution stops the example, even if later text claims success. The code is printed as JSON data and is never run locally. This is not a gateway sandbox or a custom function dispatch loop. See [code execution](https://ai.google.dev/gemini-api/docs/generate-content/code-execution).

## Run the three modes

From the documentation directory, use Node 22.18+ or 24 with `examples/http-client.mjs` alongside the script. The shared [HTTP runtime](/en/guides/http-runtime) uses a gateway Bearer token, supports root path prefixes, bounds JSON responses to 1 MiB, and times out after 60 seconds. Each command makes one generation request; each tool may need a different enabled model.

```sh theme={null}
export API_ROOT_URL="https://gateway.example.com"
export API_MODEL="YOUR_NATIVE_GEMINI_MODEL_ID"
# API_KEY must already be set in the process environment.
API_GEMINI_TOOL=search node examples/gemini-builtins.mjs
API_GEMINI_TOOL=url API_CONTEXT_URL="https://nodejs.org/en/about" node examples/gemini-builtins.mjs
API_GEMINI_TOOL=code node examples/gemini-builtins.mjs
```

```javascript theme={null}
import { requestJSON } from './http-client.mjs';

try {
  const model = process.env.API_MODEL;
  const mode = process.env.API_GEMINI_TOOL || 'search';
  if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(model || '')
    || !['search', 'url', 'code'].includes(mode)) throw new Error('Invalid configuration.');
  let prompt = 'Search the web for the latest Node.js LTS release and cite your sources.';
  let target;
  if (mode === 'url') {
    target = new URL(process.env.API_CONTEXT_URL || 'invalid:');
    if (target.protocol !== 'https:' || target.username || target.password || target.hash) throw new Error('Invalid URL.');
    prompt = `Use URL context to summarize this public page: ${target.href}`;
  } else if (mode === 'code') {
    prompt = 'Use code execution to calculate the sum of the squares of integers 1 through 20. Explain the result.';
  }
  const tool = { search: 'googleSearch', url: 'urlContext', code: 'codeExecution' }[mode];
  const result = await requestJSON(`/v1beta/models/${model}:generateContent`, {
    contents: [{ role: 'user', parts: [{ text: prompt }] }], tools: [{ [tool]: {} }],
  });
  const candidate = result.candidates?.[0];
  if (result.promptFeedback?.blockReason || !Array.isArray(result.candidates) || result.candidates.length !== 1
    || (candidate.index !== undefined && candidate.index !== 0) || candidate.finishReason !== 'STOP'
    || candidate.content?.role !== 'model' || !Array.isArray(candidate.content.parts)) throw new Error('Incomplete candidate.');
  const textParts = [];
  const executions = [];
  let pendingCode;
  for (const [partIndex, part] of candidate.content.parts.entries()) {
    if (!part || typeof part !== 'object' || Array.isArray(part)) throw new Error('Invalid part.');
    if (part.thought === true) continue;
    if (part.functionCall || part.functionResponse || part.inlineData || part.fileData) throw new Error('Unexpected content.');
    const kinds = ['text', 'executableCode', 'codeExecutionResult'].filter((key) => part[key] !== undefined);
    if (kinds.length === 0 && typeof part.thoughtSignature === 'string') continue;
    if (kinds.length !== 1) throw new Error('Unsupported part.');
    if (part.text !== undefined) {
      if (typeof part.text !== 'string') throw new Error('Invalid text.');
      textParts.push({ partIndex, text: part.text });
    } else if (mode !== 'code') throw new Error('Unexpected execution.');
    else if (part.executableCode) {
      const code = part.executableCode;
      if (pendingCode || code.language !== 'PYTHON' || typeof code.code !== 'string' || !code.code.trim()) throw new Error('Invalid code.');
      pendingCode = { partIndex, language: code.language, code: code.code };
    } else {
      const execution = part.codeExecutionResult;
      if (!pendingCode || execution?.outcome !== 'OUTCOME_OK' || typeof (execution.output ?? '') !== 'string') throw new Error('Execution failed.');
      executions.push({ ...pendingCode, resultPartIndex: partIndex, outcome: execution.outcome, output: execution.output ?? '' });
      pendingCode = undefined;
    }
  }
  if (!textParts.some((part) => part.text.trim()) || pendingCode) throw new Error('Incomplete output.');
  let evidence;
  if (mode === 'search') {
    const grounding = candidate.groundingMetadata;
    if (!Array.isArray(grounding?.groundingChunks) || !grounding.groundingChunks.length
      || !Array.isArray(grounding.groundingSupports) || !grounding.groundingSupports.length) throw new Error('No grounded answer.');
    const sources = grounding.groundingChunks.map((chunk) => {
      const uri = new URL(chunk?.web?.uri || 'invalid:');
      if (!['https:', 'http:'].includes(uri.protocol) || uri.username || uri.password) throw new Error('Invalid source.');
      return { uri: uri.href, title: typeof chunk.web.title === 'string' ? chunk.web.title : '' };
    });
    const supports = grounding.groundingSupports.map((support) => {
      const segment = support?.segment;
      const partIndex = segment?.partIndex ?? 0;
      const startIndex = segment?.startIndex ?? 0;
      const endIndex = segment?.endIndex;
      const part = textParts.find((item) => item.partIndex === partIndex);
      const bytes = Buffer.from(part?.text || '', 'utf8');
      if (!Number.isSafeInteger(partIndex) || !part || !Number.isSafeInteger(startIndex) || startIndex < 0
        || !Number.isSafeInteger(endIndex) || endIndex <= startIndex || endIndex > bytes.length
        || !Array.isArray(support.groundingChunkIndices) || !support.groundingChunkIndices.length
        || support.groundingChunkIndices.some((index) => !Number.isSafeInteger(index) || index < 0 || index >= sources.length)) throw new Error('Invalid grounding.');
      // Native offsets are UTF-8 bytes, not JavaScript UTF-16 string positions.
      new TextDecoder('utf-8', { fatal: true }).decode(bytes.subarray(0, startIndex));
      const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes.subarray(startIndex, endIndex));
      if (segment.text !== undefined && segment.text !== text) throw new Error('Mismatched segment.');
      return { partIndex, startIndex, endIndex, text, groundingChunkIndices: support.groundingChunkIndices };
    });
    evidence = { sources, supports };
  } else if (mode === 'url') {
    const urls = candidate.urlContextMetadata?.urlMetadata;
    if (!Array.isArray(urls) || !urls.length) throw new Error('Missing retrieval status.');
    evidence = urls.map((item) => {
      if (item?.urlRetrievalStatus !== 'URL_RETRIEVAL_STATUS_SUCCESS'
        || new URL(item.retrievedUrl).href !== target.href) throw new Error('Retrieval did not confirm the requested URL.');
      return { retrievedUrl: item.retrievedUrl, status: item.urlRetrievalStatus };
    });
  } else {
    if (!executions.length) throw new Error('No code executed.');
    evidence = executions;
  }
  // JSON terminal output only: no HTML rendering, local code execution, or URL fetching.
  console.log(JSON.stringify({ mode, textParts, evidence }));
} catch {
  console.error('Gemini built-in example failed. Check native tool support, completion, and tool evidence. No automatic retry.');
  process.exitCode = 1;
}
```

## Scope and source verification

The example handles a single non-streaming turn with one built-in tool, without custom function combinations, artifact downloads, or history replay. Source at `30a6d88a4`: `relaykit/dto/gemini.go`, `relay/channel/gemini/adaptor.go`, and `relay/channel/gemini/relay-gemini-native.go`. The DTO can drop newer unknown fields on request re-serialization (for example new code-part IDs); raw response preservation is not proof of lossless multi-turn replay. Verify those requirements before adding a continuation loop.

For SSE use the [native streaming guide](/en/guides/native-gemini-streaming) for framing, but its text/function example is not a built-in metadata accumulator. Retain metadata across events and validate completion before presenting a grounded result. Local fixtures verify request and parser contracts, not live search quality, URL accessibility, execution accuracy, or tool accounting.

[阅读中文](/zh/guides/gemini-builtins)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.