Tool calling

Define tools, inspect model-generated tool calls, execute tools in your application, and return their results to Gemini to generate a final response.

Orcus lets you provide tool definitions when invoking a Gemini Web model. When Gemini requests a tool, your application validates the arguments, executes the corresponding function, and sends its result back through another invoke() call. Your application is responsible for executing tools and managing the conversation until Gemini returns a final response.

Initialize the client

Create a GeminiWeb client with your session credentials before making tool-enabled requests.

src/tools.ts
import 'dotenv/config'import { GeminiWeb, type GeminiMessage } from '@orcushq/gemini' const cookies = process.env.GEMINI_COOKIESconst xsrfToken = process.env.GEMINI_XSRF_TOKEN if (!cookies?.trim() || !xsrfToken?.trim()) {  throw new Error(    'GEMINI_COOKIES and GEMINI_XSRF_TOKEN are required',  )} const model = new GeminiWeb({  model: 'gemini-3.6-flash',  cookies,  xsrfToken,})

Define tools

A tool definition contains a name, description, and JSON Schema describing its expected arguments.

typescript
const tools = [  {    name: 'get_weather',    description:      'Get current weather information for a city.',    parameters: {      type: 'object',      properties: {        city: {          type: 'string',          description: 'City name',        },      },      required: ['city'],    },  },]
  • name identifies the tool returned in a tool call.
  • description tells the model what the tool does and when it is useful.
  • parameters describes the expected arguments using JSON Schema.

Tool parameters are JSON Schema objects, not Zod schemas. Treat returned arguments as untrusted input and validate them before executing a function.

Request and inspect tool calls

Pass the tool definitions to invoke(). Use toolChoice: "required" when the request should require a tool call. The response exposes toolCalls, which contains the parsed tool name and arguments.

typescript
const prompt = [  'What is the current weather in Indore?',  'Use the get_weather tool before answering.',  'Do not invent or change the returned weather values.',].join(' ') const response = await model.invoke(prompt, {  tools,  toolChoice: 'required',}) console.log('Finish reason:', response.finishReason)console.log('Content:', response.content)console.log('Tool calls:', response.toolCalls) if (response.finishReason === 'tool_calls') {  for (const call of response.toolCalls) {    console.log('Tool name:', call.name)    console.log('Arguments:', call.arguments)  }}

Each parsed tool call contains an id, name, and arguments. A tool call is a request from the model; it does not execute your application code automatically. Check the returned tool calls before dispatching any function.

Execute tools in your application

Map each supported tool name to an application-side function. Validate its arguments and reject unknown tool names. The following example uses fixed sample data so the execution flow can be tested without an external weather service.

typescript
async function getWeather(city: string) {  if (city.trim().toLowerCase() !== 'indore') {    throw new Error(`Unsupported test city: ${city}`)  }   // Sample fixture for demonstration; replace with  // your application's actual weather implementation.  return {    city: 'Indore',    temperatureC: 31,    condition: 'Partly cloudy',    source: 'local-test-fixture',  }} async function executeTool(  name: string,  args: unknown,): Promise<unknown> {  if (    !args ||    typeof args !== 'object' ||    !('city' in args) ||    typeof args.city !== 'string' ||    !args.city.trim()  ) {    throw new Error('Invalid get_weather arguments')  }   switch (name) {    case 'get_weather':      return getWeather(args.city)     default:      throw new Error(`Unknown tool: ${name}`)  }}

Only execute tools registered by your application. Validate arguments, enforce authorization where needed, and handle execution failures before returning results to the model.

Return tool results to Gemini

After executing a tool, append the assistant message containing the original tool calls, followed by one tool message per result. Each tool result is serialized to a string. Then call invoke() again with the updated conversation and the tool definitions.

typescript
const messages: GeminiMessage[] = [  {    role: 'user',    content: prompt,  },] let response = await model.invoke(messages, {  tools,  toolChoice: 'required',}) const MAX_TOOL_ROUNDS = 4 for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {  if (response.toolCalls.length === 0) {    break  }   messages.push({    role: 'assistant',    content: response.content,    tool_calls: response.toolCalls,  })   for (const call of response.toolCalls) {    const result = await executeTool(      call.name,      call.arguments,    )     console.log('Tool result:', result)     messages.push({      role: 'tool',      name: call.name,      content: JSON.stringify(result),    })  }   response = await model.invoke(messages, {    tools,    toolChoice: 'auto',  })} if (response.toolCalls.length > 0) {  throw new Error('Maximum tool-call rounds exceeded')} console.log('Final response:', response.content)

Tool handoff

The supported handoff uses the assistant message with its `tool_calls` and tool messages containing the tool name and string content. Do not add a `tool_call_id` field to a `GeminiMessage` tool message. Pass the tool definitions again on follow-up calls.

Complete end-to-end example

The following is the expected flow: request a tool, execute it in your application, return its result, and allow Gemini to generate the final response. The final answer depends on the model response.

text
Initial tool call:  name: get_weather  arguments: { city: 'Indore' } Application tool result:  {    city: 'Indore',    temperatureC: 31,    condition: 'Partly cloudy',    source: 'local-test-fixture'  } Final response:  The current weather in Indore is 31°C  with partly cloudy conditions.

The sample weather values are test data, not a live weather report. Replace the fixture with a real application function when integrating an external service.

Handle multiple tool-call rounds

Gemini may request multiple tools in one response or request additional tools after receiving results. Preserve the conversation history and repeat the invoke → execute → append results cycle until no more tool calls are returned. Set a maximum round count to prevent an unbounded loop.

Tool choice

typescript
// Let the model decide whether to request a tool.const automatic = await model.invoke(prompt, {  tools,  toolChoice: 'auto',}) // Require a tool call for this request.const required = await model.invoke(prompt, {  tools,  toolChoice: 'required',}) // Ask the model not to request a tool.const withoutTools = await model.invoke(prompt, {  tools,  toolChoice: 'none',})

You can configure toolChoice on the client or per invocation. Treat the selected mode as guidance to the model and always inspect the returned tool calls. Do not execute tools solely because a tool name appears in generated text.

Parsing helpers

typescript
import {  renderToolsPrompt,  parseToolCalls,} from '@orcushq/gemini' const promptFragment = renderToolsPrompt(tools, 'auto') const { content, toolCalls } = parseToolCalls(text)

renderToolsPrompt formats tool definitions for inclusion in a prompt. parseToolCalls extracts recognized tool-call blocks and returns the remaining content alongside parsed calls. These helpers are available for custom integrations; ordinary usage can rely on model.invoke().

Validate every tool call

Tool calling is emulated through prompt instructions and parsed text. The model can ignore instructions, produce malformed calls, or return invalid arguments. Validate tool names and arguments, enforce permissions, and never execute a tool without application-side checks.