# How to Integrate Custom AI Providers with the 99 Neovim Plugin

> Integrate custom AI providers with the 99 Neovim plugin by creating a Lua class that inherits from BaseProvider. Learn how to build custom AI commands and easily extend the plugin's functionality.

- Repository: [ThePrimeagen/99](https://github.com/theprimeagen/99)
- Tags: how-to-guide
- Published: 2026-02-16

---

**To integrate a custom AI provider with 99, create a Lua class that inherits from `BaseProvider` in [`lua/99/providers.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/providers.lua) and implements the `_build_command`, `_get_provider_name`, and optional `_get_default_model` methods.**

The 99 project is a Neovim agent framework that abstracts AI backends through a provider pattern, allowing you to integrate any command-line AI tool. By implementing a small interface, you can connect custom models or proprietary CLIs while leveraging 99's request lifecycle, logging, and cancellation features.

## Understanding the Provider Architecture in 99

Every AI backend in 99 is treated as a **provider**—a Lua class that knows how to construct command-line calls for an external CLI. All providers inherit from `BaseProvider`, defined in **[`lua/99/providers.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/providers.lua)** (lines 20-23), which supplies the generic request lifecycle including `make_request`, temporary-file handling, cancellation, and logging.

A custom provider only needs to implement three specific methods to integrate with the framework. The base class handles the rest of the workflow, ensuring consistent behavior across different AI backends.

## Required Methods for Custom AI Providers

When you integrate a custom AI provider, you must implement the following interface:

### _build_command

This method assembles the CLI invocation for a given query and request context.

```lua
function MyProvider._build_command(_, query, request)
  -- Return a table of command-line arguments
  return {
    "my-ai-cli",
    "--model", request.context.model,
    "--output", request.context.tmp_file,
    "--prompt", query,
  }
end

```

The method receives the user's `query` string and a `request` object containing the `context` (including the model name and temporary file path). It must return a `string[]`—an array of command-line arguments.

### _get_provider_name

This method returns a human-readable identifier used for logging and debugging.

```lua
function MyProvider._get_provider_name()
  return "MyAwesomeProvider"
end

```

The return value must be a `string` that uniquely identifies your provider in logs.

### _get_default_model (Optional)

This optional method specifies the default model name when the user does not provide one.

```lua
function MyProvider._get_default_model()
  return "awesome-gpt-4"
end

```

If implemented, this `string` return value becomes the default model for requests using this provider.

## Step-by-Step: Integrate a Custom AI Provider

Follow these steps to integrate a custom AI provider with 99.

### 1. Create the Provider File

Create a new Lua file in your configuration (e.g., [`lua/99/custom_providers.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/custom_providers.lua)):

```lua
--- Custom provider that calls the fictitious "awesome-ai" CLI
local BaseProvider = require("99.providers").BaseProvider

local MyAwesomeProvider = setmetatable({}, { __index = BaseProvider })

function MyAwesomeProvider._build_command(_, query, request)
  return {
    "awesome-ai",
    "--model", request.context.model,
    "--output", request.context.tmp_file,
    "--prompt", query,
  }
end

function MyAwesomeProvider._get_provider_name()
  return "MyAwesomeProvider"
end

function MyAwesomeProvider._get_default_model()
  return "awesome-gpt-4"
end

return MyAwesomeProvider

```

### 2. Register the Provider Globally

In your [`init.lua`](https://github.com/ThePrimeagen/99/blob/main/init.lua) or configuration file, register the provider to make it the default:

```lua
local MyAwesomeProvider = require("99.custom_providers")

_99.setup({
  provider = MyAwesomeProvider,
  model = "awesome-gpt-4",
})

```

According to the source code in **[`lua/99/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/init.lua)** (line 569), the `setup` function accepts a `provider` option that sets the global default for all subsequent requests.

### 3. Override Per-Request

You can also integrate custom AI providers for single requests without changing the global configuration:

```lua
local MyAwesomeProvider = require("99.custom_providers")

local request = _99.request({
  query = "Explain the code above",
  provider = MyAwesomeProvider,
})
request:run()

```

This pattern, implemented in **[`lua/99/request/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/request/init.lua)**, allows you to specify a `provider_override` field (or `provider` key) when constructing individual requests.

## Key Integration Points and Source Files

Understanding these source files helps you integrate custom AI providers correctly:

- **[`lua/99/providers.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/providers.lua)** – Contains `BaseProvider` (lines 20-23) and the `make_request` lifecycle (lines 50-70). Also houses built-in providers like OpenCode (lines 36-63) that serve as reference implementations.
- **[`lua/99/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/init.lua)** – Public API entry point where `_99.setup` handles the `provider` configuration option (line 569).
- **[`lua/99/request/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/request/init.lua)** – Request construction logic where per-request provider overrides are processed.
- **[`lua/99/extensions/completions.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/completions.lua)** – Registry for completion providers that use the same provider interface.

## Summary

- **Inherit from `BaseProvider`** in [`lua/99/providers.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/providers.lua) to integrate custom AI providers with minimal boilerplate.
- **Implement three methods**: `_build_command` (required), `_get_provider_name` (required), and `_get_default_model` (optional).
- **Register globally** via `_99.setup({ provider = MyProvider })` or **override per-request** using the `provider` field in request options.
- **Leverage built-in lifecycle** handling for temporary files, cancellation, and logging without additional code.

## Frequently Asked Questions

### What is the BaseProvider class in 99?

`BaseProvider` is an abstract Lua class defined in [`lua/99/providers.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/providers.lua) that provides the generic request lifecycle for all AI backends. It handles `make_request`, temporary file management, process cancellation, and logging. Custom providers inherit from this class and only need to implement the CLI-specific methods to integrate with the framework.

### Can I use multiple AI providers simultaneously in 99?

Yes. While you can set a global default provider via `_99.setup()`, you can override the provider for individual requests by passing the `provider` field when calling `_99.request()`. This allows you to use different AI backends for different tasks within the same Neovim session without changing the global configuration.

### How do I override the provider for a single request?

To override the provider for a single request, pass your custom provider table to the `provider` field when constructing the request:

```lua
local request = _99.request({
  query = "Refactor this function",
  provider = require("my.custom_provider"),
})
request:run()

```

This pattern is handled in [`lua/99/request/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/request/init.lua) and takes precedence over the global provider set in `_99.setup()`.

### Where should I store my custom provider files?

You can store custom provider files anywhere in your Neovim runtimepath, such as [`lua/99/custom_providers.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/custom_providers.lua) or [`lua/custom/ai_providers.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/custom/ai_providers.lua). The only requirement is that you can require the module when passing it to `_99.setup()` or per-request options. Ensure the file returns the provider table (the result of `setmetatable` with `BaseProvider` as the index).