Neovim LLM Setup: Adapters, Keys and Custom Endpoints
Guides

Neovim LLM Setup: Adapters, Keys and Custom Endpoints

Wire a Neovim LLM plugin to your own OpenAI-compatible endpoint — how adapters are shaped, where the key should come from, and why streaming often looks broken.

There is no single Neovim AI plugin, and that is fine — the ones worth using converge on the same design. You define an adapter that describes how to reach a provider, you give it a model, and the plugin builds requests around it. Once you can read one adapter definition you can configure any of them.

What none of them will do is guess. A plugin has no idea what your endpoint serves, so anything it cannot infer becomes a field you fill in, and the fields that change behaviour are not the obvious ones.

The adapter is the whole integration

Whatever the plugin calls it — adapter, provider, backend — the object holds three things: where to send the request, how to authenticate, and which model to ask for. Everything else is defaults you can usually leave alone.

In Lua the shape is roughly this, and it is a shape rather than a paste, because the key names differ per plugin:

{
  url = "https://api.example.com/v1/chat/completions",
  api_key = os.getenv("MY_API_KEY"),
  model = "the-model-name",
  stream = true,
}

The one detail worth checking in the plugin documentation is whether the URL field wants the full path to the completions operation or only the base ending at the version segment. Plugins differ, and getting it wrong produces a duplicated path and a 404 that reads like an authentication error. Read the docstring or the example in the repository rather than assuming, and see the compatibility explainer for what the rest of that surface does and does not guarantee.

The model string is passed upstream verbatim. An identifier your provider does not publish errors rather than resolving to something close.

Where the key comes from

Never write the key into your configuration. Neovim configurations are dotfiles, dotfiles end up in public repositories, and a key in one is a key you will be rotating.

Reading an environment variable is the simplest correct answer, and most plugins accept either a string or a function that returns one. Prefer the function form where it exists, because it defers the lookup until the request is made rather than at startup — which matters if you set the variable after Neovim was already running.

A better habit is to pull the key from a password manager or a secrets file at request time, so it never sits in your shell history or environment at all. The cost is a few hundred milliseconds on the first call; the benefit is that a leaked dotfile is not an incident. If you are configuring several tools this way, using one key across your tooling keeps the rotation surface small.

Note that a Neovim started from a graphical launcher may not inherit your shell profile. If the key resolves in a terminal and not in the GUI build, that is why.

Streaming is where it looks broken

Chat plugins stream responses so text appears progressively in a buffer. When streaming misbehaves, the plugin looks broken even though the model is fine.

Two causes dominate. The first is a proxy or gateway between you and the provider that buffers the response and forwards it in one piece; the plugin waits, then dumps everything at once. The second is a strict stream parser meeting a provider whose event format differs slightly — a missing terminator event, or usage data attached to a chunk in a place the parser did not expect. Those variations are exactly what server-sent events in practice is about.

Most plugins shell out to curl for HTTP. If streaming fails entirely, run the same request through curl by hand with the streaming flag set and watch whether chunks arrive progressively in the terminal. That single test tells you whether the problem is the network path or the plugin.

Tool calling and agentic plugins

Some Neovim plugins are chat surfaces: you highlight a region, ask a question, get text back. Others run an agent loop that reads files, applies diffs and can run commands. The second kind depends entirely on tool calling.

If an agentic plugin describes changes but never applies them, the model behind your identifier is probably not negotiating tool calls, or the adapter has not declared that capability. Chat working is not evidence that tools will — they are separate paths on the wire, as covered in the tool calling explainer.

Before giving an agentic plugin permission to run shell commands, decide deliberately. An editor plugin with unrestricted command execution has the same blast radius as any other agent on your machine, and a branch plus frequent commits is the only undo that reliably works.

Loading, keymaps and the ergonomics that decide whether you use it

Plugin managers lazy-load by default, which is good for startup time and a common source of confusion: the adapter is not defined until the plugin loads, so a command may not exist until the first invocation. If a command is missing, check the lazy-loading trigger before the configuration.

Bind the two or three operations you will actually use and ignore the rest. In practice those are: ask about the visual selection, apply an inline edit to the selection, and open a chat buffer for a longer conversation. Anything beyond that goes unused.

Keep the output in a normal buffer rather than a floating window if you intend to edit or copy from it. The whole reason to run this in Neovim rather than a browser is that the response lands somewhere you can operate on with the editor you already know.

Choosing a model for this workflow

Editor use is latency-sensitive in a way that batch work is not. A model that is two seconds slower per turn is noticeably worse to work with even if its output is marginally better, because the interaction is conversational.

A practical split is a fast model bound to the inline edit and selection commands, and a stronger one available in the chat buffer for the harder questions. Most plugins allow a per-command or per-adapter override, which makes this a configuration detail rather than a compromise.

Judge candidates on your own work rather than on benchmark tables — choosing a model for coding sets out what to measure, and instruction-following usually matters more here than raw capability.

The takeaway

Define one adapter, read the docstring to learn whether the URL field wants the base or the full path, resolve the key through a function rather than a literal, and test streaming with curl before blaming the plugin. Bind two commands, not twelve. If you want a terminal agent rather than an editor plugin, Aider covers the same three connection values in a different tool.

Common questions

Should the URL be the base or the full completions path?

It depends on the plugin, and this is the most common setup failure. Some adapters want the base ending at the version segment and append the operation path themselves; others want the full path. Check the plugin documentation, because getting it wrong gives you a 404 that looks like an auth error.

My key works in the terminal but not in Neovim. Why?

A Neovim launched from a graphical launcher does not inherit your shell profile, so the environment variable is not set in that process. Use a key resolver function that reads from a password manager or a secrets file rather than relying on the environment.

Why does the response appear all at once instead of streaming?

Something in the path is buffering the response, or the plugin stream parser does not match your provider event format. Run the same request through curl with streaming enabled: if chunks arrive progressively there, the plugin parser is the problem, not the network.

Similar articles

Cursor Setup Guide: Pointing It at Your Own Endpoint
Guides
Guides·8 min read

Cursor Setup Guide: Pointing It at Your Own Endpoint

How to run Cursor against a custom OpenAI-compatible base URL, which features stop using your key, and how to tell whether the override took effect.

Read
Emacs LLM Setup: A Model Endpoint Without the Mouse
Guides
Guides·9 min read

Emacs LLM Setup: A Model Endpoint Without the Mouse

Wire an OpenAI-compatible endpoint into Emacs — package choices, auth-source instead of hardcoded keys, buffer versus region workflows, streaming and keybindings.

Read
VS Code LLM Extension Setup: Custom Endpoints That Work
Guides
Guides·8 min read

VS Code LLM Extension Setup: Custom Endpoints That Work

A provider-agnostic guide to wiring any VS Code AI extension to your own endpoint — where settings live, what the extension cannot infer, and how to debug it.

Read