Execution Control

Control tool execution behavior — results, timeout & retry, result compaction, and cancellation.

Tool Execution Results

Tool execution results are automatically serialized as strings and returned to the model. The tool() function internally handles result wrapping:

  • On success: Returns { success: true, data: <result> }
  • On failure: Returns { success: false, error: "<error message>" }

Your execute function can return any type — string, object, number — deepseek-kit handles serialization automatically:

const tool1 = tool({
  name: 'getString',
  description: 'Return a string result',
  schema: z.object({ query: z.string() }),
  execute: async () => 'This is a text result',
})

const tool2 = tool({
  name: 'getObject',
  description: 'Return a structured result',
  schema: z.object({ id: z.string() }),
  execute: async () => ({ name: 'John', age: 25, email: 'john@example.com' }),
})

Timeout and Retry

Timeout

Set a timeout for tools that may take a long time to avoid waiting indefinitely. The default timeout is 60 seconds:

const slowApiTool = tool({
  name: 'slowApi',
  description: 'Call a slow API',
  schema: z.object({ query: z.string() }),
  execute: async (input) => {
    return await callSlowApi(input.query)
  },
  timeout: 30000,
})

Retry

Configure automatic retry count for unreliable tools. When execution fails, deepseek-kit automatically retries until success or the maximum retry count is reached:

const unreliableApiTool = tool({
  name: 'unreliableApi',
  description: 'Call an unreliable API',
  schema: z.object({ query: z.string() }),
  execute: async (input) => {
    return await callUnreliableApi(input.query)
  },
  retries: 3,
})

Timeout and retry can be combined — each retry is subject to the timeout limit:

const robustTool = tool({
  name: 'robustApi',
  description: 'Call an API that needs retry and timeout protection',
  schema: z.object({ query: z.string() }),
  execute: async (input) => {
    return await callApi(input.query)
  },
  timeout: 10000,
  retries: 2,
})

Result Compaction

When tools return large outputs (e.g. file contents, API responses), the raw result can consume a significant portion of the model's context window. The compact option uses an LLM to compress verbose tool results while preserving all information the agent needs to continue reasoning.

Basic Usage

Enable compaction with default settings (threshold: 1500 characters, model: deepseek-v4-flash):

const readFileTool = tool({
  name: 'readFile',
  description: 'Read the contents of a file',
  schema: z.object({ path: z.string().describe('File path') }),
  compact: true,
  execute: async (input) => {
    return await fs.readFile(input.path, 'utf-8')
  },
})

Custom Configuration

You can customize the compaction behavior by passing an object instead of true:

const readFileTool = tool({
  name: 'readFile',
  description: 'Read the contents of a file',
  schema: z.object({ path: z.string().describe('File path') }),
  compact: {
    threshold: 3000,
    model: 'deepseek-v4',
  },
  execute: async (input) => {
    return await fs.readFile(input.path, 'utf-8')
  },
})
  • threshold (number, default 1500) — Minimum character length of a tool result before compaction is triggered. Results shorter than this are returned as-is.
  • model (Model, default 'deepseek-v4-flash') — The LLM used to compress content.

AbortSignal

deepseek-kit supports AbortSignal throughout the tool execution pipeline, allowing you to cancel in-progress tool calls. This is useful when users cancel requests or when you need to enforce time limits at the application level.

Usage with Agents

Pass a signal to generateText or agent.generate to enable cancellation:

const controller = new AbortController()

const result = await agent.generate({
  prompt: 'Read the contents of a large file',
  signal: controller.signal,
})

// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000)

When the signal is aborted:

  • In-progress tool executions are cancelled immediately
  • Pending retries are skipped
  • The agent loop stops and throws an AbortError

Integration with Timeout

AbortSignal works alongside the timeout option. The timeout acts as a per-attempt time limit, while AbortSignal provides external cancellation:

const controller = new AbortController()

const tool = tool({
  name: 'slowApi',
  description: 'Call a slow API',
  schema: z.object({ query: z.string() }),
  timeout: 30000,
  execute: async (input) => {
    return await callSlowApi(input.query)
  },
})

// Both timeout and signal can cancel execution
const result = await agent.generate({
  tools: [tool],
  prompt: 'Search for something',
  signal: controller.signal,
})