motif64

Chat · DeepSeek V4.1 Flash · OpenRouter · CPU/API

published by @dh7 on

m64 m64_04b2a07e4eac
Download .tar.gz

DeepSeek V4.1 Flash chat endpoint (OpenRouter). An OpenAI-compatible chat endpoint for DeepSeek V4.1 Flash on OpenRouter, for opencode (or any client) running on your own machine: opencode and its file access stay there, only the model calls come here. Streams. No GPU; the only egress is openrouter.ai:443, and the m64 gate adds the OpenRouter key, so the capsule never holds it.

Lineage

parent
none declared
derived from
no published capsule shares half its files
children
none

Files

5 files

AGENT.md

deepseek-flash-openrouter-gb10

An OpenAI-compatible chat endpoint for DeepSeek V4.1 Flash (OpenRouter model deepseek/deepseek-v4.1-flash). It only provides the model: run opencode (or any OpenAI-compatible client) on your own machine and point it here. Your files and tools stay on your machine; only the model calls come to this capsule.

No GPU. The container's only egress is openrouter.ai:443, through the m64 gate, which adds the OpenRouter key: the capsule never holds it.

Routes

All paths are relative to the capsule's URL (e.g. http://<tailnet-ip>:<port>).

Method Path What
POST /v1/chat/completions OpenAI chat completions, streaming or not
GET /v1/models one model: deepseek-v4.1-flash
GET /status phase (ready when usable)
GET /agent this document plus live status
GET /human/ landing page: opencode setup and a quick-test chat

POST /v1/chat/completions

Body: application/json only (415 otherwise). Accepted fields:

  • messages (required), tools, tool_choice, response_format, max_tokens, stream, capsule;
  • model, user, stream_options are accepted and ignored or passed on; n must be 1. The model is always DeepSeek V4.1 Flash, whatever model says.

Any other field (e.g. temperature, top_p) is refused with 400 naming the field.

stream: true returns Server-Sent Events (data: {...} chunks, ending with data: [DONE]), relayed as OpenRouter sends them. OpenRouter may interleave SSE comment lines (: OPENROUTER PROCESSING) while the model thinks; SSE clients ignore them. Non-streaming responses are OpenRouter's JSON, plus params_used.

Errors from OpenRouter (rate limits, upstream failures) keep their status and body.

curl -s $URL/v1/chat/completions -H 'Content-Type: application/json' \
  -d '{"messages":[{"role":"user","content":"Say hi"}],"max_tokens":50}'

curl -N $URL/v1/chat/completions -H 'Content-Type: application/json' \
  -d '{"messages":[{"role":"user","content":"Count to 5"}],"stream":true}'

opencode on your machine

~/.config/opencode/opencode.json (global) or opencode.json in the project root:

{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "dsflash": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "DeepSeek V4.1 Flash (capsule)",
      "options": { "baseURL": "<capsule URL>/v1" },
      "models": { "deepseek-v4.1-flash": { "name": "DeepSeek V4.1 Flash", "tool_call": true } }
    }
  },
  "model": "dsflash/deepseek-v4.1-flash"
}

Or, on a machine with m64: m64 deepseek-flash-openrouter-gb10 --ui opencode serves the capsule and starts opencode with a generated config pointing at it.

Access

On the tailnet (the default bind) there is no token: anyone on the tailnet can use the endpoint and so spend the OpenRouter key. With a token (--host 0.0.0.0 etc.), send Authorization: Bearer <token> (opencode: "apiKey": "<token>" in the provider options).

capsule.toml

schema = "m64/capsule@1"description = "DeepSeek V4.1 Flash chat endpoint (OpenRouter). An OpenAI-compatible chat endpoint for DeepSeek V4.1 Flash on OpenRouter, for opencode (or any client) running on your own machine: opencode and its file access stay there, only the model calls come here. Streams. No GPU; the only egress is openrouter.ai:443, and the m64 gate adds the OpenRouter key, so the capsule never holds it."kinds = ["chat"]authors = ["dh7"]license = "apache-2.0"[hardware]platform = "linux-aarch64"[runtime]sandbox = "container"# python:3.12-slim (multi-arch index digest). server.py uses the standard library only.image = "python@sha256:2f17fc044b579bab302c2e8054d3a686e2cb9a83de48e70534b94cd8ebbe06a9"gpu = falseinternal_port = 9600health = "/health"expected_load_seconds = 20# The front rewrites every request's "model" to this; server.py maps it to# deepseek/deepseek-v4.1-flash on OpenRouter. Also the name `m64 <capsule> --ui opencode` uses.model_name = "deepseek-v4.1-flash"user = "65534:65534"[network]needs = ["openrouter.ai:443"]why = "the model runs on OpenRouter: your prompts and its replies go through openrouter.ai"[secrets]required = ["OPENROUTER_API_KEY"]# The key never enters the container: server.py calls http://openrouter.ai/api/v1 through# the gate, which adds the header and goes on over TLS. The capsule's env var holds only the# placeholder m64-injected:OPENROUTER_API_KEY (and server.py does not even read it).[[secrets.inject]]name = "OPENROUTER_API_KEY"host = "openrouter.ai"header = "Authorization"value = "Bearer {secret}"