Skip to content
 
 

Repository files navigation

simple-chat logo

simple-chat

A tiny self-hosted chat gateway that speaks the OpenAI API protocol.

One binary. One model. No bells and whistles.

Telegram Go License: MIT Docker

Send standard OpenAI-format chat requests, get standard OpenAI-format responses back.


✨ Features

  • 🗣️ POST /v1/chat/completions — stream & non-stream, OpenAI protocol
  • 🧠 Deep thinking by default — reasoning exposed as reasoning_content, opt-out per request
  • 🖼️ Image understanding — image_url content parts (http(s) URLs & base64 data URLs)
  • 📋 GET /v1/models — exactly one model: deepseek-flash
  • 🔄 Multi-account rotation — round-robin pool with in-flight caps & health states
  • 🛡️ Hardened failure paths — bounded retry ladder, stream idle watchdog, honest error termination
  • 🔑 Optional API key auth — DS_API_KEY (unset = open access)
  • 🧹 Async session cleanup — upstream sessions deleted after every completion, off the response path
  • 📦 Single static binary — distroless Docker image, zero external Go dependencies

🚀 Quick start

cp .env.example .env          # optional API key
# create accounts.json:
#   {"accounts":[{"mobile":"...","email":"","password":"..."}]}
chmod 600 accounts.json
docker compose up -d          # serves :9879 -> 8080

Point any OpenAI client at http://host:9879/v1 with model deepseek-flash:

curl http://localhost:9879/v1/chat/completions \
  -H "Authorization: Bearer $DS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"deepseek-flash","messages":[{"role":"user","content":"hi"}]}'

⚙️ Configuration

Env Default Purpose
DS_ADDR :8080 listen address
DS_ACCOUNTS ./accounts.json account file (0600)
DS_API_KEY unset require Authorization: Bearer / X-Api-Key when set
DS_MAX_INFLIGHT 2 per-account concurrent request cap

Deep thinking is on by default; opt out per request with "thinking": {"type": "disabled"}:

{"model": "deepseek-flash",
 "thinking": {"type": "disabled"},   // omit or "enabled" for thinking (default)
 "messages": [{"role": "user", "content": "hi"}]}

Reasoning arrives as reasoning_content — streamed as delta.reasoning_content chunks before the delta.content chunks, and as message.reasoning_content on non-stream responses. Thinking is best-effort: if the upstream model skips thinking, you simply get an answer with no reasoning. Malformed switch values (e.g. {"type": "banana"}) are rejected with 400.

🏗️ Build

go build -o simple-chat .
go test ./...

📐 Design

spec.md documents the full design and upstream contract; gap-analysis.md records what we absorbed from a survey of 14+ similar projects — and what we deliberately rejected.

Statelessness is the design: full history passed on every call, a fresh upstream session per request, deleted asynchronously after. The client owns the conversation.

📄 License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages