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– ContainsBaseProvider(lines 20-23) and themake_requestlifecycle (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.setuphandles theproviderconfiguration 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
BaseProviderinlua/99/providers.luato 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 theproviderfield 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →