hermes-agent 2d46eda619 adapter: support OpenAI streaming (SSE) in /v1/chat/completions
OpenCode's @ai-sdk/openai-compatible sends stream:true and would render an
empty response because the adapter always returned a single non-streaming
chat.completion JSON body. Now when stream:true, emit OpenAI-compatible SSE
chat.completion.chunk events (role, content, [DONE]) so streaming clients
render text. Non-streaming path unchanged.

Adds a local EchoServer test that exercises the streaming route end-to-end.
2026-09-12 10:50:47 +00:00
2026-09-09 21:44:55 +00:00
2026-09-10 07:21:42 +00:00

n8n-openai-adapter

An OpenAI-compatible HTTP adapter that exposes self-hosted n8n chat agents behind a standard /v1/chat/completions API, so any OpenAI client (Cursor, LibreChat, the openai SDK, a custom app) can talk to your n8n agents as if they were OpenAI models.

n8n itself does not ship an inbound OpenAI-compatible endpoint (its "AI Gateway" is an outbound proxy to n8n Cloud). This small Elixir service is the bridge: one /v1/chat/completions endpoint, routed to whichever n8n agent you name in the model field.

How it works

Your OpenAI client
   POST /v1/chat/completions {"model":"scholar-agent","thread_id":"abc","messages":[...]}
        |
        v
   n8n-openai-adapter (Plug + Bandit)
     - authorize (Bearer <ADAPTER_API_KEY>)
     - look up "scholar-agent" -> n8n chat webhook URL (AgentRegistry GenServer)
     - take the last user message
     - forward to the n8n webhook {sessionId: thread_id, action: sendMessage, chatInput}
        |
        v
   n8n agent (its MCP tools, memory, etc. run as usual)
        |
        v
   returns OpenAI-shaped {"choices":[{"message":{"role":"assistant","content":...}}]}

Multiple agents = multiple model names, each mapped to a different n8n webhook in the AGENTS env var.

Configuration (env vars)

Var Required Purpose
ADAPTER_API_KEY yes Bearer key that OpenAI clients send.
ADMIN_API_KEY yes Bearer key for the admin API / web admin page.
AGENTS_FILE no Path to the JSON store (default /var/lib/n8n-openai/agents.json).
PORT no HTTP port (default 8000).
CHAT_WEBHOOK_BASIC no "user:password" if your n8n Chat Trigger is Basic-auth protected.

Agents are not configured via env — they're managed at runtime through the web admin page / admin API and persisted to AGENTS_FILE. The store starts empty; add agents after boot.

Admin API (manage agents at runtime)

Agents are persisted to AGENTS_FILE and can be added/removed without a redeploy, using the ADMIN_API_KEY:

# list
curl -H "Authorization: Bearer $ADMIN_API_KEY" https://openai.bueso.eu/admin/agents

# add / update an agent
curl -X POST -H "Authorization: Bearer $ADMIN_API_KEY" -H "Content-Type: application/json" \
  -d '{"model":"media-agent","webhook":"https://n8n.bueso.eu/webhook/<id>/chat"}' \
  https://openai.bueso.eu/admin/agents

# remove
curl -X DELETE -H "Authorization: Bearer $ADMIN_API_KEY" \
  https://openai.bueso.eu/admin/agents/media-agent

The store is authoritative and persists across restarts; no env config needed.

Building & running

mix deps.get
mix compile
ADAPTER_API_KEY=secret AGENTS='{"scholar-agent":"https://n8n.bueso.eu/webhook/<id>/chat"}' \
  PORT=8000 mix run --no-halt

Testing

MIX_ENV=test mix test

Nix

The repo ships a flake.nix exporting overlays.default and a packages.default (the packaged BEAM release), so it can be consumed as a flake input from your NixOS config just like any other flake — e.g.:

inputs.n8n-openai-adapter.url = "git+https://gitea.bueso.eu/<owner>/n8n-openai-adapter";
S
Description
OpenAI-compatible adapter exposing n8n chat agents behind /v1/chat/completions
https://elixir-lang.org/
Readme
114 MiB
Languages
Elixir 89.2%
Nix 10.8%