@huyang2024/dsh-openai-api

August 29, 2026 · View on GitHub

OpenAI-compatible HTTP surface for DeepSeek Harness, backed by real DSH agent sessions.

After this plugin is installed into a dsh profile, the harness web server additionally serves:

EndpointPurpose
POST /v1/chat/completionsChat Completions API (streaming + non-streaming)
POST /v1/responsesResponses API subset (streaming + non-streaming)
GET /v1/modelsAdvertised model id

What "agent-backed" means

Each request is served by a live DSH agent session (the harness agents registry + the agent loop), so every call gets the agent's full world: its configured tools, bash/files access, skills, prompt sections, and preset persona. This is an agent gateway, not a thin LLM proxy:

  • The agent owns tool use. Client-side tools / tool_choice / function-call messages are rejected — the agent decides what tools to call and why.
  • Streaming deltas arrive once per committed assistant message, not per token. Reasoning, tool calls, and token-level chunk streams stay off the wire; only committed assistant text reaches the client.
  • The first request for a (key, preset) pair seeds a session by rendering the client's full message array into a labeled transcript prompt; later requests admit only the latest user turn (the session already holds the history).

Session identity

(API key tenant, X-Agent-Preset):

  • The accepted apiKey namespaces the caller — one session per key, so different keys never share a session even for the same preset, and a client resumes its own session on reconnect.
  • The X-Agent-Preset header names the preset the session joins (absent → the profile default). Resolved only at session creation.

Install

# from GitHub
dsh plugin --profile web add github:huyang2024/dsh-openai-api

# or from a local checkout
dsh plugin --profile web add file:/absolute/path/to/dsh-openai-api

Then compose an insert in the profile's user patch layer — $DSH_HOME/profiles/web/cordis.patch.yml. New rows reach the tree only through insert::

- insert:
    - id: openai-api
      name: '@huyang2024/dsh-openai-api'
      inject: [webServer]
      config:
        pathPrefix: '/v1'     # optional, default shown
        apiKeys: []           # optional; see below
        model: default        # optional; model id advertised at /v1/models
        provider: ''          # optional; provider route for created sessions
        cwd: ''               # optional; absolute working directory for sessions

Restart the profile so the new row activates.

Configuration

All keys optional; created sessions fall back to the host defaults.

keydefaultwhat it does
modeldefaultModel id advertised at /v1/models. A request's model (absent, or this id) uses the host default selection; any other id is passed to the session as the model.
providerhost defaultProvider route for created sessions.
apiKeysunsetPer-client keys. Each distinct key is a separate tenant — one session per (key, preset). When set, a request must present one (Authorization: Bearer <key>); otherwise it is a 401. Unset, the surface is a single keyless tenant restricted to loopback callers.
cwdthe dsh web process's cwdAbsolute working directory for created sessions. Must start with /.

Using the API

import OpenAI from 'openai'
const client = new OpenAI({ baseURL: 'http://127.0.0.1:3080/v1', apiKey: 'local-key' })

// Optional: choose the agent preset the session joins (absent → default preset).
const preset = { 'X-Agent-Preset': 'custom-agent-preset' }

const reply = await client.chat.completions.create(
  { model: 'default', messages: [{ role: 'user', content: 'What are you?' }] },
  { headers: preset },
)

/v1/responses follows the same session model; previous_response_id is accepted and ignored (the sticky session is the store).

Auth and trust

  • With apiKeys set, an exact Authorization: Bearer <key> is required and the matched key is the tenant.
  • Without apiKeys, only loopback callers are served (a deliberate hardening over the reference implementation, so an accidental LAN bind never exposes unauthenticated agent access).
  • Browser cross-origin calls from other origins are rejected unless listed in allowedOrigins, or authenticated with a valid key. Preflight OPTIONS is answered on all three paths.

Local development

node --check lib/index.js     # syntax
node test/smoke.mjs           # agent-backed protocol tests against a faked agents service