feat: first cut at openai compatible server

This commit is contained in:
Georg Bauer
2026-07-24 21:12:48 +02:00
parent bbbe65a75f
commit d554b77b9d
18 changed files with 2612 additions and 535 deletions

150
PLAN.md
View File

@@ -13,11 +13,11 @@ sessions, tools, and turn orchestration.
| Area | Status | Available now | Still open |
| --- | --- | --- | --- |
| App shell | Implemented | Native multi-window Iced app, Codex-inspired layout, native Application/Edit/Window menus, Preferences panel, Model Manager, and streaming composer | Native app release packaging |
| Projects and sessions | Implemented with transcripts | Native folder picker, project-name dialog, create/select/delete projects and sessions, and durable structured chat history | KV payloads, rename/archive, and model binding |
| Persistence | Implemented for metadata, preferences, and chat | SQLite in Application Support, Diesel ORM, embedded migrations, constrained defaults, streamed message updates, and reopen tests | Downloaded-artifact metadata and session-runtime migrations |
| Model runtime | DeepSeek Flash vertical implemented | Rust-owned mmap/model/session graph, reused Metal kernels, 2K compressed-attention generation, DS4 sampling, lazy background loading, cancellation, and idle unloading | Ratio-4 sparse indexer beyond 2K, DSpark/SSD/steering, GLM/Pro, KV checkpoint persistence, and shared acquisition for HTTP |
| Local endpoint | Not started | Nothing listening | OpenAI-compatible HTTP and streaming surfaces |
| Agent | Basic local chat implemented | Multi-turn role rendering, structured reasoning disclosure, streamed DeepSeek Flash text, composer send/stop, and durable transcript rehydration | Tools, approvals, compaction, statistics, and KV-backed resume |
| Projects and sessions | Implemented with transcripts and KV resume | Native folder picker, project-name dialog, create/select/delete projects and sessions, durable structured chat history, and per-session KV checkpoints | Rename/archive and model binding |
| Persistence | Implemented for local chat | SQLite in Application Support, Diesel ORM, embedded migrations, constrained defaults, streamed message updates, reopen tests, context/speed state, and local KV rehydration | Downloaded-artifact metadata and session-runtime migrations; HTTP chats deliberately never enter SQLite |
| Model runtime | DeepSeek Flash vertical implemented | Rust-owned mmap/model/session graph, local Metal kernels, full configured sparse context, DS4 sampling, one process-wide UI/HTTP owner, cancellation, idle unloading, prefix continuation, and KV checkpoint save/load | DSpark/SSD/steering and GLM/Pro execution |
| Local endpoint | Chat Completions vertical implemented | Automatic configurable localhost listener (port 4000 by default), verified-on-disk model discovery, transient requests, non-streaming and SSE Chat Completions, reasoning, usage, sampling, stops, cancellation, tools, and exact in-process tool replay | GUI status and enable/disable controls, Responses, legacy Completions, Anthropic Messages, full `ds4_server.c` fixture parity, and cross-restart exact tool replay |
| Agent | Solid local chat implemented | Multi-turn role rendering, structured reasoning disclosure, Markdown, automatic scrolling, context/speed display, durable transcript rehydration, and KV-backed resume | Tools, approvals, compaction, and richer statistics |
| A2UI | Future extension | bDS2 provides the reference structured render-tool contract | Native inline surfaces for local chat after core chat and tools are stable |
| Dev brain | Not started | No vault access | Obsidian vault selection, durable access, and agent knowledge/memory tools |
| Release | Not started | Development binary builds and tests | `.app` packaging, signing, entitlements, and notarization |
@@ -28,8 +28,9 @@ without blocking the UI. Local chat now lazily acquires the Rust DeepSeek Flash
executor, streams generated text, supports cancellation and multi-turn role
replay, persists reasoning and answers while they stream, rehydrates selected
sessions, and unloads model resources after the configured idle timeout. The
current Rust graph supports DS4 compressed attention through 2K context; the sparse ratio-4 indexer,
KV checkpoints, HTTP service, and agent tools remain open.
current Rust graph supports the full configured context with ratio-4 sparse
attention and durable local KV checkpoints. The shared HTTP Chat Completions
service is available; the remaining protocol routes and agent tools remain open.
## Phase 0 — project and session shell
@@ -49,16 +50,16 @@ Exit criterion: restart the app and see the same project/session tree.
## Phase 1 — model library and on-demand loading
Status: **partially implemented**. The typed preference/download/intake path and
a DeepSeek Flash Rust/Metal vertical are implemented, including compressed KV
through 2K context, DS4 sampling, a single background owner, cancellation, and
idle unload. Sparse indexed attention, persistent KV/session sync, other model
families, and optional execution modes remain open.
a DeepSeek Flash Rust/Metal vertical are implemented, including full configured
context, ratio-4 sparse indexed attention, prefix continuation, KV checkpoint
save/load, DS4 sampling, cancellation, idle unload, and shared HTTP acquisition.
Other model families and optional execution modes remain open.
1. **Implemented:** the typed model/runtime preference contract described below
is complete before chat, the HTTP endpoint, or the engine lifecycle.
2. **Partially implemented:** mmap-backed loading, tokenizer/prompt rendering,
Rust-owned Flash session creation/evaluation/sampling, and model ownership
are complete. Prefix sync plus KV save/load remain.
2. **Implemented for DeepSeek Flash:** mmap-backed loading, tokenizer/prompt
rendering, Rust-owned session creation/evaluation/sampling, prefix sync, and
KV save/load are complete.
3. **Implemented:** retain the tested model restrictions from DwarfStar. Main
and DSpark artifacts reject invalid GGUF versions, catalog mismatches, and
incompatible metadata, tensor shapes, offsets, or quantization before
@@ -66,8 +67,11 @@ families, and optional execution modes remain open.
4. **Partially implemented:** reuse the existing `.metal` kernels and preserve mmap-backed resident
loading plus the explicit SSD-streaming path. Keep Objective-C only at the
Metal interop boundary.
5. **Implemented for local chat:** keep one engine resident at most, matching DwarfStar's instance-lock and
memory assumptions.
5. Keep exactly one model resident process-wide. Local chat and every endpoint
request must use the same owner and queue; model selection may replace the
resident model only after active work reaches a safe boundary. Multiple
simultaneously loaded models are out of scope because DS4Server targets
models large enough to consume most available memory.
6. **Implemented for local chat:** move engine work off the UI thread and support cooperative cancellation at
the safe session boundaries already defined by `ds4_session_sync`.
@@ -92,21 +96,25 @@ to Rust while preserving its semantics and on-disk compatibility:
checkpoints, model/quantization/context/payload-ABI compatibility checks,
tool-call replay metadata, disk-budget enforcement, hit tracking, and
eviction behavior.
- Use one process-wide Rust session registry and KV checkpoint store for both
local chat and the HTTP server. A remote request bound to an application
session resolves to the same logical DS4 session as the GUI and mutations are
serialized so two callers never write one live timeline concurrently.
Stateless HTTP requests still participate in the same compatible-prefix
checkpoint pool, so a prefix produced locally can be reused remotely and
vice versa.
- Store that shared checkpoint pool under Application Support in
`kv-cache/`, using the DS4 key/header/payload rules. Session rows hold only an
optional reference to their current checkpoint; the large opaque payload is
not copied into separate GUI and HTTP caches.
- Sharing does not merge unrelated conversations or expose one session's
transcript to another. It shares the DS4 implementation, checkpoint pool, and
explicitly bound logical session; each unrelated live conversation keeps its
own mutable session state.
- Use one process-wide Rust model owner and KV checkpoint implementation for
local chat and the HTTP server. Model access is serialized so two callers
never mutate the single resident inference graph concurrently.
- Application projects and sessions belong exclusively to local chat. HTTP
requests are transient and must never create projects, sessions, messages,
transcript rows, request-identity mappings, or any other durable chat record.
The outside client owns conversation identity and resends the history needed
for each request, exactly as `ds4_server.c` expects.
- Transient HTTP requests may read and write engine-owned KV cache files for
compatible-prefix reuse. Those opaque cache files are the only server-side
persistence permitted for external conversations and do not establish an
application session or recoverable transcript.
- Store engine-owned checkpoints under Application Support in `kv-cache/`.
Local chats use their application session ID; HTTP uses content-addressed
entries under `kv-cache/http/`. The large opaque payload is never stored in
SQLite, and HTTP cache entries do not create application sessions.
- Cache sharing does not merge conversations or expose transcript text. It
shares only the DS4 implementation and compatible opaque prefix checkpoints;
the request body remains the source of truth for external conversation state.
- Engine unload must first leave every reusable live timeline in a valid
persisted checkpoint. Reload restores a compatible checkpoint immediately;
if model identity, quantization, context size, payload ABI, or rendered
@@ -140,6 +148,8 @@ to Rust while preserving its semantics and on-disk compatibility:
- Let the user enable or disable DSpark for the selected model. Disable the
control for models that do not support DSpark.
- Let the user configure the inactivity timeout; default to 10 minutes.
- Let the user configure the localhost endpoint port; default to 4000 and apply
a changed port when preferences are saved.
- Persist preferences in the application SQLite database. Changing preferences
never loads a model by itself.
@@ -258,43 +268,58 @@ engine lifecycle.
## Phase 2 — OpenAI-compatible local endpoint
Status: **open; not started**.
Status: **partially implemented**. The shared single-model runtime,
verified-model discovery, configurable localhost port, and the first
`ds4_server.c`-compatible Chat Completions vertical are implemented. The
remaining protocol routes plus GUI status and enable/disable controls remain
open.
1. Add a localhost-only HTTP service whose lifecycle is independent of the
1. **Partially implemented:** add a localhost-only HTTP service whose lifecycle is independent of the
engine. It can listen while the model is unloaded and acquires the configured
model only for inference. Start and stop it from the GUI; never expose a LAN
listener without an explicit setting.
2. Implement the DwarfStar compatibility surface in this order:
model only for inference. It listens on configurable port 4000 by default;
start/stop controls remain open. Never expose a LAN listener without an
explicit setting.
2. Implement the DwarfStar compatibility surface from `ds4_server.c` in this
order, preserving its request parsing, prompt formatting, reasoning fields,
streaming events, errors, usage accounting, stop behavior, tool replay, and
cancellation semantics rather than inventing an app-specific protocol:
- `GET /v1/models`
- `POST /v1/chat/completions`
- `POST /v1/responses`
- `POST /v1/completions`
`GET /v1/models` reports the configured model without forcing it to load.
`GET /v1/models` scans managed artifacts without forcing a model to load and
reports only main model files that are fully downloaded and have a matching
on-disk verification marker. Missing, partial, unverified, or checksum-invalid
artifacts are never advertised.
3. Support streaming SSE, reasoning output, usage accounting, cancellation,
sampling parameters, tool schemas, and tool choice.
4. Port exact sampled tool-call replay and deterministic canonicalization from
`ds4_server.c` so a client's normalized JSON does not destroy KV-prefix
reuse.
5. Route API conversations through the shared session registry and KV store
defined in Phase 1. Responses `conversation` bindings and any other
persistent request identity map to the same application session used by the
GUI; stateless requests use shared DS4 prefix lookup. Persist/restore only
through the engine-owned serialization format. Add resident batching only
after single-session and cross-surface correctness are established.
5. Route API work through the single process-wide model owner and engine-owned
KV store defined in Phase 1. External requests are always stateless at the
application layer: never write their messages to SQLite and never bind a
Responses `conversation`, request identifier, or client-supplied session key
to a local project/session. The client must replay its protocol history;
DS4-compatible prefix lookup may accelerate that replay using opaque KV cache
files. Add resident batching only if one-model serialized execution is later
proven insufficient.
6. Display endpoint address, model, active requests, token rates, and errors in
the GUI.
Exit criterion: the upstream DwarfStar server tests pass against the app for
non-streaming and streaming Chat Completions and Responses calls, including a
multi-turn tool call. A compatible prefix created by local chat must register a
remote cache hit, a remote checkpoint must be reusable by local chat, and a
GUI/HTTP race on one bound session must serialize without corrupting its token
or KV frontier.
multi-turn tool call. Verified installed models are listed without loading one;
missing/unverified models are absent; local and HTTP generations cannot load two
models concurrently; external calls leave no project, session, message, or
transcript rows behind; and compatible KV cache files remain reusable.
## Phase 3 — durable agent sessions
Status: **partially implemented**. Project/session metadata and structured
transcripts are durable; model binding and KV payload persistence remain open.
Status: **partially implemented for local chat only**. Project/session metadata,
structured transcripts, context statistics, and per-session KV checkpoints are
durable. Model binding and archive behavior remain open. This phase never
applies to external endpoint conversations.
Extend each metadata-only session with transcript and shared-checkpoint
metadata:
@@ -306,14 +331,14 @@ Application Support/DS4Server.rfc1437.de/
main.gguf
dspark.gguf # only when enabled
kv-cache/
<ds4-cache-key>.kv
<local-session-id>.bin
http/<conversation-tag>.bin
```
- **Partially implemented:** migrated message tables persist user text,
reasoning state, and assistant output while generation streams. Add the
optional current-checkpoint key to SQLite. Keep large engine-owned KV
payloads only in the process-wide Phase 1 store, written with atomic
replacement; do not introduce separate agent-only or HTTP-only cache formats.
- **Implemented:** migrated message tables persist local user text, reasoning
state, assistant output, context usage, and generation speed while generation
streams. Local session KV payloads remain engine-owned files written with
atomic replacement; SQLite never stores opaque tensor data.
- Record model identity, context size, rendered token history, title,
timestamps, and working directory.
- Restore compatible KV immediately. If KV is absent or incompatible, rebuild
@@ -365,6 +390,11 @@ ephemeral presentation preferences are the only exception.
### Native macOS menus (required with chat)
Status: **partially implemented**. Application, Edit, and Window menus are
installed; Cut, Copy, Paste, and Select All now bridge into the focused Iced
text field. File/View/Help menus, dynamic enabled state, the remaining Edit
actions, and selectable transcript text remain open.
The application must install and maintain a complete native macOS menu bar;
the application-name menu alone is not sufficient. Use platform menu roles,
ordering, selectors, and standard key equivalents rather than drawing menus
@@ -493,11 +523,13 @@ tests, and release packaging remain open.
1. Finish the Flash session core as one larger slice: port the ratio-4 indexer
and indexed attention for full configured context, then add prefix sync and
compatible KV checkpoint save/load.
compatible KV checkpoint save/load. **Implemented.**
2. Put the OpenAI-compatible endpoint on the same engine lifecycle so local
chats and HTTP requests cannot load duplicate engines.
3. Add transcript/KV persistence, then connect the existing conversation UI and
finally port the agent tools.
chats and HTTP requests cannot load duplicate models. Keep HTTP conversation
state transient except for opaque KV cache files. **Chat Completions vertical
implemented; remaining reference routes are open.**
3. Local transcript/KV persistence and the conversation UI are implemented;
port the agent tools after the shared endpoint lifecycle is stable.
4. Add the opt-in Dev Brain tool after local file/search boundaries and agent
approvals are stable.
5. Add bDS2-style A2UI render tools and native inline surfaces after the core