TurboFieldfareServer exposes a local Chat Completions API for one Gemma
model. It binds to 127.0.0.1 without authentication or TLS. Do not expose it
through a proxy or tunnel.
First, install the model with the Mac app or TurboFieldfareRepack. Then check
that no other TurboFieldfare model process is running:
pgrep -fl 'TurboFieldfareServer|TurboFieldfareMac|TurboFieldfareDecodeService|TurboFieldfareCLI|TurboFieldfarePackageTests|swiftpm-testing-helper|mlx_lm|mlx-lm'If the command prints a match, do not start the server.
swift build -c release --product TurboFieldfareServer
.build/release/TurboFieldfareServer \
--model scratch/gemma4.gturbo \
--port 8080 \
--max-context 16384The server loads the model before opening the port. Wait for
TurboFieldfareServer ready, then keep the process running while clients use
it.
Check the server from another terminal:
curl --silent --show-error http://127.0.0.1:8080/health
curl --silent --show-error http://127.0.0.1:8080/v1/models
curl --silent --show-error http://127.0.0.1:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "gemma-4-26b-a4b-it",
"messages": [{"role": "user", "content": "Reply with exactly READY."}],
"temperature": 0,
"max_completion_tokens": 16
}'By default, the server runs one generation and admits up to four additional
requests for preparation or queueing. The limit is enforced before prompt
rendering and tokenization. Use --queue-limit to change it. Press Control-C
to stop the server.
The server accepts the same runtime flags as the CLI, with the same values and defaults. See Runtime controls for what each one does.
.build/release/TurboFieldfareServer \
--model scratch/gemma4.gturbo \
--expert-cache-slots 32 \
--expert-cache-policy lru \
--prefill on \
--prefill-chunk-tokens 64 \
--rdadvise boundedWithout these flags the server runs the production defaults: 16 expert-cache
slots, LFU eviction, chunked prefill on with 128-token chunks, and read advice
off. --prefill-chunk-tokens auto runs at the cap,
256, on the server: a per-request size is the smallest allowed size covering the
span being prefilled, so the cap prefills every prompt in exactly those spans,
and it costs about 32.5 MB of prefill scratch against about 16.4 MB at the 128
default. Values are validated before the model loads, so an unsupported one
exits with the usage text rather than failing partway through startup. Chunked
prefill needs at least 16 expert-cache slots, so --expert-cache-slots 8
requires --prefill off.
The settings are fixed for the life of the process. Restart the server to change them.
The base URL is http://127.0.0.1:8080/v1. Some client libraries require an
API key, but the server ignores it.
Python:
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8080/v1", api_key="local")
response = client.chat.completions.create(
model="gemma-4-26b-a4b-it",
messages=[{"role": "user", "content": "Say hello in one sentence."}],
)
print(response.choices[0].message.content)OpenCode:
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"turbofieldfare": {
"npm": "@ai-sdk/openai-compatible",
"name": "TurboFieldfare",
"options": {
"baseURL": "http://127.0.0.1:8080/v1",
"apiKey": "local"
},
"models": {
"gemma-4-26b-a4b-it": {
"name": "Gemma 4 26B-A4B IT",
"attachment": true,
"modalities": {
"input": ["text", "image"],
"output": ["text"]
},
"limit": {
"context": 16384,
"output": 4096
}
}
}
}
}
}Select turbofieldfare/gemma-4-26b-a4b-it in OpenCode.
attachment and modalities are what make images work. OpenCode decides whether
a model accepts images from this configuration, not from the capabilities field
/v1/models returns, so without them it refuses an image with "this model does
not support image input" before sending anything. Images then reach the server
through OpenCode's read tool, which returns image files as attachments; ask it
to read the file explicitly.
Pi uses its openai-completions adapter:
{
"providers": {
"turbofieldfare": {
"baseUrl": "http://127.0.0.1:8080/v1",
"api": "openai-completions",
"apiKey": "local",
"compat": {
"supportsReasoningEffort": false,
"supportsStrictMode": false,
"supportsUsageInStreaming": true
},
"models": [{
"id": "gemma-4-26b-a4b-it",
"name": "Gemma 4 26B-A4B IT",
"reasoning": false,
"input": ["text", "image"],
"contextWindow": 16384,
"maxTokens": 4096
}]
}
}
}Keep the client context setting at or below the server's --max-context.
Pi's input field declares image support the same way OpenCode's modalities
does. With it, pi -p @photo.png "What is in this image?" sends the image as a
base64 data URL in an image_url part, which is the shape this server accepts;
its RPC interface takes an explicit images array and produces the same
request.
Single-prefix KV reuse is on by default. Send the complete message history with every request. When a request continues the retained conversation exactly, the server reuses the verified KV prefix and reports the number of reused tokens in:
usage.prompt_tokens_details.cached_tokens
The server retains one prefix. A different or incompatible history replaces
it. Use --prompt-cache-mode off to disable reuse.
The server can return OpenAI-style function calls, but it cannot authorize or execute them. The client runs the tool loop:
- Send function schemas in
tools. - When
finish_reasonis"tool_calls", inspect each function name and JSON argument object. Apply the client's normal permission checks before running the function. - Append the assistant message, including its unchanged
tool_calls. - Append each result as a
role: "tool"message. Itstool_call_idmust match the call it resolves. - Send the complete history and tool schemas again.
The server accepts only function tools. Omit tool_choice or set it to auto
to allow calls. Set it to none to disable them. The server does not support
required, named tool selection, or parallel_tool_calls: false.
Tool schemas need a non-null object at the top level and explicit JSON Schema
types. Nested properties and items may use nullable forms with one concrete
type plus null, including equivalent two-branch anyOf and disjoint oneOf
forms. Unions of string constants are also supported. Overlapping oneOf,
mixed-type unions such as string | object, and allOf return HTTP 400 with
invalid_tool_schema; the server does not guess which branch the model should
use.
Endpoints:
GET /healthGET /v1/modelsPOST /v1/chat/completions
Chat Completions supports JSON and Server-Sent Events responses. Set
"stream": true for streaming. Set
"stream_options": {"include_usage": true} to receive a final usage chunk.
Requests may contain system, developer, user, assistant, and tool messages.
Supported options include temperature, top_p, top_k,
repetition_penalty, seed, stop, max_tokens,
max_completion_tokens, and function-tool fields.
Unknown top-level request fields return HTTP 400 with code
unknown_parameter and the field name in param, so a misspelled option is
refused rather than silently ignored. response_format is accepted only as
{"type": "text"}; json_object and json_schema return
unsupported_value, as do logit_bias, top_logprobs, reasoning_effort,
verbosity, modalities, audio, prediction, web_search_options, and
the legacy functions and function_call. response_format must be an object; any
other JSON value returns invalid_value. user, store, metadata,
service_tier, prompt_cache_key, and safety_identifier are accepted and
ignored. A top-level field set to null is treated as absent. Fields inside
messages, tools, and stream_options are not checked for extras. Inside
stream_options only include_usage is read, so a misspelled key there is
ignored rather than refused.
The server supports one model and one choice. It does not support the Responses API, legacy Completions, embeddings, structured output, batching, log probabilities, or remote model switching.
Context length can be 4K, 8K, 16K, 32K, 64K, 96K, 128K, 192K, or 256K. The default is 16K. Before loading the model, the server checks whether the selected context and expert cache fit its memory estimate for your Mac. Larger contexts need more memory for the FP16 KV cache. See context length and memory for the calculation and diagnostic override, and the long-context report for measured results and validation limits. On an 8 GB Mac, run one model process at a time and watch memory pressure.
For long requests, stderr reports the request lifecycle as prepared, queued, generating, completed, or failed. It includes token counts and timing, but not prompt text, tool arguments, headers, or request bodies.
messages[].content accepts image_url parts in user messages. An
image_url on any other role returns HTTP 400. The URL must be a data URL,
and detail must be absent or set to auto.
Image bytes go to disk as the request body arrives, so a large upload does not sit in memory. Before any pixel is decoded, the server works out how many tokens the images will occupy and rejects the request if they do not fit the context. Bodies and images over their limits return HTTP 413.
GET /v1/models reports capabilities as ["text", "image"] when a valid
companion pack is loaded and ["text"] otherwise. GET /health reports the
same state under vision, as ready, missing, invalid, or unsupported.
unsupported means the image tower cannot run on this Mac; it requires M2 or
newer. Text requests remain available.
Choose the pack and the residency policy with --vision-pack <dir> and
--vision-residency on-demand|keep-ready.