Assistants-shaped API façade built on the OpenAI Responses API. Keep your Assistants-style client calls and migrate your backend to Responses at your own pace.
Stack: Fastify + TypeScript, Redis or Azure Cosmos DB persistence, Zod validation, structured logging, and clean architecture.
- Assistants: create / list / get / update
- Threads: create • Messages: list / add
- Runs: create / list / get — translates thread context into a single Responses call
- Clean architecture: domain, application (services), infrastructure (repos/clients), interfaces (HTTP controllers)
- MVC-ish controllers with Zod validation and presenters that shape responses like Assistants
- Structured logs (ISO timestamp, level, component, version)
- Node 18+
OPENAI_API_KEYin environment or.env- Choose a database provider:
- Redis (7+): set
REDIS_URL - Azure Cosmos DB: set
DATABASE_PROVIDER=cosmos,COSMOS_ENDPOINT,COSMOS_KEY,COSMOS_DATABASE_ID
- Redis (7+): set
npm install
echo "OPENAI_API_KEY=sk-..." > .env
echo "REDIS_URL=redis://localhost:6379" >> .env
npm run dev # http://localhost:3500npm install
echo "OPENAI_API_KEY=sk-..." > .env
echo "DATABASE_PROVIDER=cosmos" >> .env
echo "COSMOS_ENDPOINT=https://your-account.documents.azure.com:443/" >> .env
echo "COSMOS_KEY=your-cosmos-db-key" >> .env
echo "COSMOS_DATABASE_ID=assistants-api" >> .env
npm run dev # http://localhost:3500Build locally:
docker build -t open-assistants-api:local .
docker run --rm -p 3500:3500 \
-e OPENAI_API_KEY=sk-... \
-e REDIS_URL=redis://host.docker.internal:6379 \
open-assistants-api:localRun with Cosmos DB:
docker run --rm -p 3500:3500 \
-e OPENAI_API_KEY=sk-... \
-e DATABASE_PROVIDER=cosmos \
-e COSMOS_ENDPOINT=https://your-account.documents.azure.com:443/ \
-e COSMOS_KEY=your-cosmos-db-key \
-e COSMOS_DATABASE_ID=assistants-api \
open-assistants-api:localImages are published to GHCR on release:
ghcr.io/LumenLabsDev/open-assistants-api:latestghcr.io/LumenLabsDev/open-assistants-api:<version>
GitHub Releases also attach an linux-amd64 image tar for offline use.
.env:
OPENAI_API_KEY=sk-...
PORT=3500
# Select database provider (default = redis)
DATABASE_PROVIDER=redis
# When using Redis
REDIS_URL=redis://localhost:6379
# When using Azure Cosmos DB
# DATABASE_PROVIDER=cosmos
# COSMOS_ENDPOINT=https://your-account.documents.azure.com:443/
# COSMOS_KEY=your-cosmos-db-key
# COSMOS_DATABASE_ID=assistants-apicurl -s http://localhost:3500/health
# {"status":"ok"}- Assistants resources include
object: "assistant",created_at. - Threads:
object: "thread"withcreated_at. - Messages returned as
object: "list"of items shaped like Assistants messages (text content parts). - Runs returned as
object: "thread.run".
- Create assistant
curl -s -X POST http://localhost:3500/v1/assistants -H 'content-type: application/json' \
-d '{"name":"Helper","instructions":"Answer briefly.","model":"gpt-4o-mini"}'- Create thread
curl -s -X POST http://localhost:3500/v1/threads- Add message
curl -s -X POST http://localhost:3500/v1/threads/<thread_id>/messages -H 'content-type: application/json' \
-d '{"role":"user","content":"Hello!"}'- Run
curl -s -X POST http://localhost:3500/v1/threads/<thread_id>/runs -H 'content-type: application/json' \
-d '{"assistant_id":"<assistant_id>"}'- List messages
curl -s http://localhost:3500/v1/threads/<thread_id>/messages- POST
/v1/assistants— create assistant{ name?, model?, instructions? } - GET
/v1/assistants— list assistants - GET
/v1/assistants/:assistant_id— get assistant - POST
/v1/assistants/:assistant_id— update assistant - POST
/v1/threads— create thread - GET
/v1/threads/:thread_id/messages— list messages (list wrapper) - POST
/v1/threads/:thread_id/messages— add message{ role, content }(roleinuser|assistant|system) - POST
/v1/threads/:thread_id/runs— create run{ assistant_id, temperature? } - GET
/v1/threads/:thread_id/runs— list runs (list wrapper) - GET
/v1/threads/:thread_id/runs/:run_id— get run
src/
domain/ # Entities and interfaces (ports)
application/ # Use cases (services)
infra/ # Redis/Cosmos repositories, OpenAI Responses client, ID generator
interfaces/http/ # Controllers, presenters, DTOs, validation, error handling
container.ts # Simple DI wiring (switches infra via env)
server.ts # Fastify bootstrap
- Persistence requires a DB provider (Redis or Cosmos). Configure env vars accordingly.
- Add streaming endpoints and tool-calls if needed.
- The included OpenAPI YAMLs serve as references; not used for runtime validation.
Component layering (Clean Architecture-inspired):
graph TD
subgraph Interfaces_HTTP
Ctr[Controllers]
Val[Zod Schemas]
Pres[Presenters]
end
subgraph Application_Use_Cases
SvcA[AssistantsService]
SvcT[ThreadsService]
SvcR[RunsService]
end
subgraph Domain
Types[Types]
Ports[Ports]
Errors[Errors]
Models[Models]
end
subgraph Infrastructure_Layer
Repos[(Redis Repos)]
Resp[OpenAI Responses Client]
IdGen[UUID Id Generator]
Redis[(Redis)]
OpenAI[(OpenAI Responses)]
end
Ctr -->|validate| Val
Ctr -->|call| SvcA
Ctr -->|call| SvcT
Ctr -->|call| SvcR
SvcA --> Ports
SvcT --> Ports
SvcR --> Ports
SvcA --> Types
SvcT --> Types
SvcR --> Types
Repos -->|implements| Ports
Resp -->|implements| Ports
IdGen --> Repos
Repos -->|persist| Redis
Resp -->|invoke| OpenAI
Pres --> Ctr
Run flow (create run over a thread):
sequenceDiagram
participant Client
participant HTTP as HTTP_Controller
participant Runs as RunsService
participant Assist as AssistantsRepo_Redis
participant Msg as MessagesRepo_Redis
participant RunRepo as RunsRepo_Redis
participant OA as OpenAI_Responses
Client->>HTTP: POST /v1/threads/:id/runs { assistant_id }
HTTP->>Runs: createRun(threadId, assistantId)
Runs->>Assist: get(assistantId)
Assist-->>Runs: assistant
Runs->>RunRepo: create({ queued })
Runs->>Msg: listByThread(threadId)
Runs->>OA: createTextResponse(model, input)
OA-->>Runs: text
Runs->>Msg: add({ role: assistant, content: text })
Msg-->>Runs: message{id}
Runs->>RunRepo: update({ completed, responseMessageId })
Runs-->>HTTP: run
HTTP-->>Client: 201 { run }
- Clean Architecture layering:
- Domain: core types, errors, ports (
src/domain) - Application: use cases (
src/application) - Interfaces: HTTP controllers/validation/presenters (
src/interfaces/http) - Infrastructure: repos and external clients (
src/infra)
- Domain: core types, errors, ports (
- Dependency flow: outer layers depend inward; infra implements domain ports.
- CI:
.github/workflows/ci.yml— build and test on Node 18/20/22; matrix includes runs with and without Redis service. Tests that require Redis are skipped whenREDIS_URLis not set. - Release:
.github/workflows/release.yml— on release frommain, pushes multi-arch Docker images to GHCR and uploads an amd64 image tar to the release.
npm test # run once
npm run test:watchTests include:
- Services tests using Redis repos (set
REDIS_URL) and a fake Responses client - HTTP integration test booting Fastify via
buildApp()
MIT