olace

[ daemon & cli ]

Olace Bridge

The Olace Bridge is a local model endpoint served on your machine at http://127.0.0.1:5578. It speaks three APIs at once, on one port and one key, with nothing to switch between: OpenAI Chat Completions, the OpenAI Responses API, and the Anthropic Messages API. Point any tool that speaks one of them at it, from Claude Code and Codex CLI to editors and chat apps, and that tool can use your whole Olace network: local models through Ollama, LM Studio and llama.cpp, plus the models on every paired host, reached over an end-to-end encrypted connection. Olace's servers never see the prompts or responses of local and paired requests.

This is how your other tools ride the network. The model id decides where inference happens, so a coding agent on your laptop can run on the big GPU at home just by naming it: ollama/qwen3.5:4b runs here, home-pc/ollama/qwen3.6:27b runs on your paired host.

Turn it on

In the desktop app: Settings › Local AI › Olace Bridge. Or:

olace bridge on

It is off by default and listens on loopback only. It serves this computer's models without an Olace account; models on your other computers appear once you sign in. Check it any time with olace bridge or curl http://127.0.0.1:5578/.

Model names

Models are provider-qualified, and paired hosts get a prefix from their pairing label:

ollama/qwen3.5:4b               local Ollama
lmstudio/google/gemma-4-e4b     local LM Studio
llamacpp/qwen3.5-4b             local llama.cpp
home-pc/ollama/qwen3.6:27b      paired host "Home PC"

Unambiguous shorthand is accepted; an ambiguous name returns a 404 with the candidates. GET /v1/models lists everything, including models on hosts that are currently offline.

Endpoints

Three API contracts share the port. OpenAI-style tools use http://127.0.0.1:5578/v1 as their base URL; Anthropic-style tools use http://127.0.0.1:5578 (their SDKs add /v1 themselves).

RouteAPINotes
POST /v1/chat/completionsOpenAI Chat CompletionsStreaming and non-streaming.
POST /v1/responsesOpenAI ResponsesThe API Codex CLI and the newer OpenAI SDK calls use.
POST /v1/messagesAnthropic MessagesThe API Claude Code and the Anthropic SDKs use.
POST /v1/messages/count_tokensAnthropic MessagesToken count for a request.
POST /v1/completionsOpenAILegacy text completions.
POST /v1/embeddingsOpenAILocal and paired models.
GET /v1/modelsBothOne list across local and paired providers, in a shape both the OpenAI and Anthropic SDKs read.
GET /v1/models/{id}BothAccepts canonical ids and unambiguous shorthand.

Claude Code and Codex CLI

Claude Code talks to the Bridge through the Anthropic Messages API. Set three variables and start it; the model id decides which computer runs it:

export ANTHROPIC_BASE_URL=http://127.0.0.1:5578
export ANTHROPIC_API_KEY=unused          # or your Bridge key
export ANTHROPIC_MODEL=home-pc/ollama/qwen3.6:27b
export ANTHROPIC_SMALL_FAST_MODEL=ollama/qwen3.5:4b
claude

ANTHROPIC_SMALL_FAST_MODEL is optional; it names the model Claude Code uses for its quick background tasks, so a small local model keeps those off the big GPU.

Codex CLI talks to the Bridge through the Responses API. Add the Bridge as a provider in ~/.codex/config.toml:

model = "home-pc/ollama/qwen3.6:27b"
model_provider = "olace"

[model_providers.olace]
name = "Olace Bridge"
base_url = "http://127.0.0.1:5578/v1"
env_key = "OLACE_BRIDGE_KEY"
wire_api = "responses"

Then start it with your Bridge key (any text works when no key is set):

OLACE_BRIDGE_KEY=unused codex

Examples

curl http://127.0.0.1:5578/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ollama/qwen3.5:4b",
    "messages": [{"role": "user", "content": "Hello from the Bridge"}]
  }'
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:5578/v1", api_key="unused")
reply = client.chat.completions.create(
    model="home-pc/ollama/qwen3.6:27b",
    messages=[{"role": "user", "content": "Hello from the Bridge"}],
)
print(reply.choices[0].message.content)

The Responses API, from curl and from the OpenAI SDK:

curl http://127.0.0.1:5578/v1/responses \
  -H "Content-Type: application/json" \
  -d '{"model": "ollama/qwen3.5:4b", "input": "Hello from the Bridge"}'
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:5578/v1", api_key="unused")
resp = client.responses.create(
    model="home-pc/ollama/qwen3.6:27b",
    instructions="Answer briefly.",
    input="Hello from the Bridge",
)
print(resp.output_text)

The Anthropic Messages API, from curl and from the Anthropic SDK:

curl http://127.0.0.1:5578/v1/messages \
  -H "Content-Type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "ollama/qwen3.5:4b",
    "max_tokens": 512,
    "messages": [{"role": "user", "content": "Hello from the Bridge"}]
  }'
from anthropic import Anthropic

client = Anthropic(base_url="http://127.0.0.1:5578", api_key="unused")
msg = client.messages.create(
    model="home-pc/ollama/qwen3.6:27b",
    max_tokens=512,
    messages=[{"role": "user", "content": "Hello from the Bridge"}],
)
print(msg.content[0].text)

Any tool that speaks one of these APIs works the same way: set the base URL (http://127.0.0.1:5578/v1 for OpenAI-style tools, http://127.0.0.1:5578 for Anthropic-style ones), set the API key to anything (or your Bridge key, below), and pick a model id from GET /v1/models.

Works with your tools

Most coding agents, editors, and chat apps accept a custom OpenAI-compatible or Anthropic-compatible endpoint. Where to point them:

ToolKindWhere to set it
Claude CodeTerminal coding agentANTHROPIC_BASE_URL, ANTHROPIC_API_KEY, ANTHROPIC_MODEL env vars (above)
Codex CLITerminal coding agentA ~/.codex/config.toml provider (above)
ContinueCoding assistant (VS Code, JetBrains)~/.continue/config.yaml, apiBase
ClineCoding agent (VS Code)Settings › API Provider › OpenAI Compatible
ZedEditor with agent panelsettings.json, openai_compatible provider
OpenCodeTerminal coding agentopencode.json, provider baseURL
AiderTerminal pair programmerOPENAI_API_BASE + OPENAI_API_KEY env vars
Droid (Factory)Coding agent~/.factory/settings.json, customModels
Open WebUISelf-hosted chat UIAdmin Settings › Connections › OpenAI
JanDesktop chat appSettings › Model Providers › Add Provider
Chatbox, Cherry StudioDesktop chat appsAdd provider, type "OpenAI API Compatible"
Copilot for ObsidianNotes pluginCustom model, "3rd party (openai format)"

One worked example, Continue:

# ~/.continue/config.yaml
models:
  - name: Home GPU
    provider: openai
    model: home-pc/ollama/qwen3.6:27b
    apiBase: http://127.0.0.1:5578/v1

Practical notes that save a few minutes:

  • Most tools require a non-empty API key even when the Bridge has none set; any string works.
  • Tools with a "fetch models" button read GET /v1/models and will list your whole network, paired hosts included.
  • Desktop apps built on Tauri or Electron (Jan and many others), editor webviews, and browser extensions send an origin. The Bridge allows those out of the box, along with any page served from your own computer. A web app on a real domain needs one command: olace bridge cors add https://app.example.com.
  • Open WebUI in Docker should use host.docker.internal instead of 127.0.0.1.
  • A few tools route model calls through their vendor's servers (Cursor's base-URL override, for example); those cannot reach an endpoint on your machine. The Bridge is local-first by design.

Authentication

By default there is no key and the Bridge accepts connections from this machine only. If you want a key anyway, or you plan to front the Bridge with a reverse proxy:

olace bridge key --require-key

The key is shown once and then required on every API, as Authorization: Bearer <key> or as x-api-key: <key> (the header the Anthropic SDKs and Claude Code send). Rotate with --regenerate, remove with --no-key. A web app hosted on a real domain also needs its origin allowed: olace bridge cors add <origin>, or bridge_cors_origins in daemon.json.

Behavior worth knowing

  • Streaming. stream: true is supported on all three APIs, each with its standard event stream (chat completion chunks, response.created through response.completed, message_start through message_stop). While a cold model loads, the stream sends keep-alive lines so clients do not time out.
  • Reasoning models think only when asked: send reasoning_effort on Chat Completions, reasoning.effort on Responses (low, medium, high, or max; minimal counts as low, none keeps it off), or thinking: {"type": "enabled", "budget_tokens": n} on Messages, where the budget maps onto the model's thinking level. The level is capped at what the model supports. Models that always think still do. Thinking streams as delta.reasoning_content on the OpenAI APIs and as thinking blocks (with an empty signature) on Messages.
  • Tool calls work on all three APIs (function tools on the OpenAI APIs, tools with input_schema and tool_choice on Messages). A tool call's arguments arrive whole at the end of the turn rather than token by token, because the model runtimes deliver them that way.
  • Responses API scope. input as a string or as message, function_call and function_call_output items, instructions, max_output_tokens, text.format (text, JSON mode, JSON schema). The Bridge stores nothing, so previous_response_id is not supported (resend the full input each turn), nor are background and conversation; store, metadata, truncation, include, parallel_tool_calls and OpenAI's built-in tools (web search, image generation) are accepted and ignored. Function tools grouped in a namespace work like any other function tool.
  • Messages API scope. system, text, base64 image, tool_use and tool_result blocks, max_tokens (required), stop_sequences, temperature, top_p. Not supported: image URLs (inline base64 instead) and document blocks. top_k, metadata, service_tier, cache_control and Anthropic's server tools (web search, bash, computer use, text editor) are accepted and ignored, so the request runs with your own tools only. Errors on these routes use the Anthropic error shape.
  • Images are accepted as base64 data URIs and downscaled on your device before sending; remote image URLs are rejected.
  • Honored parameters include tools, tool_choice, temperature, top_p, stop, max_tokens, response_format (JSON mode and JSON schema). Unsupported OpenAI extras are accepted and ignored; n > 1 and audio are rejected.
  • Paired hosts. Requests prefer a direct LAN connection and fall back to the encrypted relay when you are away. Sessions stay warm between requests, so bursts of agent calls skip the handshake. An offline host fails fast with 503 host_offline.
  • Errors use the standard OpenAI envelope, with meaningful codes like model_not_found, host_offline, host_busy, context_length_exceeded, and unsupported_message_role (a system or developer message that is not the first message, on a llama.cpp model).

A note on scope

The Bridge serves your local and paired models. Requests never route to Olace Cloud models, so a misconfigured tool can never quietly spend your credits.