How to Integrate Custom AI Providers with the 99 Neovim Plugin

To integrate a custom AI provider with 99, create a Lua class that inherits from BaseProvider in 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 (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.

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.

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.

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):

--- 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 or configuration file, register the provider to make it the default:

local MyAwesomeProvider = require("99.custom_providers")

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

According to the source code in 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:

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, 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 – 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 – Public API entry point where _99.setup handles the provider configuration option (line 569).
  • lua/99/request/init.lua – Request construction logic where per-request provider overrides are processed.
  • lua/99/extensions/completions.lua – Registry for completion providers that use the same provider interface.

Summary

  • Inherit from BaseProvider in 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 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:

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 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 or 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).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →