Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00871642a4 | ||
|
|
83bfac37ee | ||
|
|
10b0f4be3a | ||
|
|
a1cf2e5395 | ||
|
|
35052e95d2 | ||
|
|
5a690460cd | ||
|
|
0882ef6d1c | ||
|
|
ca6f2d7a2a | ||
|
|
bfb4cb3f66 | ||
|
|
30fd72ec1b | ||
|
|
cd9624b434 | ||
|
|
6048f68105 | ||
|
|
b8605e89dc | ||
|
|
86f827494d | ||
|
|
15aace307b | ||
|
|
89a499febb | ||
|
|
317e24a032 | ||
|
|
4d67f7d81b | ||
|
|
071bcf6f4d | ||
|
|
f599632f57 | ||
|
|
c4e841c2b9 | ||
|
|
46657ba812 | ||
|
|
e6737188c1 | ||
|
|
2c832aa8b9 | ||
|
|
fd3d031cc7 | ||
|
|
7e6c10299f | ||
|
|
0f8eea1e3a | ||
|
|
751b8122ab | ||
|
|
32d3485663 | ||
|
|
7b05ada0ca | ||
|
|
88cd45da4b | ||
|
|
d8b20d61e9 | ||
|
|
91e9711e6b | ||
|
|
d0b64340fe | ||
|
|
fb7787c87a |
@@ -1,15 +0,0 @@
|
||||
# used by CI/CD testing
|
||||
openai==1.81.0
|
||||
python-dotenv
|
||||
tiktoken
|
||||
importlib_metadata
|
||||
cohere
|
||||
redis==5.2.1
|
||||
redisvl==0.4.1
|
||||
anthropic
|
||||
orjson==3.10.12 # fast /embedding responses
|
||||
pydantic==2.10.2
|
||||
google-cloud-aiplatform==1.43.0
|
||||
fastapi-sso==0.16.0
|
||||
uvloop==0.21.0
|
||||
mcp==1.9.3 # for MCP server
|
||||
@@ -1,62 +0,0 @@
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: .devcontainer/Dockerfile
|
||||
# restart: always
|
||||
links:
|
||||
- mongodb
|
||||
- meilisearch
|
||||
# ports:
|
||||
# - 3080:3080 # Change it to 9000:3080 to use nginx
|
||||
extra_hosts: # if you are running APIs on docker you need access to, you will need to uncomment this line and next
|
||||
- "host.docker.internal:host-gateway"
|
||||
|
||||
volumes:
|
||||
# This is where VS Code should expect to find your project's source code and the value of "workspaceFolder" in .devcontainer/devcontainer.json
|
||||
- ..:/workspaces:cached
|
||||
# Uncomment the next line to use Docker from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker-compose for details.
|
||||
# - /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- HOST=0.0.0.0
|
||||
- MONGO_URI=mongodb://mongodb:27017/HanzoChat
|
||||
# - OPENAI_REVERSE_PROXY=http://host.docker.internal:8070/v1
|
||||
- MEILI_HOST=http://meilisearch:7700
|
||||
|
||||
# Runs app on the same network as the service container, allows "forwardPorts" in devcontainer.json function.
|
||||
# network_mode: service:another-service
|
||||
|
||||
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
|
||||
# (Adding the "ports" property to this file will not forward from a Codespace.)
|
||||
|
||||
# Use a non-root user for all processes - See https://aka.ms/vscode-remote/containers/non-root for details.
|
||||
user: vscode
|
||||
|
||||
# Overrides default command so things don't shut down after the process ends.
|
||||
command: /bin/sh -c "while sleep 1000; do :; done"
|
||||
|
||||
mongodb:
|
||||
container_name: chat-mongodb
|
||||
expose:
|
||||
- 27017
|
||||
# ports:
|
||||
# - 27018:27017
|
||||
image: mongo
|
||||
# restart: always
|
||||
volumes:
|
||||
- ./data-node:/data/db
|
||||
command: mongod --noauth
|
||||
meilisearch:
|
||||
container_name: chat-meilisearch
|
||||
image: getmeili/meilisearch:v1.5
|
||||
# restart: always
|
||||
expose:
|
||||
- 7700
|
||||
# Uncomment this to access meilisearch from outside docker
|
||||
# ports:
|
||||
# - 7700:7700 # if exposing these ports, make sure your master key is not the default value
|
||||
environment:
|
||||
- MEILI_NO_ANALYTICS=true
|
||||
- MEILI_MASTER_KEY=5c71cf56d672d009e36070b5bc5e47b743535ae55c818ae3b735bb6ebfb4ba63
|
||||
volumes:
|
||||
- ./meili_data_v1.5:/meili_data
|
||||
@@ -1,52 +1,18 @@
|
||||
{
|
||||
"name": "Python 3.11",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/python:3.11-bookworm",
|
||||
// https://github.com/devcontainers/images/tree/main/src/python
|
||||
// https://mcr.microsoft.com/en-us/product/devcontainers/python/tags
|
||||
|
||||
// "build": {
|
||||
// "dockerfile": "Dockerfile",
|
||||
// "context": ".."
|
||||
// },
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {},
|
||||
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
// Configure properties specific to VS Code.
|
||||
"vscode": {
|
||||
"settings": {},
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance",
|
||||
"GitHub.copilot",
|
||||
"GitHub.copilot-chat",
|
||||
"ms-python.autopep8"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [4000],
|
||||
|
||||
"containerEnv": {
|
||||
"LITELLM_LOG": "DEBUG"
|
||||
},
|
||||
|
||||
// Use 'portsAttributes' to set default properties for specific forwarded ports.
|
||||
// More info: https://containers.dev/implementors/json_reference/#port-attributes
|
||||
"portsAttributes": {
|
||||
"4000": {
|
||||
"label": "LiteLLM Server",
|
||||
"onAutoForward": "notify"
|
||||
}
|
||||
},
|
||||
|
||||
// More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "litellm",
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "pipx install poetry && poetry install -E extra_proxy -E proxy"
|
||||
}
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspaces",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [],
|
||||
"settings": {
|
||||
"terminal.integrated.profiles.linux": {
|
||||
"bash": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"postCreateCommand": "",
|
||||
"features": { "ghcr.io/devcontainers/features/git:1": {} },
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
|
||||
@@ -19,8 +19,9 @@ services:
|
||||
# - /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- HOST=0.0.0.0
|
||||
- MONGO_URI=mongodb://mongodb:27017/LibreChat
|
||||
# - OPENAI_REVERSE_PROXY=http://host.docker.internal:8070/v1
|
||||
- MONGO_URI=mongodb://mongodb:27017/HanzoChat
|
||||
# - CHATGPT_REVERSE_PROXY=http://host.docker.internal:8080/api/conversation # if you are hosting your own chatgpt reverse proxy with docker
|
||||
# - OPENAI_REVERSE_PROXY=http://host.docker.internal:8070/v1/chat/completions # if you are hosting your own chatgpt reverse proxy with docker
|
||||
- MEILI_HOST=http://meilisearch:7700
|
||||
|
||||
# Runs app on the same network as the service container, allows "forwardPorts" in devcontainer.json function.
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# Caddy reverse proxy with bearer token auth and automatic HTTPS.
|
||||
# The domain is supplied via environment variable GITNEXUS_DOMAIN,
|
||||
# and the auth token via API_TOKEN. Both are set in docker-compose.yml.
|
||||
|
||||
{$GITNEXUS_DOMAIN} {
|
||||
# Health check — unauthenticated so monitoring can probe it
|
||||
@health path /health
|
||||
handle @health {
|
||||
reverse_proxy gitnexus:4747 {
|
||||
rewrite /api/info
|
||||
}
|
||||
}
|
||||
|
||||
# All other routes require bearer token
|
||||
@authed {
|
||||
header Authorization "Bearer {$API_TOKEN}"
|
||||
}
|
||||
|
||||
handle @authed {
|
||||
reverse_proxy gitnexus:4747
|
||||
}
|
||||
|
||||
# Reject unauthenticated requests
|
||||
respond "Unauthorized" 401
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
# Long-lived GitNexus image for DigitalOcean droplet deployment.
|
||||
#
|
||||
# This image does NOT bake in the index data. Indexes are mounted from
|
||||
# the host at /indexes/<repo>/.gitnexus/ and registered at container
|
||||
# startup. A fresh index only requires rsync + container restart — no
|
||||
# image rebuild on every push.
|
||||
|
||||
FROM node:24.16.0-slim
|
||||
|
||||
ARG GITNEXUS_VERSION=1.5.3
|
||||
|
||||
# 1. Build native addons with Bookworm toolchain, then remove build tools.
|
||||
# curl stays for the docker healthcheck; Caddy lives in its own container.
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends python3 make g++ curl \
|
||||
&& npm install -g gitnexus@${GITNEXUS_VERSION} \
|
||||
&& apt-get purge -y --auto-remove python3 make g++ \
|
||||
&& rm -rf /var/lib/apt/lists/* /root/.npm
|
||||
|
||||
# 2. Upgrade libstdc++ from Trixie — @ladybugdb/core prebuilt binary needs
|
||||
# GLIBCXX_3.4.32 which Bookworm (3.4.31) doesn't ship.
|
||||
RUN echo "deb http://deb.debian.org/debian trixie main" > /etc/apt/sources.list.d/trixie.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y -t trixie libstdc++6 \
|
||||
&& rm /etc/apt/sources.list.d/trixie.list \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 3. Pre-install LadybugDB FTS + vector extensions so ~/.kuzu/extension/
|
||||
# is baked into the image. Workaround for upstream GitNexus 1.5.3 bug.
|
||||
COPY install-extensions.js /tmp/install-extensions.js
|
||||
RUN node /tmp/install-extensions.js && rm -rf /tmp/install-extensions.js /tmp/lbug-ext-install
|
||||
|
||||
# 4. Patch lbug-adapter.js to also LOAD EXTENSION vector after FTS.
|
||||
RUN LBUG_ADAPTER=/usr/local/lib/node_modules/gitnexus/dist/mcp/core/lbug-adapter.js \
|
||||
&& grep -q "LOAD EXTENSION fts" "$LBUG_ADAPTER" \
|
||||
&& sed -i "s|await available\[0\]\.query('LOAD EXTENSION fts');|await available[0].query('LOAD EXTENSION fts'); try { await available[0].query('LOAD EXTENSION vector'); } catch (e) { /* vector extension may not be installed */ }|g" "$LBUG_ADAPTER" \
|
||||
&& grep -c "LOAD EXTENSION vector" "$LBUG_ADAPTER" \
|
||||
&& echo "lbug-adapter.js patched to load vector extension"
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
EXPOSE 4747
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
@@ -1,87 +0,0 @@
|
||||
# GitNexus stack for the DigitalOcean droplet.
|
||||
#
|
||||
# Two services: the gitnexus server (bound to an internal network only)
|
||||
# and a Caddy reverse proxy that handles TLS + auth.
|
||||
#
|
||||
# Index data lives on the host at /opt/gitnexus/indexes/ and is
|
||||
# bind-mounted read-write into the gitnexus container. The deploy
|
||||
# workflow rsyncs fresh indexes into that directory and restarts
|
||||
# only the gitnexus container — Caddy keeps running undisturbed.
|
||||
#
|
||||
# Break-glass: if gitnexus is stuck unhealthy and you need to restart
|
||||
# just Caddy (e.g. to push an emergency Caddyfile fix), the
|
||||
# `depends_on: condition: service_healthy` would block:
|
||||
# docker compose up -d caddy
|
||||
# Use --no-deps to bypass the dependency check:
|
||||
# docker compose up -d --no-deps caddy
|
||||
|
||||
name: gitnexus
|
||||
|
||||
# Shared logging defaults applied to both services so the droplet's
|
||||
# disk doesn't fill up with unbounded json-file logs.
|
||||
x-logging: &default-logging
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: '50m'
|
||||
max-file: '3'
|
||||
|
||||
services:
|
||||
gitnexus:
|
||||
# Override via GITNEXUS_IMAGE in /opt/gitnexus/.env to use a fork or
|
||||
# a pinned version tag like :v1.5.3 for reproducible rollbacks.
|
||||
image: ${GITNEXUS_IMAGE:-ghcr.io/danny-avila/librechat-gitnexus:latest}
|
||||
container_name: gitnexus
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- gitnexus-net
|
||||
volumes:
|
||||
- /opt/gitnexus/indexes:/indexes
|
||||
# memswap_limit equal to mem_limit disables swap for this container.
|
||||
# Without it, Docker lets the process silently swap onto host disk,
|
||||
# turning sub-second graph queries into multi-second ones. Hard
|
||||
# OOM-kill is preferable — the container restarts via unless-stopped,
|
||||
# the deploy health poll catches it, and the failure is explicit.
|
||||
mem_limit: 1792m
|
||||
memswap_limit: 1792m
|
||||
logging: *default-logging
|
||||
healthcheck:
|
||||
test: ['CMD', 'curl', '-fsS', 'http://127.0.0.1:4747/api/info']
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
|
||||
caddy:
|
||||
image: caddy:2-alpine
|
||||
container_name: gitnexus-caddy
|
||||
restart: unless-stopped
|
||||
# service_healthy (not just service_started) ensures Caddy doesn't
|
||||
# start routing traffic until gitnexus passes its initial healthcheck
|
||||
# on a cold `compose up`. This only governs initial startup ordering —
|
||||
# during force-recreates of gitnexus, Caddy stays up and may briefly
|
||||
# return 502 while the new gitnexus container binds its port. The
|
||||
# deploy workflow's health poll catches any sustained failure.
|
||||
depends_on:
|
||||
gitnexus:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- '80:80'
|
||||
- '443:443'
|
||||
networks:
|
||||
- gitnexus-net
|
||||
volumes:
|
||||
- /opt/gitnexus/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- caddy-data:/data
|
||||
- caddy-config:/config
|
||||
logging: *default-logging
|
||||
environment:
|
||||
GITNEXUS_DOMAIN: ${GITNEXUS_DOMAIN}
|
||||
API_TOKEN: ${API_TOKEN}
|
||||
|
||||
networks:
|
||||
gitnexus-net:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
caddy-data:
|
||||
caddy-config:
|
||||
@@ -1,48 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Cap Node heap below the container's cgroup limit (1792m in compose),
|
||||
# leaving room for @ladybugdb/core's C++ heap and OS overhead. Native
|
||||
# allocations happen outside V8's view, so a slim V8 budget is the only
|
||||
# thing between a heavy query and a cgroup OOM-kill. Without this cap,
|
||||
# gitnexus defaults to --max-old-space-size=8192 and reserves memory
|
||||
# the container doesn't have.
|
||||
export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=1280}"
|
||||
|
||||
# Register every index mounted under /indexes/<name>/.gitnexus/.
|
||||
# This is idempotent — re-registering an existing repo updates the
|
||||
# metadata pointer without touching the index data.
|
||||
#
|
||||
# Registration failure handling:
|
||||
# - main (LibreChat) and dev (LibreChat-dev) are critical. If either
|
||||
# fails to register, exit 1 so docker marks the container unhealthy
|
||||
# and the deploy workflow's readiness check surfaces the error.
|
||||
# - PR indexes (LibreChat-pr-*) are best-effort. A corrupt PR index
|
||||
# shouldn't take the whole server down.
|
||||
if [ -d /indexes ]; then
|
||||
for dir in /indexes/*/; do
|
||||
[ -d "$dir" ] || continue
|
||||
name=$(basename "$dir")
|
||||
[ -d "$dir.gitnexus" ] || continue
|
||||
echo "Registering index: $name"
|
||||
if ! gitnexus index "$dir" --allow-non-git; then
|
||||
case "$name" in
|
||||
LibreChat|LibreChat-dev)
|
||||
echo "ERROR: failed to register critical index $name" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "WARN: failed to register PR index $name — skipping" >&2
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "WARN: /indexes directory not mounted" >&2
|
||||
fi
|
||||
|
||||
# Bind 0.0.0.0 inside the container so Caddy (in a separate container
|
||||
# on the same docker network) can reach gitnexus at gitnexus:4747.
|
||||
# docker-compose.yml intentionally does NOT expose port 4747 on the
|
||||
# host — only Caddy's 80/443 are published.
|
||||
exec gitnexus serve --host 0.0.0.0 --port 4747
|
||||
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Pre-install LadybugDB extensions (FTS + vector) into the Docker image's
|
||||
* extension cache (~/.kuzu/extension/). Without this, gitnexus serve's
|
||||
* lbug-adapter calls LOAD EXTENSION fts at runtime but fails silently
|
||||
* because the extension was never installed, causing all BM25 and
|
||||
* semantic queries via the query() tool to return empty.
|
||||
*
|
||||
* Workaround for upstream GitNexus 1.5.3 bug where the CI-produced
|
||||
* .gitnexus/ artifact doesn't include the extension cache.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
// @ladybugdb/core lives under the globally-installed gitnexus package.
|
||||
// This path is stable across gitnexus versions because npm always nests
|
||||
// transitive deps under the installed package's node_modules.
|
||||
const lbugPath = '/usr/local/lib/node_modules/gitnexus/node_modules/@ladybugdb/core';
|
||||
const lbug = require(lbugPath);
|
||||
|
||||
const tmpDir = '/tmp/lbug-ext-install';
|
||||
fs.mkdirSync(tmpDir, { recursive: true });
|
||||
|
||||
// Open a throwaway database just to run INSTALL against. The extension
|
||||
// cache persists in ~/.kuzu/extension/ regardless of which database was
|
||||
// used to install it, so the throwaway db and tmpDir are deleted in the
|
||||
// Dockerfile after this script finishes.
|
||||
const db = new lbug.Database(path.join(tmpDir, 'db'), 0, false, false);
|
||||
const conn = new lbug.Connection(db);
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await conn.query('INSTALL fts');
|
||||
console.log('FTS extension installed');
|
||||
} catch (err) {
|
||||
console.error('FTS install failed:', err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
try {
|
||||
await conn.query('INSTALL vector');
|
||||
console.log('Vector extension installed');
|
||||
} catch (err) {
|
||||
console.error('Vector install failed:', err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
+16
-16
@@ -1,17 +1,17 @@
|
||||
docs
|
||||
cookbook
|
||||
.circleci
|
||||
.github
|
||||
tests
|
||||
.git
|
||||
.github
|
||||
.circleci
|
||||
.devcontainer
|
||||
*.tgz
|
||||
log.txt
|
||||
docker/Dockerfile.*
|
||||
node_modules
|
||||
**/.circleci
|
||||
**/.editorconfig
|
||||
**/.dockerignore
|
||||
**/.git
|
||||
**/.DS_Store
|
||||
**/.vscode
|
||||
**/node_modules
|
||||
.env
|
||||
.env.*
|
||||
!.env.static
|
||||
|
||||
# Specific patterns to ignore
|
||||
data-node
|
||||
meili_data*
|
||||
librechat*
|
||||
Dockerfile*
|
||||
docs
|
||||
|
||||
# Ignore all hidden files
|
||||
.*
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
PORT=3080
|
||||
RAG_PORT=8000
|
||||
MEILI_MASTER_KEY=masterkey
|
||||
ENDPOINTS=openAI,anthropic,assistants,custom
|
||||
|
||||
# Security keys (generate random strings)
|
||||
CREDS_KEY=a_random_32_character_string_for_creds
|
||||
CREDS_IV=a_random_16_character_string_for_creds
|
||||
JWT_SECRET=a_random_secret_for_jwt_tokens
|
||||
JWT_REFRESH_SECRET=another_random_secret_for_refresh_tokens
|
||||
@@ -1 +0,0 @@
|
||||
ENDPOINTS=openAI,anthropic,assistants,custom
|
||||
-660
@@ -1,660 +0,0 @@
|
||||
#=====================================================================#
|
||||
# Hanzo Chat Configuration #
|
||||
#=====================================================================#
|
||||
# Please refer to the reference documentation for assistance #
|
||||
# with configuring your Hanzo Chat environment. #
|
||||
# #
|
||||
# https://docs.hanzo.ai/chat/configuration/dotenv #
|
||||
#=====================================================================#
|
||||
|
||||
#==================================================#
|
||||
# Server Configuration #
|
||||
#==================================================#
|
||||
|
||||
HOST=localhost
|
||||
PORT=3080
|
||||
|
||||
MONGO_URI=mongodb://127.0.0.1:27017/HanzoChat
|
||||
|
||||
DOMAIN_CLIENT=http://localhost:3080
|
||||
DOMAIN_SERVER=http://localhost:3080
|
||||
|
||||
NO_INDEX=true
|
||||
# Use the address that is at most n number of hops away from the Express application.
|
||||
# req.socket.remoteAddress is the first hop, and the rest are looked for in the X-Forwarded-For header from right to left.
|
||||
# A value of 0 means that the first untrusted address would be req.socket.remoteAddress, i.e. there is no reverse proxy.
|
||||
# Defaulted to 1.
|
||||
TRUST_PROXY=1
|
||||
|
||||
#===============#
|
||||
# JSON Logging #
|
||||
#===============#
|
||||
|
||||
# Use when process console logs in cloud deployment like GCP/AWS
|
||||
CONSOLE_JSON=false
|
||||
|
||||
#===============#
|
||||
# Debug Logging #
|
||||
#===============#
|
||||
|
||||
DEBUG_LOGGING=true
|
||||
DEBUG_CONSOLE=false
|
||||
|
||||
#=============#
|
||||
# Permissions #
|
||||
#=============#
|
||||
|
||||
# UID=1000
|
||||
# GID=1000
|
||||
|
||||
#===============#
|
||||
# Configuration #
|
||||
#===============#
|
||||
# Use an absolute path, a relative path, or a URL
|
||||
|
||||
# CONFIG_PATH="/alternative/path/to/chat.yaml"
|
||||
|
||||
#===================================================#
|
||||
# Endpoints #
|
||||
#===================================================#
|
||||
|
||||
# ENDPOINTS=openAI,assistants,azureOpenAI,google,anthropic
|
||||
|
||||
PROXY=
|
||||
|
||||
#===================================#
|
||||
# Known Endpoints - chat.yaml #
|
||||
#===================================#
|
||||
# https://docs.hanzo.ai/chat/configuration/ai_endpoints
|
||||
|
||||
# ANYSCALE_API_KEY=
|
||||
# APIPIE_API_KEY=
|
||||
# COHERE_API_KEY=
|
||||
# DEEPSEEK_API_KEY=
|
||||
# DATABRICKS_API_KEY=
|
||||
# FIREWORKS_API_KEY=
|
||||
# GROQ_API_KEY=
|
||||
# HUGGINGFACE_TOKEN=
|
||||
# MISTRAL_API_KEY=
|
||||
# OPENROUTER_KEY=
|
||||
# PERPLEXITY_API_KEY=
|
||||
# SHUTTLEAI_API_KEY=
|
||||
# TOGETHERAI_API_KEY=
|
||||
# UNIFY_API_KEY=
|
||||
# XAI_API_KEY=
|
||||
|
||||
#============#
|
||||
# Anthropic #
|
||||
#============#
|
||||
|
||||
ANTHROPIC_API_KEY=user_provided
|
||||
# ANTHROPIC_MODELS=claude-opus-4-20250514,claude-sonnet-4-20250514,claude-3-7-sonnet-20250219,claude-3-5-sonnet-20241022,claude-3-5-haiku-20241022,claude-3-opus-20240229,claude-3-sonnet-20240229,claude-3-haiku-20240307
|
||||
# ANTHROPIC_REVERSE_PROXY=
|
||||
|
||||
#============#
|
||||
# Azure #
|
||||
#============#
|
||||
|
||||
# Note: these variables are DEPRECATED
|
||||
# Use the `chat.yaml` configuration for `azureOpenAI` instead
|
||||
# You may also continue to use them if you opt out of using the `chat.yaml` configuration
|
||||
|
||||
# AZURE_OPENAI_DEFAULT_MODEL=gpt-3.5-turbo # Deprecated
|
||||
# AZURE_OPENAI_MODELS=gpt-3.5-turbo,gpt-4 # Deprecated
|
||||
# AZURE_USE_MODEL_AS_DEPLOYMENT_NAME=TRUE # Deprecated
|
||||
# AZURE_API_KEY= # Deprecated
|
||||
# AZURE_OPENAI_API_INSTANCE_NAME= # Deprecated
|
||||
# AZURE_OPENAI_API_DEPLOYMENT_NAME= # Deprecated
|
||||
# AZURE_OPENAI_API_VERSION= # Deprecated
|
||||
# AZURE_OPENAI_API_COMPLETIONS_DEPLOYMENT_NAME= # Deprecated
|
||||
# AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME= # Deprecated
|
||||
# PLUGINS_USE_AZURE="true" # Deprecated
|
||||
|
||||
#=================#
|
||||
# AWS Bedrock #
|
||||
#=================#
|
||||
|
||||
# BEDROCK_AWS_DEFAULT_REGION=us-east-1 # A default region must be provided
|
||||
# BEDROCK_AWS_ACCESS_KEY_ID=someAccessKey
|
||||
# BEDROCK_AWS_SECRET_ACCESS_KEY=someSecretAccessKey
|
||||
# BEDROCK_AWS_SESSION_TOKEN=someSessionToken
|
||||
|
||||
# Note: This example list is not meant to be exhaustive. If omitted, all known, supported model IDs will be included for you.
|
||||
# BEDROCK_AWS_MODELS=anthropic.claude-3-5-sonnet-20240620-v1:0,meta.llama3-1-8b-instruct-v1:0
|
||||
|
||||
# See all Bedrock model IDs here: https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html#model-ids-arns
|
||||
|
||||
# Notes on specific models:
|
||||
# The following models are not support due to not supporting streaming:
|
||||
# ai21.j2-mid-v1
|
||||
|
||||
# The following models are not support due to not supporting conversation history:
|
||||
# ai21.j2-ultra-v1, cohere.command-text-v14, cohere.command-light-text-v14
|
||||
|
||||
#============#
|
||||
# Google #
|
||||
#============#
|
||||
|
||||
GOOGLE_KEY=user_provided
|
||||
|
||||
# GOOGLE_REVERSE_PROXY=
|
||||
# Some reverse proxies do not support the X-goog-api-key header, uncomment to pass the API key in Authorization header instead.
|
||||
# GOOGLE_AUTH_HEADER=true
|
||||
|
||||
# Gemini API (AI Studio)
|
||||
# GOOGLE_MODELS=gemini-2.5-pro,gemini-2.5-flash,gemini-2.5-flash-lite-preview-06-17,gemini-2.0-flash,gemini-2.0-flash-lite
|
||||
|
||||
# Vertex AI
|
||||
# GOOGLE_MODELS=gemini-2.5-pro,gemini-2.5-flash,gemini-2.5-flash-lite-preview-06-17,gemini-2.0-flash-001,gemini-2.0-flash-lite-001
|
||||
|
||||
# GOOGLE_TITLE_MODEL=gemini-2.0-flash-lite-001
|
||||
|
||||
# GOOGLE_LOC=us-central1
|
||||
|
||||
# Google Safety Settings
|
||||
# NOTE: These settings apply to both Vertex AI and Gemini API (AI Studio)
|
||||
#
|
||||
# For Vertex AI:
|
||||
# To use the BLOCK_NONE setting, you need either:
|
||||
# (a) Access through an allowlist via your Google account team, or
|
||||
# (b) Switch to monthly invoiced billing: https://cloud.google.com/billing/docs/how-to/invoiced-billing
|
||||
#
|
||||
# For Gemini API (AI Studio):
|
||||
# BLOCK_NONE is available by default, no special account requirements.
|
||||
#
|
||||
# Available options: BLOCK_NONE, BLOCK_ONLY_HIGH, BLOCK_MEDIUM_AND_ABOVE, BLOCK_LOW_AND_ABOVE
|
||||
#
|
||||
# GOOGLE_SAFETY_SEXUALLY_EXPLICIT=BLOCK_ONLY_HIGH
|
||||
# GOOGLE_SAFETY_HATE_SPEECH=BLOCK_ONLY_HIGH
|
||||
# GOOGLE_SAFETY_HARASSMENT=BLOCK_ONLY_HIGH
|
||||
# GOOGLE_SAFETY_DANGEROUS_CONTENT=BLOCK_ONLY_HIGH
|
||||
# GOOGLE_SAFETY_CIVIC_INTEGRITY=BLOCK_ONLY_HIGH
|
||||
|
||||
#============#
|
||||
# OpenAI #
|
||||
#============#
|
||||
|
||||
OPENAI_API_KEY=user_provided
|
||||
# OPENAI_MODELS=o1,o1-mini,o1-preview,gpt-4o,gpt-4.5-preview,chatgpt-4o-latest,gpt-4o-mini,gpt-3.5-turbo-0125,gpt-3.5-turbo-0301,gpt-3.5-turbo,gpt-4,gpt-4-0613,gpt-4-vision-preview,gpt-3.5-turbo-0613,gpt-3.5-turbo-16k-0613,gpt-4-0125-preview,gpt-4-turbo-preview,gpt-4-1106-preview,gpt-3.5-turbo-1106,gpt-3.5-turbo-instruct,gpt-3.5-turbo-instruct-0914,gpt-3.5-turbo-16k
|
||||
|
||||
DEBUG_OPENAI=false
|
||||
|
||||
# TITLE_CONVO=false
|
||||
# OPENAI_TITLE_MODEL=gpt-4o-mini
|
||||
|
||||
# OPENAI_SUMMARIZE=true
|
||||
# OPENAI_SUMMARY_MODEL=gpt-4o-mini
|
||||
|
||||
# OPENAI_FORCE_PROMPT=true
|
||||
|
||||
# OPENAI_REVERSE_PROXY=
|
||||
|
||||
# OPENAI_ORGANIZATION=
|
||||
|
||||
#====================#
|
||||
# Assistants API #
|
||||
#====================#
|
||||
|
||||
ASSISTANTS_API_KEY=user_provided
|
||||
# ASSISTANTS_BASE_URL=
|
||||
# ASSISTANTS_MODELS=gpt-4o,gpt-4o-mini,gpt-3.5-turbo-0125,gpt-3.5-turbo-16k-0613,gpt-3.5-turbo-16k,gpt-3.5-turbo,gpt-4,gpt-4-0314,gpt-4-32k-0314,gpt-4-0613,gpt-3.5-turbo-0613,gpt-3.5-turbo-1106,gpt-4-0125-preview,gpt-4-turbo-preview,gpt-4-1106-preview
|
||||
|
||||
#==========================#
|
||||
# Azure Assistants API #
|
||||
#==========================#
|
||||
|
||||
# Note: You should map your credentials with custom variables according to your Azure OpenAI Configuration
|
||||
# The models for Azure Assistants are also determined by your Azure OpenAI configuration.
|
||||
|
||||
# More info, including how to enable use of Assistants with Azure here:
|
||||
# https://docs.hanzo.ai/chat/configuration/ai_endpoints/azure#using-assistants-with-azure
|
||||
|
||||
#============#
|
||||
# Plugins #
|
||||
#============#
|
||||
|
||||
# PLUGIN_MODELS=gpt-4o,gpt-4o-mini,gpt-4,gpt-4-turbo-preview,gpt-4-0125-preview,gpt-4-1106-preview,gpt-4-0613,gpt-3.5-turbo,gpt-3.5-turbo-0125,gpt-3.5-turbo-1106,gpt-3.5-turbo-0613
|
||||
|
||||
DEBUG_PLUGINS=true
|
||||
|
||||
CREDS_KEY=f34be427ebb29de8d88c107a71546019685ed8b241d8f2ed00c3df97ad2566f0
|
||||
CREDS_IV=e2341419ec3dd3d19b13a1a87fafcbfb
|
||||
|
||||
# Azure AI Search
|
||||
#-----------------
|
||||
AZURE_AI_SEARCH_SERVICE_ENDPOINT=
|
||||
AZURE_AI_SEARCH_INDEX_NAME=
|
||||
AZURE_AI_SEARCH_API_KEY=
|
||||
|
||||
AZURE_AI_SEARCH_API_VERSION=
|
||||
AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE=
|
||||
AZURE_AI_SEARCH_SEARCH_OPTION_TOP=
|
||||
AZURE_AI_SEARCH_SEARCH_OPTION_SELECT=
|
||||
|
||||
# OpenAI Image Tools Customization
|
||||
#----------------
|
||||
# IMAGE_GEN_OAI_DESCRIPTION_WITH_FILES=Custom description for image generation tool when files are present
|
||||
# IMAGE_GEN_OAI_DESCRIPTION_NO_FILES=Custom description for image generation tool when no files are present
|
||||
# IMAGE_EDIT_OAI_DESCRIPTION=Custom description for image editing tool
|
||||
# IMAGE_GEN_OAI_PROMPT_DESCRIPTION=Custom prompt description for image generation tool
|
||||
# IMAGE_EDIT_OAI_PROMPT_DESCRIPTION=Custom prompt description for image editing tool
|
||||
|
||||
# DALL·E
|
||||
#----------------
|
||||
# DALLE_API_KEY=
|
||||
# DALLE3_API_KEY=
|
||||
# DALLE2_API_KEY=
|
||||
# DALLE3_SYSTEM_PROMPT=
|
||||
# DALLE2_SYSTEM_PROMPT=
|
||||
# DALLE_REVERSE_PROXY=
|
||||
# DALLE3_BASEURL=
|
||||
# DALLE2_BASEURL=
|
||||
|
||||
# DALL·E (via Azure OpenAI)
|
||||
# Note: requires some of the variables above to be set
|
||||
#----------------
|
||||
# DALLE3_AZURE_API_VERSION=
|
||||
# DALLE2_AZURE_API_VERSION=
|
||||
|
||||
# Flux
|
||||
#-----------------
|
||||
FLUX_API_BASE_URL=https://api.us1.bfl.ai
|
||||
# FLUX_API_BASE_URL = 'https://api.bfl.ml';
|
||||
|
||||
# Get your API key at https://api.us1.bfl.ai/auth/profile
|
||||
# FLUX_API_KEY=
|
||||
|
||||
# Google
|
||||
#-----------------
|
||||
GOOGLE_SEARCH_API_KEY=
|
||||
GOOGLE_CSE_ID=
|
||||
|
||||
# YOUTUBE
|
||||
#-----------------
|
||||
YOUTUBE_API_KEY=
|
||||
|
||||
# SerpAPI
|
||||
#-----------------
|
||||
SERPAPI_API_KEY=
|
||||
|
||||
# Stable Diffusion
|
||||
#-----------------
|
||||
SD_WEBUI_URL=http://host.docker.internal:7860
|
||||
|
||||
# Tavily
|
||||
#-----------------
|
||||
TAVILY_API_KEY=
|
||||
|
||||
# Traversaal
|
||||
#-----------------
|
||||
TRAVERSAAL_API_KEY=
|
||||
|
||||
# WolframAlpha
|
||||
#-----------------
|
||||
WOLFRAM_APP_ID=
|
||||
|
||||
# Zapier
|
||||
#-----------------
|
||||
ZAPIER_NLA_API_KEY=
|
||||
|
||||
#==================================================#
|
||||
# Search #
|
||||
#==================================================#
|
||||
|
||||
SEARCH=true
|
||||
MEILI_NO_ANALYTICS=true
|
||||
MEILI_HOST=http://localhost:7700
|
||||
MEILI_MASTER_KEY=DrhYf7zENyR6AlUCKmnz0eYASOQdl6zxH7s7MKFSfFCt
|
||||
|
||||
# Optional: Disable indexing, useful in a multi-node setup
|
||||
# where only one instance should perform an index sync.
|
||||
# MEILI_NO_SYNC=true
|
||||
|
||||
#==================================================#
|
||||
# Speech to Text & Text to Speech #
|
||||
#==================================================#
|
||||
|
||||
STT_API_KEY=
|
||||
TTS_API_KEY=
|
||||
|
||||
#==================================================#
|
||||
# RAG #
|
||||
#==================================================#
|
||||
# More info: https://docs.hanzo.ai/chat/configuration/rag_api
|
||||
|
||||
# RAG_OPENAI_BASEURL=
|
||||
# RAG_OPENAI_API_KEY=
|
||||
# RAG_USE_FULL_CONTEXT=
|
||||
# EMBEDDINGS_PROVIDER=openai
|
||||
# EMBEDDINGS_MODEL=text-embedding-3-small
|
||||
|
||||
#===================================================#
|
||||
# User System #
|
||||
#===================================================#
|
||||
|
||||
#========================#
|
||||
# Moderation #
|
||||
#========================#
|
||||
|
||||
OPENAI_MODERATION=false
|
||||
OPENAI_MODERATION_API_KEY=
|
||||
# OPENAI_MODERATION_REVERSE_PROXY=
|
||||
|
||||
BAN_VIOLATIONS=true
|
||||
BAN_DURATION=1000 * 60 * 60 * 2
|
||||
BAN_INTERVAL=20
|
||||
|
||||
LOGIN_VIOLATION_SCORE=1
|
||||
REGISTRATION_VIOLATION_SCORE=1
|
||||
CONCURRENT_VIOLATION_SCORE=1
|
||||
MESSAGE_VIOLATION_SCORE=1
|
||||
NON_BROWSER_VIOLATION_SCORE=20
|
||||
|
||||
LOGIN_MAX=7
|
||||
LOGIN_WINDOW=5
|
||||
REGISTER_MAX=5
|
||||
REGISTER_WINDOW=60
|
||||
|
||||
LIMIT_CONCURRENT_MESSAGES=true
|
||||
CONCURRENT_MESSAGE_MAX=2
|
||||
|
||||
LIMIT_MESSAGE_IP=true
|
||||
MESSAGE_IP_MAX=40
|
||||
MESSAGE_IP_WINDOW=1
|
||||
|
||||
LIMIT_MESSAGE_USER=false
|
||||
MESSAGE_USER_MAX=40
|
||||
MESSAGE_USER_WINDOW=1
|
||||
|
||||
ILLEGAL_MODEL_REQ_SCORE=5
|
||||
|
||||
#========================#
|
||||
# Balance #
|
||||
#========================#
|
||||
|
||||
# CHECK_BALANCE=false
|
||||
# START_BALANCE=20000 # note: the number of tokens that will be credited after registration.
|
||||
|
||||
#========================#
|
||||
# Registration and Login #
|
||||
#========================#
|
||||
|
||||
ALLOW_EMAIL_LOGIN=true
|
||||
ALLOW_REGISTRATION=true
|
||||
ALLOW_SOCIAL_LOGIN=false
|
||||
ALLOW_SOCIAL_REGISTRATION=false
|
||||
ALLOW_PASSWORD_RESET=false
|
||||
# ALLOW_ACCOUNT_DELETION=true # note: enabled by default if omitted/commented out
|
||||
ALLOW_UNVERIFIED_EMAIL_LOGIN=true
|
||||
|
||||
SESSION_EXPIRY=1000 * 60 * 15
|
||||
REFRESH_TOKEN_EXPIRY=(1000 * 60 * 60 * 24) * 7
|
||||
|
||||
JWT_SECRET=16f8c0ef4a5d391b26034086c628469d3f9f497f08163ab9b40137092f2909ef
|
||||
JWT_REFRESH_SECRET=eaa5191f2914e30b9387fd84e254e4ba6fc51b4654968a9b0803b456a54b8418
|
||||
|
||||
# Discord
|
||||
DISCORD_CLIENT_ID=
|
||||
DISCORD_CLIENT_SECRET=
|
||||
DISCORD_CALLBACK_URL=/oauth/discord/callback
|
||||
|
||||
# Facebook
|
||||
FACEBOOK_CLIENT_ID=
|
||||
FACEBOOK_CLIENT_SECRET=
|
||||
FACEBOOK_CALLBACK_URL=/oauth/facebook/callback
|
||||
|
||||
# GitHub
|
||||
GITHUB_CLIENT_ID=
|
||||
GITHUB_CLIENT_SECRET=
|
||||
GITHUB_CALLBACK_URL=/oauth/github/callback
|
||||
# GitHub Enterprise
|
||||
# GITHUB_ENTERPRISE_BASE_URL=
|
||||
# GITHUB_ENTERPRISE_USER_AGENT=
|
||||
|
||||
# Google
|
||||
GOOGLE_CLIENT_ID=
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
GOOGLE_CALLBACK_URL=/oauth/google/callback
|
||||
|
||||
# Apple
|
||||
APPLE_CLIENT_ID=
|
||||
APPLE_TEAM_ID=
|
||||
APPLE_KEY_ID=
|
||||
APPLE_PRIVATE_KEY_PATH=
|
||||
APPLE_CALLBACK_URL=/oauth/apple/callback
|
||||
|
||||
# OpenID
|
||||
OPENID_CLIENT_ID=
|
||||
OPENID_CLIENT_SECRET=
|
||||
OPENID_ISSUER=
|
||||
OPENID_SESSION_SECRET=
|
||||
OPENID_SCOPE="openid profile email"
|
||||
OPENID_CALLBACK_URL=/oauth/openid/callback
|
||||
OPENID_REQUIRED_ROLE=
|
||||
OPENID_REQUIRED_ROLE_TOKEN_KIND=
|
||||
OPENID_REQUIRED_ROLE_PARAMETER_PATH=
|
||||
# Set to determine which user info property returned from OpenID Provider to store as the User's username
|
||||
OPENID_USERNAME_CLAIM=
|
||||
# Set to determine which user info property returned from OpenID Provider to store as the User's name
|
||||
OPENID_NAME_CLAIM=
|
||||
|
||||
OPENID_BUTTON_LABEL=
|
||||
OPENID_IMAGE_URL=
|
||||
# Set to true to automatically redirect to the OpenID provider when a user visits the login page
|
||||
# This will bypass the login form completely for users, only use this if OpenID is your only authentication method
|
||||
OPENID_AUTO_REDIRECT=false
|
||||
# Set to true to use PKCE (Proof Key for Code Exchange) for OpenID authentication
|
||||
OPENID_USE_PKCE=false
|
||||
#Set to true to reuse openid tokens for authentication management instead of using the mongodb session and the custom refresh token.
|
||||
OPENID_REUSE_TOKENS=
|
||||
#By default, signing key verification results are cached in order to prevent excessive HTTP requests to the JWKS endpoint.
|
||||
#If a signing key matching the kid is found, this will be cached and the next time this kid is requested the signing key will be served from the cache.
|
||||
#Default is true.
|
||||
OPENID_JWKS_URL_CACHE_ENABLED=
|
||||
OPENID_JWKS_URL_CACHE_TIME= # 600000 ms eq to 10 minutes leave empty to disable caching
|
||||
#Set to true to trigger token exchange flow to acquire access token for the userinfo endpoint.
|
||||
OPENID_ON_BEHALF_FLOW_FOR_USERINFRO_REQUIRED=
|
||||
OPENID_ON_BEHALF_FLOW_USERINFRO_SCOPE = "user.read" # example for Scope Needed for Microsoft Graph API
|
||||
# Set to true to use the OpenID Connect end session endpoint for logout
|
||||
OPENID_USE_END_SESSION_ENDPOINT=
|
||||
|
||||
|
||||
# SAML
|
||||
# Note: If OpenID is enabled, SAML authentication will be automatically disabled.
|
||||
SAML_ENTRY_POINT=
|
||||
SAML_ISSUER=
|
||||
SAML_CERT=
|
||||
SAML_CALLBACK_URL=/oauth/saml/callback
|
||||
SAML_SESSION_SECRET=
|
||||
|
||||
# Attribute mappings (optional)
|
||||
SAML_EMAIL_CLAIM=
|
||||
SAML_USERNAME_CLAIM=
|
||||
SAML_GIVEN_NAME_CLAIM=
|
||||
SAML_FAMILY_NAME_CLAIM=
|
||||
SAML_PICTURE_CLAIM=
|
||||
SAML_NAME_CLAIM=
|
||||
|
||||
# Logint buttion settings (optional)
|
||||
SAML_BUTTON_LABEL=
|
||||
SAML_IMAGE_URL=
|
||||
|
||||
# Whether the SAML Response should be signed.
|
||||
# - If "true", the entire `SAML Response` will be signed.
|
||||
# - If "false" or unset, only the `SAML Assertion` will be signed (default behavior).
|
||||
# SAML_USE_AUTHN_RESPONSE_SIGNED=
|
||||
|
||||
|
||||
# LDAP
|
||||
LDAP_URL=
|
||||
LDAP_BIND_DN=
|
||||
LDAP_BIND_CREDENTIALS=
|
||||
LDAP_USER_SEARCH_BASE=
|
||||
#LDAP_SEARCH_FILTER="mail="
|
||||
LDAP_CA_CERT_PATH=
|
||||
# LDAP_TLS_REJECT_UNAUTHORIZED=
|
||||
# LDAP_STARTTLS=
|
||||
# LDAP_LOGIN_USES_USERNAME=true
|
||||
# LDAP_ID=
|
||||
# LDAP_USERNAME=
|
||||
# LDAP_EMAIL=
|
||||
# LDAP_FULL_NAME=
|
||||
|
||||
#========================#
|
||||
# Email Password Reset #
|
||||
#========================#
|
||||
|
||||
EMAIL_SERVICE=
|
||||
EMAIL_HOST=
|
||||
EMAIL_PORT=25
|
||||
EMAIL_ENCRYPTION=
|
||||
EMAIL_ENCRYPTION_HOSTNAME=
|
||||
EMAIL_ALLOW_SELFSIGNED=
|
||||
EMAIL_USERNAME=
|
||||
EMAIL_PASSWORD=
|
||||
EMAIL_FROM_NAME=
|
||||
EMAIL_FROM=noreply@hanzo.ai
|
||||
|
||||
#========================#
|
||||
# Mailgun API #
|
||||
#========================#
|
||||
|
||||
# MAILGUN_API_KEY=your-mailgun-api-key
|
||||
# MAILGUN_DOMAIN=mg.yourdomain.com
|
||||
# EMAIL_FROM=noreply@yourdomain.com
|
||||
# EMAIL_FROM_NAME="Hanzo Chat"
|
||||
|
||||
# # Optional: For EU region
|
||||
# MAILGUN_HOST=https://api.eu.mailgun.net
|
||||
|
||||
#========================#
|
||||
# Firebase CDN #
|
||||
#========================#
|
||||
|
||||
FIREBASE_API_KEY=
|
||||
FIREBASE_AUTH_DOMAIN=
|
||||
FIREBASE_PROJECT_ID=
|
||||
FIREBASE_STORAGE_BUCKET=
|
||||
FIREBASE_MESSAGING_SENDER_ID=
|
||||
FIREBASE_APP_ID=
|
||||
|
||||
#========================#
|
||||
# S3 AWS Bucket #
|
||||
#========================#
|
||||
|
||||
AWS_ENDPOINT_URL=
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_REGION=
|
||||
AWS_BUCKET_NAME=
|
||||
|
||||
#========================#
|
||||
# Azure Blob Storage #
|
||||
#========================#
|
||||
|
||||
AZURE_STORAGE_CONNECTION_STRING=
|
||||
AZURE_STORAGE_PUBLIC_ACCESS=false
|
||||
AZURE_CONTAINER_NAME=files
|
||||
|
||||
#========================#
|
||||
# Shared Links #
|
||||
#========================#
|
||||
|
||||
ALLOW_SHARED_LINKS=true
|
||||
ALLOW_SHARED_LINKS_PUBLIC=true
|
||||
|
||||
#==============================#
|
||||
# Static File Cache Control #
|
||||
#==============================#
|
||||
|
||||
# Leave commented out to use defaults: 1 day (86400 seconds) for s-maxage and 2 days (172800 seconds) for max-age
|
||||
# NODE_ENV must be set to production for these to take effect
|
||||
# STATIC_CACHE_MAX_AGE=172800
|
||||
# STATIC_CACHE_S_MAX_AGE=86400
|
||||
|
||||
# If you have another service in front of your Hanzo Chat doing compression, disable express based compression here
|
||||
# DISABLE_COMPRESSION=true
|
||||
|
||||
#===================================================#
|
||||
# UI #
|
||||
#===================================================#
|
||||
|
||||
APP_TITLE=Hanzo Chat
|
||||
# CUSTOM_FOOTER="My custom footer"
|
||||
HELP_AND_FAQ_URL=https://hanzo.ai
|
||||
|
||||
# SHOW_BIRTHDAY_ICON=true
|
||||
|
||||
# Google tag manager id
|
||||
#ANALYTICS_GTM_ID=user provided google tag manager id
|
||||
|
||||
#===============#
|
||||
# REDIS Options #
|
||||
#===============#
|
||||
|
||||
# REDIS_URI=10.10.10.10:6379
|
||||
# USE_REDIS=true
|
||||
|
||||
# USE_REDIS_CLUSTER=true
|
||||
# REDIS_CA=/path/to/ca.crt
|
||||
|
||||
#==================================================#
|
||||
# Others #
|
||||
#==================================================#
|
||||
# You should leave the following commented out #
|
||||
|
||||
# NODE_ENV=
|
||||
|
||||
# E2E_USER_EMAIL=
|
||||
# E2E_USER_PASSWORD=
|
||||
|
||||
#=====================================================#
|
||||
# Cache Headers #
|
||||
#=====================================================#
|
||||
# Headers that control caching of the index.html #
|
||||
# Default configuration prevents caching to ensure #
|
||||
# users always get the latest version. Customize #
|
||||
# only if you understand caching implications. #
|
||||
|
||||
# INDEX_CACHE_CONTROL=no-cache, no-store, must-revalidate
|
||||
# INDEX_PRAGMA=no-cache
|
||||
# INDEX_EXPIRES=0
|
||||
|
||||
# no-cache: Forces validation with server before using cached version
|
||||
# no-store: Prevents storing the response entirely
|
||||
# must-revalidate: Prevents using stale content when offline
|
||||
|
||||
#=====================================================#
|
||||
# OpenWeather #
|
||||
#=====================================================#
|
||||
OPENWEATHER_API_KEY=
|
||||
|
||||
#====================================#
|
||||
# Hanzo Chat Code Interpreter API #
|
||||
#====================================#
|
||||
|
||||
# https://code.hanzo.ai
|
||||
# HANZO_CODE_API_KEY=your-key
|
||||
|
||||
#======================#
|
||||
# Web Search #
|
||||
#======================#
|
||||
|
||||
# Note: All of the following variable names can be customized.
|
||||
# Omit values to allow user to provide them.
|
||||
|
||||
# For more information on configuration values, see:
|
||||
# https://docs.hanzo.ai/chat/features/web_search
|
||||
|
||||
# Search Provider (Required)
|
||||
# SERPER_API_KEY=your_serper_api_key
|
||||
|
||||
# Scraper (Required)
|
||||
# FIRECRAWL_API_KEY=your_firecrawl_api_key
|
||||
# Optional: Custom Firecrawl API URL
|
||||
# FIRECRAWL_API_URL=your_firecrawl_api_url
|
||||
|
||||
# Reranker (Required)
|
||||
# JINA_API_KEY=your_jina_api_key
|
||||
# or
|
||||
# COHERE_API_KEY=your_cohere_api_key
|
||||
@@ -1,95 +0,0 @@
|
||||
# ==================================
|
||||
# BRAND MODE EXAMPLE CONFIGURATION
|
||||
# ==================================
|
||||
# This example shows how to configure Hanzo Chat in brand mode
|
||||
# for white-label deployments
|
||||
|
||||
# Copy the main .env.example first, then override with these settings:
|
||||
# cp .env.example .env
|
||||
# Then add these brand mode settings
|
||||
|
||||
# ==================================
|
||||
# Enable Brand Mode
|
||||
# ==================================
|
||||
BRAND_MODE=true
|
||||
|
||||
# ==================================
|
||||
# Brand Configuration
|
||||
# ==================================
|
||||
BRAND_NAME=ACME AI
|
||||
BRAND_MODEL_DEFAULT=ACME Assistant
|
||||
BRAND_MODEL_PRO=ACME Assistant Pro
|
||||
|
||||
# Optional branding
|
||||
BRAND_COLOR=#FF6B6B
|
||||
BRAND_LOGO_URL=/assets/acme-logo.svg
|
||||
APP_TITLE=ACME AI Chat
|
||||
|
||||
# ==================================
|
||||
# Backend Configuration
|
||||
# ==================================
|
||||
# Configure which actual AI provider to use behind the scenes
|
||||
|
||||
# Example 1: Using Anthropic Claude
|
||||
BRAND_BACKEND_ENDPOINT=anthropic
|
||||
BRAND_BACKEND_API_KEY=sk-ant-api03-YOUR-KEY-HERE
|
||||
BRAND_BACKEND_MODEL_DEFAULT=claude-3-5-sonnet-20241022
|
||||
BRAND_BACKEND_MODEL_PRO=claude-3-5-opus-latest
|
||||
|
||||
# Example 2: Using OpenAI
|
||||
# BRAND_BACKEND_ENDPOINT=openAI
|
||||
# BRAND_BACKEND_API_KEY=sk-YOUR-OPENAI-KEY
|
||||
# BRAND_BACKEND_MODEL_DEFAULT=gpt-4o-mini
|
||||
# BRAND_BACKEND_MODEL_PRO=gpt-4o
|
||||
|
||||
# Example 3: Using Hanzo AI API (recommended)
|
||||
# BRAND_BACKEND_ENDPOINT=hanzoai
|
||||
# BRAND_BACKEND_API_KEY=hsk-YOUR-HANZO-KEY
|
||||
# BRAND_BACKEND_BASE_URL=https://api.hanzo.ai/v1
|
||||
# BRAND_BACKEND_MODEL_DEFAULT=hanzo-zen-1
|
||||
# BRAND_BACKEND_MODEL_PRO=hanzo-zen-1-pro
|
||||
|
||||
# ==================================
|
||||
# Other Required Settings
|
||||
# ==================================
|
||||
# Don't forget to set these from the main .env.example:
|
||||
|
||||
# Server
|
||||
HOST=0.0.0.0
|
||||
PORT=3080
|
||||
|
||||
# Database
|
||||
MONGO_URI=mongodb://mongodb:27017/BrandChat
|
||||
|
||||
# Security (generate your own!)
|
||||
JWT_SECRET=your-super-secret-jwt-key-change-this
|
||||
JWT_REFRESH_SECRET=your-refresh-secret-key-change-this
|
||||
CREDS_KEY=your-32-character-encryption-key
|
||||
CREDS_IV=16-char-creds-iv
|
||||
|
||||
# Search (optional)
|
||||
MEILI_HOST=http://meilisearch:7700
|
||||
MEILI_MASTER_KEY=your-master-key-minimum-16-bytes
|
||||
|
||||
# ==================================
|
||||
# What Brand Mode Does
|
||||
# ==================================
|
||||
# When BRAND_MODE=true:
|
||||
#
|
||||
# 1. Only shows your branded models (ACME Assistant, ACME Assistant Pro)
|
||||
# 2. Hides all endpoint/provider selection UI
|
||||
# 3. Hides API key configuration options
|
||||
# 4. Routes all requests through your configured backend
|
||||
# 5. Users never see the actual AI provider being used
|
||||
# 6. Perfect for white-label SaaS deployments
|
||||
#
|
||||
# Your users will only see:
|
||||
# - Your brand name (ACME AI)
|
||||
# - Your model names (ACME Assistant)
|
||||
# - Your logo and colors
|
||||
# - No configuration options
|
||||
#
|
||||
# Behind the scenes, you control:
|
||||
# - Which AI provider to use
|
||||
# - Which actual models to use
|
||||
# - All API keys and routing
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
# Docker Registry Configuration
|
||||
# Uncomment one of the following to choose your registry:
|
||||
|
||||
# For Docker Hub (default):
|
||||
DOCKER_REGISTRY=docker.io
|
||||
DOCKER_IMAGE_PREFIX=
|
||||
|
||||
# For GitHub Container Registry:
|
||||
# DOCKER_REGISTRY=ghcr.io
|
||||
# DOCKER_IMAGE_PREFIX=ghcr.io/
|
||||
|
||||
# Organization name
|
||||
DOCKER_ORG=hanzoai
|
||||
+97
-264
@@ -2,37 +2,51 @@
|
||||
# Hanzo Chat Configuration #
|
||||
#=====================================================================#
|
||||
# Please refer to the reference documentation for assistance #
|
||||
# with configuring your Hanzo Chat environment. #
|
||||
# with configuring your Hanzo Chat environment. #
|
||||
# #
|
||||
# https://hanzo.ai/docs/chat/configuration/dotenv #
|
||||
# https://docs.hanzo.ai/chat/configuration/dotenv #
|
||||
#=====================================================================#
|
||||
|
||||
#==================================================#
|
||||
# Server Configuration #
|
||||
#==================================================#
|
||||
|
||||
HOST=localhost
|
||||
HOST=0.0.0.0
|
||||
PORT=3080
|
||||
|
||||
MONGO_URI=mongodb://127.0.0.1:27017/HanzoChat
|
||||
#The maximum number of connections in the connection pool. */
|
||||
MONGO_MAX_POOL_SIZE=
|
||||
#The minimum number of connections in the connection pool. */
|
||||
MONGO_MIN_POOL_SIZE=
|
||||
#The maximum number of connections that may be in the process of being established concurrently by the connection pool. */
|
||||
MONGO_MAX_CONNECTING=
|
||||
#The maximum number of milliseconds that a connection can remain idle in the pool before being removed and closed. */
|
||||
MONGO_MAX_IDLE_TIME_MS=
|
||||
#The maximum time in milliseconds that a thread can wait for a connection to become available. */
|
||||
MONGO_WAIT_QUEUE_TIMEOUT_MS=
|
||||
# Set to false to disable automatic index creation for all models associated with this connection. */
|
||||
MONGO_AUTO_INDEX=
|
||||
# Set to `false` to disable Mongoose automatically calling `createCollection()` on every model created on this connection. */
|
||||
MONGO_AUTO_CREATE=
|
||||
# For Docker: mongodb://mongodb:27017/Hanzo
|
||||
# For Local: mongodb://127.0.0.1:27017/HanzoChat
|
||||
MONGO_URI=mongodb://mongodb:27017/Hanzo
|
||||
|
||||
DOMAIN_CLIENT=http://localhost:3080
|
||||
DOMAIN_SERVER=http://localhost:3080
|
||||
|
||||
#==================================================#
|
||||
# Brand Mode Configuration #
|
||||
#==================================================#
|
||||
# Enable brand mode for white-label deployments
|
||||
BRAND_MODE=true
|
||||
|
||||
# Vendor display name (shown in UI)
|
||||
BRAND_NAME=Hanzo AI
|
||||
|
||||
# Fixed model names for brand mode
|
||||
BRAND_MODEL_DEFAULT=Hanzo Zen-1
|
||||
BRAND_MODEL_PRO=Hanzo Zen-1 Pro
|
||||
|
||||
# Branding
|
||||
APP_TITLE=Hanzo AI Chat
|
||||
BRAND_COLOR=#000000
|
||||
BRAND_LOGO_URL=/assets/hanzo-logo.svg
|
||||
|
||||
# Backend routing for brand mode
|
||||
# The actual API endpoint and key used internally
|
||||
BRAND_BACKEND_ENDPOINT=anthropic # Options: openAI, anthropic, hanzoai, custom
|
||||
BRAND_BACKEND_API_KEY=
|
||||
BRAND_BACKEND_BASE_URL=https://api.hanzo.ai/v1
|
||||
BRAND_BACKEND_MODEL_DEFAULT=claude-3-5-sonnet-20241022 # Actual model used
|
||||
BRAND_BACKEND_MODEL_PRO=claude-3-opus-20240229 # Actual pro model used
|
||||
|
||||
NO_INDEX=true
|
||||
# Use the address that is at most n number of hops away from the Express application.
|
||||
# req.socket.remoteAddress is the first hop, and the rest are looked for in the X-Forwarded-For header from right to left.
|
||||
@@ -40,17 +54,6 @@ NO_INDEX=true
|
||||
# Defaulted to 1.
|
||||
TRUST_PROXY=1
|
||||
|
||||
# Minimum password length for user authentication
|
||||
# Default: 8
|
||||
# Note: When using LDAP authentication, you may want to set this to 1
|
||||
# to bypass local password validation, as LDAP servers handle their own
|
||||
# password policies.
|
||||
# MIN_PASSWORD_LENGTH=8
|
||||
|
||||
# When enabled, the app will continue running after encountering uncaught exceptions
|
||||
# instead of exiting the process. Not recommended for production unless necessary.
|
||||
# CONTINUE_ON_UNCAUGHT_EXCEPTION=false
|
||||
|
||||
#===============#
|
||||
# JSON Logging #
|
||||
#===============#
|
||||
@@ -72,18 +75,6 @@ DEBUG_CONSOLE=false
|
||||
# UID=1000
|
||||
# GID=1000
|
||||
|
||||
#==============#
|
||||
# Node Options #
|
||||
#==============#
|
||||
|
||||
# NOTE: NODE_MAX_OLD_SPACE_SIZE is NOT recognized by Node.js directly.
|
||||
# This variable is used as a build argument for Docker or CI/CD workflows,
|
||||
# and is NOT used by Node.js to set the heap size at runtime.
|
||||
# To configure Node.js memory, use NODE_OPTIONS, e.g.:
|
||||
# NODE_OPTIONS="--max-old-space-size=6144"
|
||||
# See: https://nodejs.org/api/cli.html#--max-old-space-sizesize-in-mib
|
||||
NODE_MAX_OLD_SPACE_SIZE=6144
|
||||
|
||||
#===============#
|
||||
# Configuration #
|
||||
#===============#
|
||||
@@ -91,16 +82,6 @@ NODE_MAX_OLD_SPACE_SIZE=6144
|
||||
|
||||
# CONFIG_PATH="/alternative/path/to/chat.yaml"
|
||||
|
||||
#==================#
|
||||
# Langfuse Tracing #
|
||||
#==================#
|
||||
|
||||
# Get Langfuse API keys for your project from the project settings page: https://cloud.langfuse.com
|
||||
|
||||
# LANGFUSE_PUBLIC_KEY=
|
||||
# LANGFUSE_SECRET_KEY=
|
||||
# LANGFUSE_BASE_URL=
|
||||
|
||||
#===================================================#
|
||||
# Endpoints #
|
||||
#===================================================#
|
||||
@@ -109,18 +90,17 @@ NODE_MAX_OLD_SPACE_SIZE=6144
|
||||
|
||||
PROXY=
|
||||
|
||||
#===============================#
|
||||
# Hanzo API (Zen models) #
|
||||
#===============================#
|
||||
# Publishable key for unauthenticated users (rate-limited).
|
||||
# Authenticated users get their real API key after IAM login (hanzo.id).
|
||||
# Endpoint: https://api.hanzo.ai/v1
|
||||
HANZO_API_KEY=
|
||||
#============#
|
||||
# Hanzo API #
|
||||
#============#
|
||||
# Unified API Gateway for all LLMs
|
||||
# When set, all models will route through Hanzo API unless user provides their own keys
|
||||
# HANZO_API_KEY=hsk_...
|
||||
|
||||
#===================================#
|
||||
# Known Endpoints - chat.yaml #
|
||||
# Known Endpoints - chat.yaml #
|
||||
#===================================#
|
||||
# https://hanzo.ai/docs/chat/configuration/ai_endpoints
|
||||
# https://docs.hanzo.ai/chat/configuration/ai_endpoints
|
||||
|
||||
# ANYSCALE_API_KEY=
|
||||
# APIPIE_API_KEY=
|
||||
@@ -143,13 +123,9 @@ HANZO_API_KEY=
|
||||
#============#
|
||||
|
||||
ANTHROPIC_API_KEY=user_provided
|
||||
# ANTHROPIC_MODELS=claude-sonnet-4-6,claude-opus-4-6,claude-opus-4-20250514,claude-sonnet-4-20250514,claude-3-7-sonnet-20250219,claude-3-5-sonnet-20241022,claude-3-5-haiku-20241022,claude-3-opus-20240229,claude-3-sonnet-20240229,claude-3-haiku-20240307
|
||||
# ANTHROPIC_MODELS=claude-opus-4-20250514,claude-sonnet-4-20250514,claude-3-7-sonnet-20250219,claude-3-5-sonnet-20241022,claude-3-5-haiku-20241022,claude-3-opus-20240229,claude-3-sonnet-20240229,claude-3-haiku-20240307
|
||||
# ANTHROPIC_REVERSE_PROXY=
|
||||
|
||||
# Set to true to use Anthropic models through Google Vertex AI instead of direct API
|
||||
# ANTHROPIC_USE_VERTEX=
|
||||
# ANTHROPIC_VERTEX_REGION=us-east5
|
||||
|
||||
#============#
|
||||
# Azure #
|
||||
#============#
|
||||
@@ -167,6 +143,7 @@ ANTHROPIC_API_KEY=user_provided
|
||||
# AZURE_OPENAI_API_VERSION= # Deprecated
|
||||
# AZURE_OPENAI_API_COMPLETIONS_DEPLOYMENT_NAME= # Deprecated
|
||||
# AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME= # Deprecated
|
||||
# PLUGINS_USE_AZURE="true" # Deprecated
|
||||
|
||||
#=================#
|
||||
# AWS Bedrock #
|
||||
@@ -178,8 +155,7 @@ ANTHROPIC_API_KEY=user_provided
|
||||
# BEDROCK_AWS_SESSION_TOKEN=someSessionToken
|
||||
|
||||
# Note: This example list is not meant to be exhaustive. If omitted, all known, supported model IDs will be included for you.
|
||||
# BEDROCK_AWS_MODELS=anthropic.claude-sonnet-4-6,anthropic.claude-opus-4-6-v1,anthropic.claude-3-5-sonnet-20240620-v1:0,meta.llama3-1-8b-instruct-v1:0
|
||||
# Cross-region inference model IDs: us.anthropic.claude-sonnet-4-6,us.anthropic.claude-opus-4-6-v1,global.anthropic.claude-opus-4-6-v1
|
||||
# BEDROCK_AWS_MODELS=anthropic.claude-3-5-sonnet-20240620-v1:0,meta.llama3-1-8b-instruct-v1:0
|
||||
|
||||
# See all Bedrock model IDs here: https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html#model-ids-arns
|
||||
|
||||
@@ -201,23 +177,15 @@ GOOGLE_KEY=user_provided
|
||||
# GOOGLE_AUTH_HEADER=true
|
||||
|
||||
# Gemini API (AI Studio)
|
||||
# GOOGLE_MODELS=gemini-3.1-pro-preview,gemini-3.1-pro-preview-customtools,gemini-2.5-pro,gemini-2.5-flash,gemini-2.5-flash-lite,gemini-2.0-flash,gemini-2.0-flash-lite
|
||||
# GOOGLE_MODELS=gemini-2.5-pro,gemini-2.5-flash,gemini-2.5-flash-lite-preview-06-17,gemini-2.0-flash,gemini-2.0-flash-lite
|
||||
|
||||
# Vertex AI
|
||||
# GOOGLE_MODELS=gemini-3.1-pro-preview,gemini-3.1-pro-preview-customtools,gemini-2.5-pro,gemini-2.5-flash,gemini-2.5-flash-lite,gemini-2.0-flash-001,gemini-2.0-flash-lite-001
|
||||
# GOOGLE_MODELS=gemini-2.5-pro,gemini-2.5-flash,gemini-2.5-flash-lite-preview-06-17,gemini-2.0-flash-001,gemini-2.0-flash-lite-001
|
||||
|
||||
# GOOGLE_TITLE_MODEL=gemini-2.0-flash-lite-001
|
||||
|
||||
# Google Cloud region for Vertex AI (used by both chat and image generation)
|
||||
# GOOGLE_LOC=us-central1
|
||||
|
||||
# Alternative region env var for Gemini Image Generation
|
||||
# GOOGLE_CLOUD_LOCATION=global
|
||||
|
||||
# Vertex AI Service Account Configuration
|
||||
# Path to your Google Cloud service account JSON file
|
||||
# GOOGLE_SERVICE_KEY_FILE=/path/to/service-account.json
|
||||
|
||||
# Google Safety Settings
|
||||
# NOTE: These settings apply to both Vertex AI and Gemini API (AI Studio)
|
||||
#
|
||||
@@ -237,33 +205,12 @@ GOOGLE_KEY=user_provided
|
||||
# GOOGLE_SAFETY_DANGEROUS_CONTENT=BLOCK_ONLY_HIGH
|
||||
# GOOGLE_SAFETY_CIVIC_INTEGRITY=BLOCK_ONLY_HIGH
|
||||
|
||||
#========================#
|
||||
# Gemini Image Generation #
|
||||
#========================#
|
||||
|
||||
# Gemini Image Generation Tool (for Agents)
|
||||
# Supports multiple authentication methods in priority order:
|
||||
# 1. User-provided API key (via GUI)
|
||||
# 2. GEMINI_API_KEY env var (admin-configured)
|
||||
# 3. GOOGLE_KEY env var (shared with Google chat endpoint)
|
||||
# 4. Vertex AI service account (via GOOGLE_SERVICE_KEY_FILE)
|
||||
|
||||
# Option A: Use dedicated Gemini API key for image generation
|
||||
# GEMINI_API_KEY=your-gemini-api-key
|
||||
|
||||
# Option B: Use Vertex AI (no API key needed, uses service account)
|
||||
# Set this to enable Vertex AI and allow tool without requiring API keys
|
||||
# GEMINI_VERTEX_ENABLED=true
|
||||
|
||||
# Vertex AI model for image generation (defaults to gemini-2.5-flash-image)
|
||||
# GEMINI_IMAGE_MODEL=gemini-2.5-flash-image
|
||||
|
||||
#============#
|
||||
# OpenAI #
|
||||
#============#
|
||||
|
||||
OPENAI_API_KEY=user_provided
|
||||
# OPENAI_MODELS=gpt-5,gpt-5-codex,gpt-5-mini,gpt-5-nano,o3-pro,o3,o4-mini,gpt-4.1,gpt-4.1-mini,gpt-4.1-nano,o3-mini,o1-pro,o1,gpt-4o,gpt-4o-mini
|
||||
# OPENAI_MODELS=o1,o1-mini,o1-preview,gpt-4o,gpt-4.5-preview,chatgpt-4o-latest,gpt-4o-mini,gpt-3.5-turbo-0125,gpt-3.5-turbo-0301,gpt-3.5-turbo,gpt-4,gpt-4-0613,gpt-4-vision-preview,gpt-3.5-turbo-0613,gpt-3.5-turbo-16k-0613,gpt-4-0125-preview,gpt-4-turbo-preview,gpt-4-1106-preview,gpt-3.5-turbo-1106,gpt-3.5-turbo-instruct,gpt-3.5-turbo-instruct-0914,gpt-3.5-turbo-16k
|
||||
|
||||
DEBUG_OPENAI=false
|
||||
|
||||
@@ -295,7 +242,18 @@ ASSISTANTS_API_KEY=user_provided
|
||||
# The models for Azure Assistants are also determined by your Azure OpenAI configuration.
|
||||
|
||||
# More info, including how to enable use of Assistants with Azure here:
|
||||
# https://hanzo.ai/docs/chat/configuration/ai_endpoints/azure#using-assistants-with-azure
|
||||
# https://docs.hanzo.ai/chat/configuration/ai_endpoints/azure#using-assistants-with-azure
|
||||
|
||||
#============#
|
||||
# Plugins #
|
||||
#============#
|
||||
|
||||
# PLUGIN_MODELS=gpt-4o,gpt-4o-mini,gpt-4,gpt-4-turbo-preview,gpt-4-0125-preview,gpt-4-1106-preview,gpt-4-0613,gpt-3.5-turbo,gpt-3.5-turbo-0125,gpt-3.5-turbo-1106,gpt-3.5-turbo-0613
|
||||
|
||||
DEBUG_PLUGINS=true
|
||||
|
||||
CREDS_KEY=f34be427ebb29de8d88c107a71546019685ed8b241d8f2ed00c3df97ad2566f0
|
||||
CREDS_IV=e2341419ec3dd3d19b13a1a87fafcbfb
|
||||
|
||||
# Azure AI Search
|
||||
#-----------------
|
||||
@@ -310,11 +268,6 @@ AZURE_AI_SEARCH_SEARCH_OPTION_SELECT=
|
||||
|
||||
# OpenAI Image Tools Customization
|
||||
#----------------
|
||||
# IMAGE_GEN_OAI_API_KEY= # Create or reuse OpenAI API key for image generation tool
|
||||
# IMAGE_GEN_OAI_BASEURL= # Custom OpenAI base URL for image generation tool
|
||||
# IMAGE_GEN_OAI_AZURE_API_VERSION= # Custom Azure OpenAI deployments
|
||||
# IMAGE_GEN_OAI_MODEL=gpt-image-1 # OpenAI image model (e.g., gpt-image-1, gpt-image-1.5)
|
||||
# IMAGE_GEN_OAI_DESCRIPTION=
|
||||
# IMAGE_GEN_OAI_DESCRIPTION_WITH_FILES=Custom description for image generation tool when files are present
|
||||
# IMAGE_GEN_OAI_DESCRIPTION_NO_FILES=Custom description for image generation tool when no files are present
|
||||
# IMAGE_EDIT_OAI_DESCRIPTION=Custom description for image editing tool
|
||||
@@ -351,6 +304,14 @@ FLUX_API_BASE_URL=https://api.us1.bfl.ai
|
||||
GOOGLE_SEARCH_API_KEY=
|
||||
GOOGLE_CSE_ID=
|
||||
|
||||
# YOUTUBE
|
||||
#-----------------
|
||||
YOUTUBE_API_KEY=
|
||||
|
||||
# SerpAPI
|
||||
#-----------------
|
||||
SERPAPI_API_KEY=
|
||||
|
||||
# Stable Diffusion
|
||||
#-----------------
|
||||
SD_WEBUI_URL=http://host.docker.internal:7860
|
||||
@@ -377,8 +338,10 @@ ZAPIER_NLA_API_KEY=
|
||||
|
||||
SEARCH=true
|
||||
MEILI_NO_ANALYTICS=true
|
||||
MEILI_HOST=http://0.0.0.0:7700
|
||||
MEILI_MASTER_KEY=
|
||||
# For Docker: http://meilisearch:7700
|
||||
# For Local: http://0.0.0.0:7700
|
||||
MEILI_HOST=http://meilisearch:7700
|
||||
MEILI_MASTER_KEY=your-master-key-minimum-16-bytes
|
||||
|
||||
# Optional: Disable indexing, useful in a multi-node setup
|
||||
# where only one instance should perform an index sync.
|
||||
@@ -394,7 +357,7 @@ TTS_API_KEY=
|
||||
#==================================================#
|
||||
# RAG #
|
||||
#==================================================#
|
||||
# More info: https://hanzo.ai/docs/chat/configuration/rag_api
|
||||
# More info: https://docs.hanzo.ai/chat/configuration/rag_api
|
||||
|
||||
# RAG_OPENAI_BASEURL=
|
||||
# RAG_OPENAI_API_KEY=
|
||||
@@ -423,11 +386,6 @@ REGISTRATION_VIOLATION_SCORE=1
|
||||
CONCURRENT_VIOLATION_SCORE=1
|
||||
MESSAGE_VIOLATION_SCORE=1
|
||||
NON_BROWSER_VIOLATION_SCORE=20
|
||||
TTS_VIOLATION_SCORE=0
|
||||
STT_VIOLATION_SCORE=0
|
||||
FORK_VIOLATION_SCORE=0
|
||||
IMPORT_VIOLATION_SCORE=0
|
||||
FILE_UPLOAD_VIOLATION_SCORE=0
|
||||
|
||||
LOGIN_MAX=7
|
||||
LOGIN_WINDOW=5
|
||||
@@ -463,29 +421,14 @@ ALLOW_REGISTRATION=true
|
||||
ALLOW_SOCIAL_LOGIN=false
|
||||
ALLOW_SOCIAL_REGISTRATION=false
|
||||
ALLOW_PASSWORD_RESET=false
|
||||
|
||||
#=====================================================#
|
||||
# Guest Chat (anonymous preview) #
|
||||
#=====================================================#
|
||||
# Off by default. When enabled, unauthenticated visitors get a small free
|
||||
# quota on the free Zen model routed through api.hanzo.ai. After the quota is
|
||||
# spent the UI surfaces the existing OpenID/hanzo.id login. Guests are scoped
|
||||
# server-side to the free model only and rejected from every other route.
|
||||
ALLOW_GUEST_CHAT=false
|
||||
# GUEST_MESSAGE_MAX=3 # free messages per IP before login is required
|
||||
# GUEST_ENDPOINT=Hanzo # custom endpoint name from librechat.yaml (api.hanzo.ai)
|
||||
# GUEST_MODEL=zen3-nano # free Zen model id guests are pinned to
|
||||
# GUEST_TOKEN_EXPIRY=3600000 # guest JWT lifetime in ms (default 1h)
|
||||
# GUEST_TOKEN_MAX=20 # max guest sessions issued per IP per window
|
||||
# GUEST_TOKEN_WINDOW=60 # guest-session issuance window in minutes
|
||||
# ALLOW_ACCOUNT_DELETION=true # note: enabled by default if omitted/commented out
|
||||
ALLOW_UNVERIFIED_EMAIL_LOGIN=true
|
||||
|
||||
SESSION_EXPIRY=1000 * 60 * 15
|
||||
REFRESH_TOKEN_EXPIRY=(1000 * 60 * 60 * 24) * 7
|
||||
|
||||
JWT_SECRET=
|
||||
JWT_REFRESH_SECRET=
|
||||
JWT_SECRET=16f8c0ef4a5d391b26034086c628469d3f9f497f08163ab9b40137092f2909ef
|
||||
JWT_REFRESH_SECRET=eaa5191f2914e30b9387fd84e254e4ba6fc51b4654968a9b0803b456a54b8418
|
||||
|
||||
# Discord
|
||||
DISCORD_CLIENT_ID=
|
||||
@@ -527,15 +470,10 @@ OPENID_CALLBACK_URL=/oauth/openid/callback
|
||||
OPENID_REQUIRED_ROLE=
|
||||
OPENID_REQUIRED_ROLE_TOKEN_KIND=
|
||||
OPENID_REQUIRED_ROLE_PARAMETER_PATH=
|
||||
OPENID_ADMIN_ROLE=
|
||||
OPENID_ADMIN_ROLE_PARAMETER_PATH=
|
||||
OPENID_ADMIN_ROLE_TOKEN_KIND=
|
||||
# Set to determine which user info property returned from OpenID Provider to store as the User's username
|
||||
OPENID_USERNAME_CLAIM=
|
||||
# Set to determine which user info property returned from OpenID Provider to store as the User's name
|
||||
OPENID_NAME_CLAIM=
|
||||
# Optional audience parameter for OpenID authorization requests
|
||||
OPENID_AUDIENCE=
|
||||
|
||||
OPENID_BUTTON_LABEL=
|
||||
OPENID_IMAGE_URL=
|
||||
@@ -552,28 +490,11 @@ OPENID_REUSE_TOKENS=
|
||||
OPENID_JWKS_URL_CACHE_ENABLED=
|
||||
OPENID_JWKS_URL_CACHE_TIME= # 600000 ms eq to 10 minutes leave empty to disable caching
|
||||
#Set to true to trigger token exchange flow to acquire access token for the userinfo endpoint.
|
||||
OPENID_ON_BEHALF_FLOW_FOR_USERINFO_REQUIRED=
|
||||
OPENID_ON_BEHALF_FLOW_USERINFO_SCOPE="user.read" # example for Scope Needed for Microsoft Graph API
|
||||
OPENID_ON_BEHALF_FLOW_FOR_USERINFRO_REQUIRED=
|
||||
OPENID_ON_BEHALF_FLOW_USERINFRO_SCOPE = "user.read" # example for Scope Needed for Microsoft Graph API
|
||||
# Set to true to use the OpenID Connect end session endpoint for logout
|
||||
OPENID_USE_END_SESSION_ENDPOINT=
|
||||
# URL to redirect to after OpenID logout (defaults to ${DOMAIN_CLIENT}/login)
|
||||
OPENID_POST_LOGOUT_REDIRECT_URI=
|
||||
|
||||
#========================#
|
||||
# SharePoint Integration #
|
||||
#========================#
|
||||
# Requires Entra ID (OpenID) authentication to be configured
|
||||
|
||||
# Enable SharePoint file picker in chat and agent panels
|
||||
# ENABLE_SHAREPOINT_FILEPICKER=true
|
||||
|
||||
# SharePoint tenant base URL (e.g., https://yourtenant.sharepoint.com)
|
||||
# SHAREPOINT_BASE_URL=https://yourtenant.sharepoint.com
|
||||
|
||||
# Microsoft Graph API And SharePoint scopes for file picker
|
||||
# SHAREPOINT_PICKER_SHAREPOINT_SCOPE==https://yourtenant.sharepoint.com/AllSites.Read
|
||||
# SHAREPOINT_PICKER_GRAPH_SCOPE=Files.Read.All
|
||||
#========================#
|
||||
|
||||
# SAML
|
||||
# Note: If OpenID is enabled, SAML authentication will be automatically disabled.
|
||||
@@ -601,21 +522,6 @@ SAML_IMAGE_URL=
|
||||
# SAML_USE_AUTHN_RESPONSE_SIGNED=
|
||||
|
||||
|
||||
#===============================================#
|
||||
# Microsoft Graph API / Entra ID Integration #
|
||||
#===============================================#
|
||||
|
||||
# Enable Entra ID people search integration in permissions/sharing system
|
||||
# When enabled, the people picker will search both local database and Entra ID
|
||||
USE_ENTRA_ID_FOR_PEOPLE_SEARCH=false
|
||||
|
||||
# When enabled, entra id groups owners will be considered as members of the group
|
||||
ENTRA_ID_INCLUDE_OWNERS_AS_MEMBERS=false
|
||||
|
||||
# Microsoft Graph API scopes needed for people/group search
|
||||
# Default scopes provide access to user profiles and group memberships
|
||||
OPENID_GRAPH_SCOPES=User.Read,People.Read,GroupMember.Read.All
|
||||
|
||||
# LDAP
|
||||
LDAP_URL=
|
||||
LDAP_BIND_DN=
|
||||
@@ -678,9 +584,6 @@ AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_REGION=
|
||||
AWS_BUCKET_NAME=
|
||||
# Required for path-style S3-compatible providers (MinIO, Hetzner, Backblaze B2, etc.)
|
||||
# that don't support virtual-hosted-style URLs (bucket.endpoint). Not needed for AWS S3.
|
||||
# AWS_FORCE_PATH_STYLE=false
|
||||
|
||||
#========================#
|
||||
# Azure Blob Storage #
|
||||
@@ -709,86 +612,28 @@ ALLOW_SHARED_LINKS_PUBLIC=true
|
||||
# If you have another service in front of your Hanzo Chat doing compression, disable express based compression here
|
||||
# DISABLE_COMPRESSION=true
|
||||
|
||||
# If you have gzipped version of uploaded image images in the same folder, this will enable gzip scan and serving of these images
|
||||
# Note: The images folder will be scanned on startup and a ma kept in memory. Be careful for large number of images.
|
||||
# ENABLE_IMAGE_OUTPUT_GZIP_SCAN=true
|
||||
|
||||
#===================================================#
|
||||
# UI #
|
||||
#===================================================#
|
||||
|
||||
APP_TITLE=Hanzo Chat
|
||||
# APP_TITLE is set in brand mode section above
|
||||
# CUSTOM_FOOTER="My custom footer"
|
||||
HELP_AND_FAQ_URL=https://hanzo.ai/chat
|
||||
HELP_AND_FAQ_URL=https://hanzo.ai
|
||||
|
||||
# SHOW_BIRTHDAY_ICON=true
|
||||
|
||||
# Google tag manager id
|
||||
#ANALYTICS_GTM_ID=user provided google tag manager id
|
||||
|
||||
# limit conversation file imports to a certain number of bytes in size to avoid the container
|
||||
# maxing out memory limitations by unremarking this line and supplying a file size in bytes
|
||||
# such as the below example of 250 mib
|
||||
# CONVERSATION_IMPORT_MAX_FILE_SIZE_BYTES=262144000
|
||||
|
||||
|
||||
#===============#
|
||||
# REDIS Options #
|
||||
#===============#
|
||||
|
||||
# Enable Redis for caching and session storage
|
||||
# REDIS_URI=10.10.10.10:6379
|
||||
# USE_REDIS=true
|
||||
# Enable Redis for resumable LLM streams (defaults to USE_REDIS value if not set)
|
||||
# Set to false to use in-memory storage for streams while keeping Redis for other caches
|
||||
# USE_REDIS_STREAMS=true
|
||||
|
||||
# Single Redis instance
|
||||
# REDIS_URI=redis://127.0.0.1:6379
|
||||
|
||||
# Redis cluster (multiple nodes)
|
||||
# REDIS_URI=redis://127.0.0.1:7001,redis://127.0.0.1:7002,redis://127.0.0.1:7003
|
||||
|
||||
# Redis with TLS/SSL encryption and CA certificate
|
||||
# REDIS_URI=rediss://127.0.0.1:6380
|
||||
# REDIS_CA=/path/to/ca-cert.pem
|
||||
|
||||
# Elasticache may need to use an alternate dnsLookup for TLS connections. see "Special Note: Aws Elasticache Clusters with TLS" on this webpage: https://www.npmjs.com/package/ioredis
|
||||
# Enable alternative dnsLookup for redis
|
||||
# REDIS_USE_ALTERNATIVE_DNS_LOOKUP=true
|
||||
|
||||
# Redis authentication (if required)
|
||||
# REDIS_USERNAME=your_redis_username
|
||||
# REDIS_PASSWORD=your_redis_password
|
||||
|
||||
# Redis key prefix configuration
|
||||
# Use environment variable name for dynamic prefix (recommended for cloud deployments)
|
||||
# REDIS_KEY_PREFIX_VAR=K_REVISION
|
||||
# Or use static prefix directly
|
||||
# REDIS_KEY_PREFIX=librechat
|
||||
|
||||
# Redis connection limits
|
||||
# REDIS_MAX_LISTENERS=40
|
||||
|
||||
# Redis ping interval in seconds (0 = disabled, >0 = enabled)
|
||||
# When set to a positive integer, Redis clients will ping the server at this interval to keep connections alive
|
||||
# When unset or 0, no pinging is performed (recommended for most use cases)
|
||||
# REDIS_PING_INTERVAL=300
|
||||
|
||||
# Force specific cache namespaces to use in-memory storage even when Redis is enabled
|
||||
# Comma-separated list of CacheKeys
|
||||
# Defaults to CONFIG_STORE,APP_CONFIG so YAML-derived config stays per-container (safe for blue/green deployments)
|
||||
# Set to empty string to force all namespaces through Redis: FORCED_IN_MEMORY_CACHE_NAMESPACES=
|
||||
# FORCED_IN_MEMORY_CACHE_NAMESPACES=CONFIG_STORE,APP_CONFIG
|
||||
|
||||
# Leader Election Configuration (for multi-instance deployments with Redis)
|
||||
# Duration in seconds that the leader lease is valid before it expires (default: 25)
|
||||
# LEADER_LEASE_DURATION=25
|
||||
# Interval in seconds at which the leader renews its lease (default: 10)
|
||||
# LEADER_RENEW_INTERVAL=10
|
||||
# Maximum number of retry attempts when renewing the lease fails (default: 3)
|
||||
# LEADER_RENEW_ATTEMPTS=3
|
||||
# Delay in seconds between retry attempts when renewing the lease (default: 0.5)
|
||||
# LEADER_RENEW_RETRY_DELAY=0.5
|
||||
# USE_REDIS_CLUSTER=true
|
||||
# REDIS_CA=/path/to/ca.crt
|
||||
|
||||
#==================================================#
|
||||
# Others #
|
||||
@@ -822,43 +667,31 @@ HELP_AND_FAQ_URL=https://hanzo.ai/chat
|
||||
OPENWEATHER_API_KEY=
|
||||
|
||||
#====================================#
|
||||
# Hanzo Chat Code Interpreter API #
|
||||
# Hanzo Chat Code Interpreter API #
|
||||
#====================================#
|
||||
|
||||
# "Run Code" routes through the Hanzo unified backend (api.hanzo.ai/v1/exec),
|
||||
# which executes in a Hanzo sandbox. execute_code POSTs {BASEURL}/exec with
|
||||
# X-API-Key. https://hanzo.ai/docs/chat/code-interpreter
|
||||
# LIBRECHAT_CODE_BASEURL=https://api.hanzo.ai/v1
|
||||
# LIBRECHAT_CODE_API_KEY=your-key # prod: KMS chat-secrets/LIBRECHAT_CODE_API_KEY
|
||||
# https://code.hanzo.ai
|
||||
# HANZO_CODE_API_KEY=your-key
|
||||
|
||||
#======================#
|
||||
# Web Search #
|
||||
#======================#
|
||||
|
||||
# Web Search routes through the Hanzo unified backend ONLY — no external SaaS.
|
||||
# The searxng provider + firecrawl scraper both point at api.hanzo.ai/v1/websearch
|
||||
# (Hanzo metasearch + Hanzo Crawl). See librechat.yaml `webSearch`.
|
||||
# https://hanzo.ai/docs/chat/features/web_search
|
||||
# SEARXNG_INSTANCE_URL=https://api.hanzo.ai/v1/websearch
|
||||
# FIRECRAWL_API_URL=https://api.hanzo.ai/v1/websearch
|
||||
# WEBSEARCH_API_KEY=your-key # prod: KMS chat-secrets/WEBSEARCH_API_KEY
|
||||
#
|
||||
# Do NOT set SERPER_API_KEY / TAVILY_API_KEY / JINA_API_KEY / COHERE_API_KEY:
|
||||
# external providers are intentionally disabled (one way, unified backend).
|
||||
# Note: All of the following variable names can be customized.
|
||||
# Omit values to allow user to provide them.
|
||||
|
||||
#======================#
|
||||
# MCP Configuration #
|
||||
#======================#
|
||||
# For more information on configuration values, see:
|
||||
# https://docs.hanzo.ai/chat/features/web_search
|
||||
|
||||
# Treat 401/403 responses as OAuth requirement when no oauth metadata found
|
||||
# MCP_OAUTH_ON_AUTH_ERROR=true
|
||||
# Search Provider (Required)
|
||||
# SERPER_API_KEY=your_serper_api_key
|
||||
|
||||
# Timeout for OAuth detection requests in milliseconds
|
||||
# MCP_OAUTH_DETECTION_TIMEOUT=5000
|
||||
# Scraper (Required)
|
||||
# FIRECRAWL_API_KEY=your_firecrawl_api_key
|
||||
# Optional: Custom Firecrawl API URL
|
||||
# FIRECRAWL_API_URL=your_firecrawl_api_url
|
||||
|
||||
# Cache connection status checks for this many milliseconds to avoid expensive verification
|
||||
# MCP_CONNECTION_CHECK_TTL=60000
|
||||
|
||||
# Skip code challenge method validation (e.g., for AWS Cognito that supports S256 but doesn't advertise it)
|
||||
# When set to true, forces S256 code challenge even if not advertised in .well-known/openid-configuration
|
||||
# MCP_SKIP_CODE_CHALLENGE_CHECK=false
|
||||
# Reranker (Required)
|
||||
# JINA_API_KEY=your_jina_api_key
|
||||
# or
|
||||
# COHERE_API_KEY=your_cohere_api_key
|
||||
|
||||
@@ -1,660 +0,0 @@
|
||||
#=====================================================================#
|
||||
# LibreChat Configuration #
|
||||
#=====================================================================#
|
||||
# Please refer to the reference documentation for assistance #
|
||||
# with configuring your LibreChat environment. #
|
||||
# #
|
||||
# https://www.librechat.ai/docs/configuration/dotenv #
|
||||
#=====================================================================#
|
||||
|
||||
#==================================================#
|
||||
# Server Configuration #
|
||||
#==================================================#
|
||||
|
||||
HOST=localhost
|
||||
PORT=3080
|
||||
|
||||
MONGO_URI=mongodb://127.0.0.1:27017/LibreChat
|
||||
|
||||
DOMAIN_CLIENT=http://localhost:3080
|
||||
DOMAIN_SERVER=http://localhost:3080
|
||||
|
||||
NO_INDEX=true
|
||||
# Use the address that is at most n number of hops away from the Express application.
|
||||
# req.socket.remoteAddress is the first hop, and the rest are looked for in the X-Forwarded-For header from right to left.
|
||||
# A value of 0 means that the first untrusted address would be req.socket.remoteAddress, i.e. there is no reverse proxy.
|
||||
# Defaulted to 1.
|
||||
TRUST_PROXY=1
|
||||
|
||||
#===============#
|
||||
# JSON Logging #
|
||||
#===============#
|
||||
|
||||
# Use when process console logs in cloud deployment like GCP/AWS
|
||||
CONSOLE_JSON=false
|
||||
|
||||
#===============#
|
||||
# Debug Logging #
|
||||
#===============#
|
||||
|
||||
DEBUG_LOGGING=true
|
||||
DEBUG_CONSOLE=false
|
||||
|
||||
#=============#
|
||||
# Permissions #
|
||||
#=============#
|
||||
|
||||
# UID=1000
|
||||
# GID=1000
|
||||
|
||||
#===============#
|
||||
# Configuration #
|
||||
#===============#
|
||||
# Use an absolute path, a relative path, or a URL
|
||||
|
||||
# CONFIG_PATH="/alternative/path/to/librechat.yaml"
|
||||
|
||||
#===================================================#
|
||||
# Endpoints #
|
||||
#===================================================#
|
||||
|
||||
# ENDPOINTS=openAI,assistants,azureOpenAI,google,anthropic
|
||||
|
||||
PROXY=
|
||||
|
||||
#===================================#
|
||||
# Known Endpoints - librechat.yaml #
|
||||
#===================================#
|
||||
# https://www.librechat.ai/docs/configuration/librechat_yaml/ai_endpoints
|
||||
|
||||
# ANYSCALE_API_KEY=
|
||||
# APIPIE_API_KEY=
|
||||
# COHERE_API_KEY=
|
||||
# DEEPSEEK_API_KEY=
|
||||
# DATABRICKS_API_KEY=
|
||||
# FIREWORKS_API_KEY=
|
||||
# GROQ_API_KEY=
|
||||
# HUGGINGFACE_TOKEN=
|
||||
# MISTRAL_API_KEY=
|
||||
# OPENROUTER_KEY=
|
||||
# PERPLEXITY_API_KEY=
|
||||
# SHUTTLEAI_API_KEY=
|
||||
# TOGETHERAI_API_KEY=
|
||||
# UNIFY_API_KEY=
|
||||
# XAI_API_KEY=
|
||||
|
||||
#============#
|
||||
# Anthropic #
|
||||
#============#
|
||||
|
||||
ANTHROPIC_API_KEY=user_provided
|
||||
# ANTHROPIC_MODELS=claude-opus-4-20250514,claude-sonnet-4-20250514,claude-3-7-sonnet-20250219,claude-3-5-sonnet-20241022,claude-3-5-haiku-20241022,claude-3-opus-20240229,claude-3-sonnet-20240229,claude-3-haiku-20240307
|
||||
# ANTHROPIC_REVERSE_PROXY=
|
||||
|
||||
#============#
|
||||
# Azure #
|
||||
#============#
|
||||
|
||||
# Note: these variables are DEPRECATED
|
||||
# Use the `librechat.yaml` configuration for `azureOpenAI` instead
|
||||
# You may also continue to use them if you opt out of using the `librechat.yaml` configuration
|
||||
|
||||
# AZURE_OPENAI_DEFAULT_MODEL=gpt-3.5-turbo # Deprecated
|
||||
# AZURE_OPENAI_MODELS=gpt-3.5-turbo,gpt-4 # Deprecated
|
||||
# AZURE_USE_MODEL_AS_DEPLOYMENT_NAME=TRUE # Deprecated
|
||||
# AZURE_API_KEY= # Deprecated
|
||||
# AZURE_OPENAI_API_INSTANCE_NAME= # Deprecated
|
||||
# AZURE_OPENAI_API_DEPLOYMENT_NAME= # Deprecated
|
||||
# AZURE_OPENAI_API_VERSION= # Deprecated
|
||||
# AZURE_OPENAI_API_COMPLETIONS_DEPLOYMENT_NAME= # Deprecated
|
||||
# AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME= # Deprecated
|
||||
# PLUGINS_USE_AZURE="true" # Deprecated
|
||||
|
||||
#=================#
|
||||
# AWS Bedrock #
|
||||
#=================#
|
||||
|
||||
# BEDROCK_AWS_DEFAULT_REGION=us-east-1 # A default region must be provided
|
||||
# BEDROCK_AWS_ACCESS_KEY_ID=someAccessKey
|
||||
# BEDROCK_AWS_SECRET_ACCESS_KEY=someSecretAccessKey
|
||||
# BEDROCK_AWS_SESSION_TOKEN=someSessionToken
|
||||
|
||||
# Note: This example list is not meant to be exhaustive. If omitted, all known, supported model IDs will be included for you.
|
||||
# BEDROCK_AWS_MODELS=anthropic.claude-3-5-sonnet-20240620-v1:0,meta.llama3-1-8b-instruct-v1:0
|
||||
|
||||
# See all Bedrock model IDs here: https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html#model-ids-arns
|
||||
|
||||
# Notes on specific models:
|
||||
# The following models are not support due to not supporting streaming:
|
||||
# ai21.j2-mid-v1
|
||||
|
||||
# The following models are not support due to not supporting conversation history:
|
||||
# ai21.j2-ultra-v1, cohere.command-text-v14, cohere.command-light-text-v14
|
||||
|
||||
#============#
|
||||
# Google #
|
||||
#============#
|
||||
|
||||
GOOGLE_KEY=user_provided
|
||||
|
||||
# GOOGLE_REVERSE_PROXY=
|
||||
# Some reverse proxies do not support the X-goog-api-key header, uncomment to pass the API key in Authorization header instead.
|
||||
# GOOGLE_AUTH_HEADER=true
|
||||
|
||||
# Gemini API (AI Studio)
|
||||
# GOOGLE_MODELS=gemini-2.5-pro,gemini-2.5-flash,gemini-2.5-flash-lite-preview-06-17,gemini-2.0-flash,gemini-2.0-flash-lite
|
||||
|
||||
# Vertex AI
|
||||
# GOOGLE_MODELS=gemini-2.5-pro,gemini-2.5-flash,gemini-2.5-flash-lite-preview-06-17,gemini-2.0-flash-001,gemini-2.0-flash-lite-001
|
||||
|
||||
# GOOGLE_TITLE_MODEL=gemini-2.0-flash-lite-001
|
||||
|
||||
# GOOGLE_LOC=us-central1
|
||||
|
||||
# Google Safety Settings
|
||||
# NOTE: These settings apply to both Vertex AI and Gemini API (AI Studio)
|
||||
#
|
||||
# For Vertex AI:
|
||||
# To use the BLOCK_NONE setting, you need either:
|
||||
# (a) Access through an allowlist via your Google account team, or
|
||||
# (b) Switch to monthly invoiced billing: https://cloud.google.com/billing/docs/how-to/invoiced-billing
|
||||
#
|
||||
# For Gemini API (AI Studio):
|
||||
# BLOCK_NONE is available by default, no special account requirements.
|
||||
#
|
||||
# Available options: BLOCK_NONE, BLOCK_ONLY_HIGH, BLOCK_MEDIUM_AND_ABOVE, BLOCK_LOW_AND_ABOVE
|
||||
#
|
||||
# GOOGLE_SAFETY_SEXUALLY_EXPLICIT=BLOCK_ONLY_HIGH
|
||||
# GOOGLE_SAFETY_HATE_SPEECH=BLOCK_ONLY_HIGH
|
||||
# GOOGLE_SAFETY_HARASSMENT=BLOCK_ONLY_HIGH
|
||||
# GOOGLE_SAFETY_DANGEROUS_CONTENT=BLOCK_ONLY_HIGH
|
||||
# GOOGLE_SAFETY_CIVIC_INTEGRITY=BLOCK_ONLY_HIGH
|
||||
|
||||
#============#
|
||||
# OpenAI #
|
||||
#============#
|
||||
|
||||
OPENAI_API_KEY=user_provided
|
||||
# OPENAI_MODELS=o1,o1-mini,o1-preview,gpt-4o,gpt-4.5-preview,chatgpt-4o-latest,gpt-4o-mini,gpt-3.5-turbo-0125,gpt-3.5-turbo-0301,gpt-3.5-turbo,gpt-4,gpt-4-0613,gpt-4-vision-preview,gpt-3.5-turbo-0613,gpt-3.5-turbo-16k-0613,gpt-4-0125-preview,gpt-4-turbo-preview,gpt-4-1106-preview,gpt-3.5-turbo-1106,gpt-3.5-turbo-instruct,gpt-3.5-turbo-instruct-0914,gpt-3.5-turbo-16k
|
||||
|
||||
DEBUG_OPENAI=false
|
||||
|
||||
# TITLE_CONVO=false
|
||||
# OPENAI_TITLE_MODEL=gpt-4o-mini
|
||||
|
||||
# OPENAI_SUMMARIZE=true
|
||||
# OPENAI_SUMMARY_MODEL=gpt-4o-mini
|
||||
|
||||
# OPENAI_FORCE_PROMPT=true
|
||||
|
||||
# OPENAI_REVERSE_PROXY=
|
||||
|
||||
# OPENAI_ORGANIZATION=
|
||||
|
||||
#====================#
|
||||
# Assistants API #
|
||||
#====================#
|
||||
|
||||
ASSISTANTS_API_KEY=user_provided
|
||||
# ASSISTANTS_BASE_URL=
|
||||
# ASSISTANTS_MODELS=gpt-4o,gpt-4o-mini,gpt-3.5-turbo-0125,gpt-3.5-turbo-16k-0613,gpt-3.5-turbo-16k,gpt-3.5-turbo,gpt-4,gpt-4-0314,gpt-4-32k-0314,gpt-4-0613,gpt-3.5-turbo-0613,gpt-3.5-turbo-1106,gpt-4-0125-preview,gpt-4-turbo-preview,gpt-4-1106-preview
|
||||
|
||||
#==========================#
|
||||
# Azure Assistants API #
|
||||
#==========================#
|
||||
|
||||
# Note: You should map your credentials with custom variables according to your Azure OpenAI Configuration
|
||||
# The models for Azure Assistants are also determined by your Azure OpenAI configuration.
|
||||
|
||||
# More info, including how to enable use of Assistants with Azure here:
|
||||
# https://www.librechat.ai/docs/configuration/librechat_yaml/ai_endpoints/azure#using-assistants-with-azure
|
||||
|
||||
#============#
|
||||
# Plugins #
|
||||
#============#
|
||||
|
||||
# PLUGIN_MODELS=gpt-4o,gpt-4o-mini,gpt-4,gpt-4-turbo-preview,gpt-4-0125-preview,gpt-4-1106-preview,gpt-4-0613,gpt-3.5-turbo,gpt-3.5-turbo-0125,gpt-3.5-turbo-1106,gpt-3.5-turbo-0613
|
||||
|
||||
DEBUG_PLUGINS=true
|
||||
|
||||
CREDS_KEY=f34be427ebb29de8d88c107a71546019685ed8b241d8f2ed00c3df97ad2566f0
|
||||
CREDS_IV=e2341419ec3dd3d19b13a1a87fafcbfb
|
||||
|
||||
# Azure AI Search
|
||||
#-----------------
|
||||
AZURE_AI_SEARCH_SERVICE_ENDPOINT=
|
||||
AZURE_AI_SEARCH_INDEX_NAME=
|
||||
AZURE_AI_SEARCH_API_KEY=
|
||||
|
||||
AZURE_AI_SEARCH_API_VERSION=
|
||||
AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE=
|
||||
AZURE_AI_SEARCH_SEARCH_OPTION_TOP=
|
||||
AZURE_AI_SEARCH_SEARCH_OPTION_SELECT=
|
||||
|
||||
# OpenAI Image Tools Customization
|
||||
#----------------
|
||||
# IMAGE_GEN_OAI_DESCRIPTION_WITH_FILES=Custom description for image generation tool when files are present
|
||||
# IMAGE_GEN_OAI_DESCRIPTION_NO_FILES=Custom description for image generation tool when no files are present
|
||||
# IMAGE_EDIT_OAI_DESCRIPTION=Custom description for image editing tool
|
||||
# IMAGE_GEN_OAI_PROMPT_DESCRIPTION=Custom prompt description for image generation tool
|
||||
# IMAGE_EDIT_OAI_PROMPT_DESCRIPTION=Custom prompt description for image editing tool
|
||||
|
||||
# DALL·E
|
||||
#----------------
|
||||
# DALLE_API_KEY=
|
||||
# DALLE3_API_KEY=
|
||||
# DALLE2_API_KEY=
|
||||
# DALLE3_SYSTEM_PROMPT=
|
||||
# DALLE2_SYSTEM_PROMPT=
|
||||
# DALLE_REVERSE_PROXY=
|
||||
# DALLE3_BASEURL=
|
||||
# DALLE2_BASEURL=
|
||||
|
||||
# DALL·E (via Azure OpenAI)
|
||||
# Note: requires some of the variables above to be set
|
||||
#----------------
|
||||
# DALLE3_AZURE_API_VERSION=
|
||||
# DALLE2_AZURE_API_VERSION=
|
||||
|
||||
# Flux
|
||||
#-----------------
|
||||
FLUX_API_BASE_URL=https://api.us1.bfl.ai
|
||||
# FLUX_API_BASE_URL = 'https://api.bfl.ml';
|
||||
|
||||
# Get your API key at https://api.us1.bfl.ai/auth/profile
|
||||
# FLUX_API_KEY=
|
||||
|
||||
# Google
|
||||
#-----------------
|
||||
GOOGLE_SEARCH_API_KEY=
|
||||
GOOGLE_CSE_ID=
|
||||
|
||||
# YOUTUBE
|
||||
#-----------------
|
||||
YOUTUBE_API_KEY=
|
||||
|
||||
# SerpAPI
|
||||
#-----------------
|
||||
SERPAPI_API_KEY=
|
||||
|
||||
# Stable Diffusion
|
||||
#-----------------
|
||||
SD_WEBUI_URL=http://host.docker.internal:7860
|
||||
|
||||
# Tavily
|
||||
#-----------------
|
||||
TAVILY_API_KEY=
|
||||
|
||||
# Traversaal
|
||||
#-----------------
|
||||
TRAVERSAAL_API_KEY=
|
||||
|
||||
# WolframAlpha
|
||||
#-----------------
|
||||
WOLFRAM_APP_ID=
|
||||
|
||||
# Zapier
|
||||
#-----------------
|
||||
ZAPIER_NLA_API_KEY=
|
||||
|
||||
#==================================================#
|
||||
# Search #
|
||||
#==================================================#
|
||||
|
||||
SEARCH=true
|
||||
MEILI_NO_ANALYTICS=true
|
||||
MEILI_HOST=http://0.0.0.0:7700
|
||||
MEILI_MASTER_KEY=DrhYf7zENyR6AlUCKmnz0eYASOQdl6zxH7s7MKFSfFCt
|
||||
|
||||
# Optional: Disable indexing, useful in a multi-node setup
|
||||
# where only one instance should perform an index sync.
|
||||
# MEILI_NO_SYNC=true
|
||||
|
||||
#==================================================#
|
||||
# Speech to Text & Text to Speech #
|
||||
#==================================================#
|
||||
|
||||
STT_API_KEY=
|
||||
TTS_API_KEY=
|
||||
|
||||
#==================================================#
|
||||
# RAG #
|
||||
#==================================================#
|
||||
# More info: https://www.librechat.ai/docs/configuration/rag_api
|
||||
|
||||
# RAG_OPENAI_BASEURL=
|
||||
# RAG_OPENAI_API_KEY=
|
||||
# RAG_USE_FULL_CONTEXT=
|
||||
# EMBEDDINGS_PROVIDER=openai
|
||||
# EMBEDDINGS_MODEL=text-embedding-3-small
|
||||
|
||||
#===================================================#
|
||||
# User System #
|
||||
#===================================================#
|
||||
|
||||
#========================#
|
||||
# Moderation #
|
||||
#========================#
|
||||
|
||||
OPENAI_MODERATION=false
|
||||
OPENAI_MODERATION_API_KEY=
|
||||
# OPENAI_MODERATION_REVERSE_PROXY=
|
||||
|
||||
BAN_VIOLATIONS=true
|
||||
BAN_DURATION=1000 * 60 * 60 * 2
|
||||
BAN_INTERVAL=20
|
||||
|
||||
LOGIN_VIOLATION_SCORE=1
|
||||
REGISTRATION_VIOLATION_SCORE=1
|
||||
CONCURRENT_VIOLATION_SCORE=1
|
||||
MESSAGE_VIOLATION_SCORE=1
|
||||
NON_BROWSER_VIOLATION_SCORE=20
|
||||
|
||||
LOGIN_MAX=7
|
||||
LOGIN_WINDOW=5
|
||||
REGISTER_MAX=5
|
||||
REGISTER_WINDOW=60
|
||||
|
||||
LIMIT_CONCURRENT_MESSAGES=true
|
||||
CONCURRENT_MESSAGE_MAX=2
|
||||
|
||||
LIMIT_MESSAGE_IP=true
|
||||
MESSAGE_IP_MAX=40
|
||||
MESSAGE_IP_WINDOW=1
|
||||
|
||||
LIMIT_MESSAGE_USER=false
|
||||
MESSAGE_USER_MAX=40
|
||||
MESSAGE_USER_WINDOW=1
|
||||
|
||||
ILLEGAL_MODEL_REQ_SCORE=5
|
||||
|
||||
#========================#
|
||||
# Balance #
|
||||
#========================#
|
||||
|
||||
# CHECK_BALANCE=false
|
||||
# START_BALANCE=20000 # note: the number of tokens that will be credited after registration.
|
||||
|
||||
#========================#
|
||||
# Registration and Login #
|
||||
#========================#
|
||||
|
||||
ALLOW_EMAIL_LOGIN=true
|
||||
ALLOW_REGISTRATION=true
|
||||
ALLOW_SOCIAL_LOGIN=false
|
||||
ALLOW_SOCIAL_REGISTRATION=false
|
||||
ALLOW_PASSWORD_RESET=false
|
||||
# ALLOW_ACCOUNT_DELETION=true # note: enabled by default if omitted/commented out
|
||||
ALLOW_UNVERIFIED_EMAIL_LOGIN=true
|
||||
|
||||
SESSION_EXPIRY=1000 * 60 * 15
|
||||
REFRESH_TOKEN_EXPIRY=(1000 * 60 * 60 * 24) * 7
|
||||
|
||||
JWT_SECRET=16f8c0ef4a5d391b26034086c628469d3f9f497f08163ab9b40137092f2909ef
|
||||
JWT_REFRESH_SECRET=eaa5191f2914e30b9387fd84e254e4ba6fc51b4654968a9b0803b456a54b8418
|
||||
|
||||
# Discord
|
||||
DISCORD_CLIENT_ID=
|
||||
DISCORD_CLIENT_SECRET=
|
||||
DISCORD_CALLBACK_URL=/oauth/discord/callback
|
||||
|
||||
# Facebook
|
||||
FACEBOOK_CLIENT_ID=
|
||||
FACEBOOK_CLIENT_SECRET=
|
||||
FACEBOOK_CALLBACK_URL=/oauth/facebook/callback
|
||||
|
||||
# GitHub
|
||||
GITHUB_CLIENT_ID=
|
||||
GITHUB_CLIENT_SECRET=
|
||||
GITHUB_CALLBACK_URL=/oauth/github/callback
|
||||
# GitHub Enterprise
|
||||
# GITHUB_ENTERPRISE_BASE_URL=
|
||||
# GITHUB_ENTERPRISE_USER_AGENT=
|
||||
|
||||
# Google
|
||||
GOOGLE_CLIENT_ID=
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
GOOGLE_CALLBACK_URL=/oauth/google/callback
|
||||
|
||||
# Apple
|
||||
APPLE_CLIENT_ID=
|
||||
APPLE_TEAM_ID=
|
||||
APPLE_KEY_ID=
|
||||
APPLE_PRIVATE_KEY_PATH=
|
||||
APPLE_CALLBACK_URL=/oauth/apple/callback
|
||||
|
||||
# OpenID
|
||||
OPENID_CLIENT_ID=
|
||||
OPENID_CLIENT_SECRET=
|
||||
OPENID_ISSUER=
|
||||
OPENID_SESSION_SECRET=
|
||||
OPENID_SCOPE="openid profile email"
|
||||
OPENID_CALLBACK_URL=/oauth/openid/callback
|
||||
OPENID_REQUIRED_ROLE=
|
||||
OPENID_REQUIRED_ROLE_TOKEN_KIND=
|
||||
OPENID_REQUIRED_ROLE_PARAMETER_PATH=
|
||||
# Set to determine which user info property returned from OpenID Provider to store as the User's username
|
||||
OPENID_USERNAME_CLAIM=
|
||||
# Set to determine which user info property returned from OpenID Provider to store as the User's name
|
||||
OPENID_NAME_CLAIM=
|
||||
|
||||
OPENID_BUTTON_LABEL=
|
||||
OPENID_IMAGE_URL=
|
||||
# Set to true to automatically redirect to the OpenID provider when a user visits the login page
|
||||
# This will bypass the login form completely for users, only use this if OpenID is your only authentication method
|
||||
OPENID_AUTO_REDIRECT=false
|
||||
# Set to true to use PKCE (Proof Key for Code Exchange) for OpenID authentication
|
||||
OPENID_USE_PKCE=false
|
||||
#Set to true to reuse openid tokens for authentication management instead of using the mongodb session and the custom refresh token.
|
||||
OPENID_REUSE_TOKENS=
|
||||
#By default, signing key verification results are cached in order to prevent excessive HTTP requests to the JWKS endpoint.
|
||||
#If a signing key matching the kid is found, this will be cached and the next time this kid is requested the signing key will be served from the cache.
|
||||
#Default is true.
|
||||
OPENID_JWKS_URL_CACHE_ENABLED=
|
||||
OPENID_JWKS_URL_CACHE_TIME= # 600000 ms eq to 10 minutes leave empty to disable caching
|
||||
#Set to true to trigger token exchange flow to acquire access token for the userinfo endpoint.
|
||||
OPENID_ON_BEHALF_FLOW_FOR_USERINFRO_REQUIRED=
|
||||
OPENID_ON_BEHALF_FLOW_USERINFRO_SCOPE = "user.read" # example for Scope Needed for Microsoft Graph API
|
||||
# Set to true to use the OpenID Connect end session endpoint for logout
|
||||
OPENID_USE_END_SESSION_ENDPOINT=
|
||||
|
||||
|
||||
# SAML
|
||||
# Note: If OpenID is enabled, SAML authentication will be automatically disabled.
|
||||
SAML_ENTRY_POINT=
|
||||
SAML_ISSUER=
|
||||
SAML_CERT=
|
||||
SAML_CALLBACK_URL=/oauth/saml/callback
|
||||
SAML_SESSION_SECRET=
|
||||
|
||||
# Attribute mappings (optional)
|
||||
SAML_EMAIL_CLAIM=
|
||||
SAML_USERNAME_CLAIM=
|
||||
SAML_GIVEN_NAME_CLAIM=
|
||||
SAML_FAMILY_NAME_CLAIM=
|
||||
SAML_PICTURE_CLAIM=
|
||||
SAML_NAME_CLAIM=
|
||||
|
||||
# Logint buttion settings (optional)
|
||||
SAML_BUTTON_LABEL=
|
||||
SAML_IMAGE_URL=
|
||||
|
||||
# Whether the SAML Response should be signed.
|
||||
# - If "true", the entire `SAML Response` will be signed.
|
||||
# - If "false" or unset, only the `SAML Assertion` will be signed (default behavior).
|
||||
# SAML_USE_AUTHN_RESPONSE_SIGNED=
|
||||
|
||||
|
||||
# LDAP
|
||||
LDAP_URL=
|
||||
LDAP_BIND_DN=
|
||||
LDAP_BIND_CREDENTIALS=
|
||||
LDAP_USER_SEARCH_BASE=
|
||||
#LDAP_SEARCH_FILTER="mail="
|
||||
LDAP_CA_CERT_PATH=
|
||||
# LDAP_TLS_REJECT_UNAUTHORIZED=
|
||||
# LDAP_STARTTLS=
|
||||
# LDAP_LOGIN_USES_USERNAME=true
|
||||
# LDAP_ID=
|
||||
# LDAP_USERNAME=
|
||||
# LDAP_EMAIL=
|
||||
# LDAP_FULL_NAME=
|
||||
|
||||
#========================#
|
||||
# Email Password Reset #
|
||||
#========================#
|
||||
|
||||
EMAIL_SERVICE=
|
||||
EMAIL_HOST=
|
||||
EMAIL_PORT=25
|
||||
EMAIL_ENCRYPTION=
|
||||
EMAIL_ENCRYPTION_HOSTNAME=
|
||||
EMAIL_ALLOW_SELFSIGNED=
|
||||
EMAIL_USERNAME=
|
||||
EMAIL_PASSWORD=
|
||||
EMAIL_FROM_NAME=
|
||||
EMAIL_FROM=noreply@librechat.ai
|
||||
|
||||
#========================#
|
||||
# Mailgun API #
|
||||
#========================#
|
||||
|
||||
# MAILGUN_API_KEY=your-mailgun-api-key
|
||||
# MAILGUN_DOMAIN=mg.yourdomain.com
|
||||
# EMAIL_FROM=noreply@yourdomain.com
|
||||
# EMAIL_FROM_NAME="LibreChat"
|
||||
|
||||
# # Optional: For EU region
|
||||
# MAILGUN_HOST=https://api.eu.mailgun.net
|
||||
|
||||
#========================#
|
||||
# Firebase CDN #
|
||||
#========================#
|
||||
|
||||
FIREBASE_API_KEY=
|
||||
FIREBASE_AUTH_DOMAIN=
|
||||
FIREBASE_PROJECT_ID=
|
||||
FIREBASE_STORAGE_BUCKET=
|
||||
FIREBASE_MESSAGING_SENDER_ID=
|
||||
FIREBASE_APP_ID=
|
||||
|
||||
#========================#
|
||||
# S3 AWS Bucket #
|
||||
#========================#
|
||||
|
||||
AWS_ENDPOINT_URL=
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_REGION=
|
||||
AWS_BUCKET_NAME=
|
||||
|
||||
#========================#
|
||||
# Azure Blob Storage #
|
||||
#========================#
|
||||
|
||||
AZURE_STORAGE_CONNECTION_STRING=
|
||||
AZURE_STORAGE_PUBLIC_ACCESS=false
|
||||
AZURE_CONTAINER_NAME=files
|
||||
|
||||
#========================#
|
||||
# Shared Links #
|
||||
#========================#
|
||||
|
||||
ALLOW_SHARED_LINKS=true
|
||||
ALLOW_SHARED_LINKS_PUBLIC=true
|
||||
|
||||
#==============================#
|
||||
# Static File Cache Control #
|
||||
#==============================#
|
||||
|
||||
# Leave commented out to use defaults: 1 day (86400 seconds) for s-maxage and 2 days (172800 seconds) for max-age
|
||||
# NODE_ENV must be set to production for these to take effect
|
||||
# STATIC_CACHE_MAX_AGE=172800
|
||||
# STATIC_CACHE_S_MAX_AGE=86400
|
||||
|
||||
# If you have another service in front of your LibreChat doing compression, disable express based compression here
|
||||
# DISABLE_COMPRESSION=true
|
||||
|
||||
#===================================================#
|
||||
# UI #
|
||||
#===================================================#
|
||||
|
||||
APP_TITLE=LibreChat
|
||||
# CUSTOM_FOOTER="My custom footer"
|
||||
HELP_AND_FAQ_URL=https://librechat.ai
|
||||
|
||||
# SHOW_BIRTHDAY_ICON=true
|
||||
|
||||
# Google tag manager id
|
||||
#ANALYTICS_GTM_ID=user provided google tag manager id
|
||||
|
||||
#===============#
|
||||
# REDIS Options #
|
||||
#===============#
|
||||
|
||||
# REDIS_URI=10.10.10.10:6379
|
||||
# USE_REDIS=true
|
||||
|
||||
# USE_REDIS_CLUSTER=true
|
||||
# REDIS_CA=/path/to/ca.crt
|
||||
|
||||
#==================================================#
|
||||
# Others #
|
||||
#==================================================#
|
||||
# You should leave the following commented out #
|
||||
|
||||
# NODE_ENV=
|
||||
|
||||
# E2E_USER_EMAIL=
|
||||
# E2E_USER_PASSWORD=
|
||||
|
||||
#=====================================================#
|
||||
# Cache Headers #
|
||||
#=====================================================#
|
||||
# Headers that control caching of the index.html #
|
||||
# Default configuration prevents caching to ensure #
|
||||
# users always get the latest version. Customize #
|
||||
# only if you understand caching implications. #
|
||||
|
||||
# INDEX_CACHE_CONTROL=no-cache, no-store, must-revalidate
|
||||
# INDEX_PRAGMA=no-cache
|
||||
# INDEX_EXPIRES=0
|
||||
|
||||
# no-cache: Forces validation with server before using cached version
|
||||
# no-store: Prevents storing the response entirely
|
||||
# must-revalidate: Prevents using stale content when offline
|
||||
|
||||
#=====================================================#
|
||||
# OpenWeather #
|
||||
#=====================================================#
|
||||
OPENWEATHER_API_KEY=
|
||||
|
||||
#====================================#
|
||||
# LibreChat Code Interpreter API #
|
||||
#====================================#
|
||||
|
||||
# https://code.librechat.ai
|
||||
# LIBRECHAT_CODE_API_KEY=your-key
|
||||
|
||||
#======================#
|
||||
# Web Search #
|
||||
#======================#
|
||||
|
||||
# Note: All of the following variable names can be customized.
|
||||
# Omit values to allow user to provide them.
|
||||
|
||||
# For more information on configuration values, see:
|
||||
# https://librechat.ai/docs/features/web_search
|
||||
|
||||
# Search Provider (Required)
|
||||
# SERPER_API_KEY=your_serper_api_key
|
||||
|
||||
# Scraper (Required)
|
||||
# FIRECRAWL_API_KEY=your_firecrawl_api_key
|
||||
# Optional: Custom Firecrawl API URL
|
||||
# FIRECRAWL_API_URL=your_firecrawl_api_url
|
||||
|
||||
# Reranker (Required)
|
||||
# JINA_API_KEY=your_jina_api_key
|
||||
# or
|
||||
# COHERE_API_KEY=your_cohere_api_key
|
||||
@@ -1,89 +0,0 @@
|
||||
# Hanzo Chat - Cloud Deployment Configuration
|
||||
# All AI models routed through api.hanzo.ai (zen4 series + standard models)
|
||||
|
||||
# ====== HANZO CLOUD API ======
|
||||
OPENAI_API_KEY=sk-hanzo-your-api-key-here
|
||||
OPENAI_BASE_URL=https://api.hanzo.ai/v1
|
||||
|
||||
# Code Interpreter ("Run Code") — Hanzo unified backend.
|
||||
# LibreChat's execute_code tool POSTs {LIBRECHAT_CODE_BASEURL}/exec with header
|
||||
# X-API-Key. cloud-api serves /v1/exec (-> sandboxed executor). The key is
|
||||
# KMS-sourced (chat-secrets/LIBRECHAT_CODE_API_KEY). These are the ONLY vars
|
||||
# LibreChat reads — the old CODE_EXECUTION_ENDPOINT/RUNTIME_API_KEY were dead.
|
||||
LIBRECHAT_CODE_BASEURL=https://api.hanzo.ai/v1
|
||||
LIBRECHAT_CODE_API_KEY=set-in-KMS-chat-secrets
|
||||
|
||||
# Web Search — Hanzo unified backend (searxng + firecrawl compat over Hanzo
|
||||
# search + crawl). See librechat.yaml `webSearch`. One shared service key.
|
||||
SEARXNG_INSTANCE_URL=https://api.hanzo.ai/v1/websearch
|
||||
FIRECRAWL_API_URL=https://api.hanzo.ai/v1/websearch
|
||||
WEBSEARCH_API_KEY=set-in-KMS-chat-secrets
|
||||
|
||||
# ====== BRANDING ======
|
||||
APP_TITLE=Hanzo Chat
|
||||
CUSTOM_FOOTER=Powered by Hanzo AI
|
||||
|
||||
# ====== LOCAL SERVICES ======
|
||||
# MongoDB for chat history
|
||||
MONGO_URI=mongodb://hanzo:hanzo123@localhost:27017/HanzoChat?authSource=admin
|
||||
MONGO_PASSWORD=hanzo123
|
||||
|
||||
# Meilisearch for full-text search
|
||||
MEILI_HOST=http://localhost:7700
|
||||
MEILI_MASTER_KEY=HanzoMeiliMasterKey
|
||||
|
||||
# ====== APPLICATION SETTINGS ======
|
||||
DOMAIN_CLIENT=https://hanzo.chat
|
||||
DOMAIN_SERVER=https://hanzo.chat
|
||||
|
||||
# ====== SECURITY (CHANGE IN PRODUCTION) ======
|
||||
JWT_SECRET=hanzo-jwt-secret-change-in-production
|
||||
JWT_REFRESH_SECRET=hanzo-refresh-secret-change-in-production
|
||||
|
||||
# ====== HANZO IAM (hanzo.id) OIDC SSO ======
|
||||
OPENID_CLIENT_ID=chat-app
|
||||
OPENID_CLIENT_SECRET=chat-app-client-secret
|
||||
OPENID_ISSUER=https://hanzo.id
|
||||
OPENID_SESSION_SECRET=hanzo-openid-session-change-in-production
|
||||
OPENID_SCOPE=openid profile email
|
||||
OPENID_CALLBACK_URL=/oauth/openid/callback
|
||||
OPENID_BUTTON_LABEL=Log in with Hanzo
|
||||
OPENID_IMAGE_URL=https://hanzo.ai/logo/icon.svg
|
||||
OPENID_USERNAME_CLAIM=name
|
||||
OPENID_NAME_CLAIM=name
|
||||
OPENID_USE_PKCE=true
|
||||
|
||||
# ====== FEATURES ======
|
||||
ALLOW_REGISTRATION=true
|
||||
ALLOW_UNVERIFIED_EMAIL_LOGIN=true
|
||||
ALLOW_SOCIAL_LOGIN=true
|
||||
ALLOW_SOCIAL_REGISTRATION=true
|
||||
|
||||
# MCP Tools
|
||||
MCP_ENABLED=true
|
||||
|
||||
# ====== AVAILABLE MODELS ======
|
||||
# zen4 series (default, all via api.hanzo.ai):
|
||||
# zen4 - General purpose
|
||||
# zen4-pro - High capability
|
||||
# zen4-max - Extended context
|
||||
# zen4-mini - Fast & efficient
|
||||
# zen4-ultra - Maximum reasoning
|
||||
# zen4-coder - Code specialist
|
||||
# zen4-thinking - Deep reasoning
|
||||
# zen4-coder-pro - Premium coding
|
||||
#
|
||||
# Standard models (via api.hanzo.ai):
|
||||
# gpt-4o, gpt-4o-mini
|
||||
# claude-sonnet-4-5, claude-haiku-4-5
|
||||
# deepseek-r1-distill-70b
|
||||
# qwen3-32b
|
||||
|
||||
# Debug
|
||||
DEBUG_LOGGING=false
|
||||
|
||||
# ====== NOTES ======
|
||||
# 1. Get your Hanzo API key from https://hanzo.ai/dashboard
|
||||
# 2. All AI providers are accessed through api.hanzo.ai -- no individual keys needed
|
||||
# 3. Model list is configured in librechat.yaml under endpoints.custom (Hanzo endpoint)
|
||||
# 4. zen4 is the default model for new conversations
|
||||
@@ -1,41 +0,0 @@
|
||||
# Hanzo IAM Integration Configuration
|
||||
# Copy this file to .env and update with your values
|
||||
|
||||
# Hanzo IAM SSO Configuration
|
||||
HANZO_IAM_CLIENT_ID=chat-local
|
||||
HANZO_IAM_CLIENT_SECRET=your-client-secret-here
|
||||
HANZO_IAM_SERVER_URL=http://localhost:9003
|
||||
|
||||
# For local development with Hanzo Stack
|
||||
# HANZO_IAM_SERVER_URL=http://iam:3000 # Use this when running in Docker
|
||||
|
||||
# Hanzo Router Configuration
|
||||
ROUTER_MASTER_KEY=your-master-key-here
|
||||
LITELLM_MASTER_KEY=${ROUTER_MASTER_KEY} # For backward compatibility
|
||||
PROXY_BASE_URL=http://localhost:3081
|
||||
|
||||
# Development Mode Demo Credentials (only works in development)
|
||||
# These are only used when NODE_ENV=development or DEV_MODE=true
|
||||
DEMO_USERNAME=demo@hanzo.ai
|
||||
DEMO_PASSWORD=hanzo123
|
||||
|
||||
# Choose Authentication Method:
|
||||
# Option 1: Built-in Auth (default)
|
||||
UI_USERNAME=admin
|
||||
# UI_PASSWORD=<your-password> # If not set, uses ROUTER_MASTER_KEY
|
||||
|
||||
# Option 2: Hanzo IAM SSO
|
||||
# Comment out UI_USERNAME above and set HANZO_IAM_CLIENT_ID/SECRET
|
||||
|
||||
# Option 3: Other SSO Providers (requires Hanzo Router Enterprise)
|
||||
# GOOGLE_CLIENT_ID=your-google-client-id
|
||||
# GOOGLE_CLIENT_SECRET=your-google-client-secret
|
||||
# MICROSOFT_CLIENT_ID=your-microsoft-client-id
|
||||
# MICROSOFT_CLIENT_SECRET=your-microsoft-client-secret
|
||||
# MICROSOFT_TENANT=your-tenant-id
|
||||
|
||||
# Database Configuration
|
||||
DATABASE_URL=mongodb://localhost:27017/hanzo-router
|
||||
|
||||
# Enable Development Mode Features
|
||||
DEV_MODE=false # Set to true for demo credentials
|
||||
@@ -1,67 +0,0 @@
|
||||
# Hanzo AI Chat Stack Environment Variables
|
||||
# Copy this file to .env and update with your values
|
||||
|
||||
# ==================================
|
||||
# LLM API Keys (at least one required)
|
||||
# ==================================
|
||||
OPENAI_API_KEY=sk-...
|
||||
ANTHROPIC_API_KEY=sk-ant-...
|
||||
TOGETHER_API_KEY=...
|
||||
|
||||
# ==================================
|
||||
# Database Configuration
|
||||
# ==================================
|
||||
POSTGRES_PASSWORD=hanzo123
|
||||
MONGO_PASSWORD=hanzo123
|
||||
REDIS_PASSWORD=hanzosecret
|
||||
|
||||
# ==================================
|
||||
# Service URLs
|
||||
# ==================================
|
||||
DOMAIN_CLIENT=http://localhost:3081
|
||||
DOMAIN_SERVER=http://localhost:3081
|
||||
|
||||
# ==================================
|
||||
# Security Keys (CHANGE IN PRODUCTION!)
|
||||
# ==================================
|
||||
ROUTER_MASTER_KEY=sk-hanzo-master-key
|
||||
JWT_SECRET=hanzo-jwt-secret-change-in-production
|
||||
JWT_REFRESH_SECRET=hanzo-refresh-secret-change-in-production
|
||||
CREDS_KEY=hanzo-creds-key-change-in-production
|
||||
CREDS_IV=hanzo-creds-iv-change
|
||||
MEILI_MASTER_KEY=HanzoMeiliMasterKey
|
||||
|
||||
# ==================================
|
||||
# Feature Flags
|
||||
# ==================================
|
||||
ALLOW_REGISTRATION=true
|
||||
ALLOW_UNVERIFIED_EMAIL_LOGIN=false
|
||||
DEBUG=false
|
||||
|
||||
# ==================================
|
||||
# Performance Tuning
|
||||
# ==================================
|
||||
ROUTER_WORKERS=4
|
||||
|
||||
# ==================================
|
||||
# Optional: Additional LLM Providers
|
||||
# ==================================
|
||||
# AZURE_API_KEY=...
|
||||
# AZURE_API_BASE=https://your-resource.openai.azure.com/
|
||||
# GROQ_API_KEY=...
|
||||
# COHERE_API_KEY=...
|
||||
|
||||
# ==================================
|
||||
# Optional: Observability
|
||||
# ==================================
|
||||
# LANGFUSE_PUBLIC_KEY=...
|
||||
# LANGFUSE_SECRET_KEY=...
|
||||
# LANGFUSE_HOST=https://cloud.langfuse.com
|
||||
|
||||
# ==================================
|
||||
# Optional: Authentication Providers
|
||||
# ==================================
|
||||
# GOOGLE_CLIENT_ID=...
|
||||
# GOOGLE_CLIENT_SECRET=...
|
||||
# GITHUB_CLIENT_ID=...
|
||||
# GITHUB_CLIENT_SECRET=...
|
||||
@@ -1 +0,0 @@
|
||||
ENDPOINTS=openAI,anthropic,custom
|
||||
@@ -1,37 +0,0 @@
|
||||
# Test Environment Configuration
|
||||
NODE_ENV=test
|
||||
|
||||
# MongoDB for tests (use in-memory or test container)
|
||||
MONGO_URI=mongodb://localhost:27017/HanzoChat_test
|
||||
|
||||
# Disable external services in tests
|
||||
MEILI_HOST=
|
||||
RAG_API_URL=
|
||||
|
||||
# Test user configuration
|
||||
ALLOW_REGISTRATION=true
|
||||
ALLOW_UNVERIFIED_EMAIL_LOGIN=true
|
||||
|
||||
# Disable rate limiting in tests
|
||||
RATE_LIMIT_ENABLED=false
|
||||
|
||||
# Use test keys
|
||||
CREDS_KEY=test-creds-key-32-chars-long-string
|
||||
CREDS_IV=test-creds-iv-16
|
||||
JWT_SECRET=test-jwt-secret-very-secure
|
||||
JWT_REFRESH_SECRET=test-jwt-refresh-secret
|
||||
|
||||
# Disable analytics in tests
|
||||
GOOGLE_ANALYTICS_ID=
|
||||
SENTRY_DSN=
|
||||
|
||||
# Test domain
|
||||
DOMAIN_CLIENT=http://localhost:3080
|
||||
DOMAIN_SERVER=http://localhost:3080
|
||||
|
||||
# Disable SSL in tests
|
||||
TRUST_PROXY=0
|
||||
|
||||
# Silent logging
|
||||
DEBUG_LOGGING=false
|
||||
CONSOLE_JSON=false
|
||||
@@ -1,46 +0,0 @@
|
||||
[flake8]
|
||||
ignore =
|
||||
# The following ignores can be removed when formatting using black
|
||||
W191,W291,W292,W293,W391,W504
|
||||
E101,E111,E114,E116,E117,E121,E122,E123,E124,E125,E126,E127,E128,E129,E131,
|
||||
E201,E202,E221,E222,E225,E226,E231,E241,E251,E252,E261,E265,E271,E272,E275,
|
||||
E301,E302,E303,E305,E306,
|
||||
# line break before binary operator
|
||||
W503,
|
||||
# inline comment should start with '# '
|
||||
E262,
|
||||
# too many leading '#' for block comment
|
||||
E266,
|
||||
# multiple imports on one line
|
||||
E401,
|
||||
# module level import not at top of file
|
||||
E402,
|
||||
# Line too long (82 > 79 characters)
|
||||
E501,
|
||||
# comparison to None should be 'if cond is None:'
|
||||
E711,
|
||||
# comparison to True should be 'if cond is True:' or 'if cond:'
|
||||
E712,
|
||||
# do not compare types, for exact checks use `is` / `is not`, for instance checks use `isinstance()`
|
||||
E721,
|
||||
# do not use bare 'except'
|
||||
E722,
|
||||
# x is imported but unused
|
||||
F401,
|
||||
# 'from . import *' used; unable to detect undefined names
|
||||
F403,
|
||||
# x may be undefined, or defined from star imports:
|
||||
F405,
|
||||
# f-string is missing placeholders
|
||||
F541,
|
||||
# dictionary key '' repeated with different values
|
||||
F601,
|
||||
# redefinition of unused x from line 123
|
||||
F811,
|
||||
# undefined name x
|
||||
F821,
|
||||
# local variable x is assigned to but never used
|
||||
F841,
|
||||
|
||||
# https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#flake8
|
||||
extend-ignore = E203
|
||||
@@ -1,10 +0,0 @@
|
||||
# Add the commit hash of any commit you want to ignore in `git blame` here.
|
||||
# One commit hash per line.
|
||||
#
|
||||
# The GitHub Blame UI will use this file automatically!
|
||||
#
|
||||
# Run this command to always ignore formatting commits in `git blame`
|
||||
# git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||
|
||||
# Update pydantic code to fix warnings (GH-3600)
|
||||
876840e9957bc7e9f7d6a2b58c4d7c53dad16481
|
||||
@@ -1 +0,0 @@
|
||||
*.ipynb linguist-vendored
|
||||
@@ -0,0 +1,106 @@
|
||||
# GitHub Branch Protection Rules
|
||||
|
||||
This document outlines the recommended branch protection rules for the Hanzo Chat repository.
|
||||
|
||||
## Main Branch Protection
|
||||
|
||||
Go to Settings → Branches → Add rule for `main`:
|
||||
|
||||
### Required Status Checks
|
||||
- [x] **Require status checks to pass before merging**
|
||||
- [x] Test Suite
|
||||
- [x] Code Quality
|
||||
- [x] Build Check
|
||||
- [x] Security Scan
|
||||
|
||||
### Required Reviews
|
||||
- [x] **Require pull request reviews before merging**
|
||||
- Required approving reviews: 1
|
||||
- [x] Dismiss stale pull request approvals when new commits are pushed
|
||||
- [x] Require review from CODEOWNERS
|
||||
|
||||
### Additional Settings
|
||||
- [x] **Require branches to be up to date before merging**
|
||||
- [x] **Include administrators**
|
||||
- [x] **Restrict who can push to matching branches**
|
||||
- Add teams/users who can merge to main
|
||||
|
||||
## Develop Branch Protection
|
||||
|
||||
Go to Settings → Branches → Add rule for `develop`:
|
||||
|
||||
### Required Status Checks
|
||||
- [x] **Require status checks to pass before merging**
|
||||
- [x] Test Suite
|
||||
- [x] Code Quality
|
||||
|
||||
### Required Reviews
|
||||
- [x] **Require pull request reviews before merging**
|
||||
- Required approving reviews: 1
|
||||
|
||||
## GitHub Actions Secrets
|
||||
|
||||
Add these secrets in Settings → Secrets and variables → Actions:
|
||||
|
||||
1. **CODECOV_TOKEN** - For coverage reporting (optional)
|
||||
2. **SNYK_TOKEN** - For security scanning (optional)
|
||||
3. **OPENAI_API_KEY** - For tests that require API access
|
||||
4. **ANTHROPIC_API_KEY** - For tests that require API access
|
||||
|
||||
## Recommended `.github/CODEOWNERS` file
|
||||
|
||||
```
|
||||
# Global owners
|
||||
* @hanzoai/core-team
|
||||
|
||||
# Frontend
|
||||
/client/ @hanzoai/frontend-team
|
||||
/packages/data-provider/ @hanzoai/frontend-team
|
||||
|
||||
# Backend
|
||||
/api/ @hanzoai/backend-team
|
||||
/packages/data-schemas/ @hanzoai/backend-team
|
||||
|
||||
# CI/CD
|
||||
/.github/ @hanzoai/devops-team
|
||||
```
|
||||
|
||||
## PR Merge Strategy
|
||||
|
||||
In Settings → General → Pull Requests:
|
||||
|
||||
- [x] Allow squash merging
|
||||
- [ ] Allow merge commits
|
||||
- [ ] Allow rebase merging
|
||||
- [x] Automatically delete head branches
|
||||
|
||||
## Status Checks Configuration
|
||||
|
||||
The following GitHub Actions workflows provide status checks:
|
||||
|
||||
1. **ci.yml** - Main CI pipeline
|
||||
- `Test Suite` - Runs all tests
|
||||
- `Code Quality` - Linting and formatting
|
||||
- `Security Scan` - Dependency scanning
|
||||
- `Build Check` - Ensures code builds
|
||||
|
||||
2. **pr-checks.yml** - PR-specific checks
|
||||
- `Client Tests` - Runs only on client changes
|
||||
- `API Tests` - Runs only on API changes
|
||||
- `Bundle Size Check` - Monitors client bundle size
|
||||
- `TypeScript Check` - Type checking
|
||||
|
||||
## Monitoring
|
||||
|
||||
Set up notifications for:
|
||||
- Failed CI runs on main branch
|
||||
- Security vulnerabilities
|
||||
- Dependency updates
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Never push directly to main** - Always use pull requests
|
||||
2. **Keep PRs small and focused** - Easier to review and test
|
||||
3. **Write descriptive PR titles** - They become part of the git history
|
||||
4. **Update tests** - All new features should include tests
|
||||
5. **Monitor CI times** - Keep test suites fast (< 10 minutes)
|
||||
@@ -60,7 +60,7 @@ representative at an online or offline event.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement here on GitHub or
|
||||
on the official GitHub repository.
|
||||
on the official [Discord Server](https://discord.gg/hanzoai).
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
|
||||
+46
-39
@@ -4,11 +4,11 @@ Thank you to all the contributors who have helped make this project possible! We
|
||||
|
||||
## Contributing Guidelines
|
||||
|
||||
If the feature you would like to contribute has not already received prior approval from the project maintainers, please submit a request in the [Feature Requests & Suggestions category](https://github.com/hanzoai/chat/discussions/new?category=feature-requests-suggestions) of the discussions board before beginning work on it. The requests should include specific implementation details, including areas of the application that will be affected by the change (including designs if applicable), and any other relevant information that might be required for a speedy review. However, proposals are not required for small changes, bug fixes, or documentation improvements. Small changes and bug fixes should be tied to an [issue](https://github.com/hanzoai/chat/issues) and included in the corresponding pull request for tracking purposes.
|
||||
If the feature you would like to contribute has not already received prior approval from the project maintainers (i.e., the feature is currently on the [roadmap](https://github.com/users/hanzoai/projects/2)), please submit a request in the [Feature Requests & Suggestions category](https://github.com/hanzoai/chat/discussions/new?category=feature-requests-suggestions) of the discussions board before beginning work on it. The requests should include specific implementation details, including areas of the application that will be affected by the change (including designs if applicable), and any other relevant information that might be required for a speedy review. However, proposals are not required for small changes, bug fixes, or documentation improvements. Small changes and bug fixes should be tied to an [issue](https://github.com/hanzoai/chat/issues) and included in the corresponding pull request for tracking purposes.
|
||||
|
||||
Please note that a pull request involving a feature that has not been reviewed and approved by the project maintainers may be rejected. We appreciate your understanding and cooperation.
|
||||
|
||||
If you would like to discuss the changes you wish to make, open an issue or discussion on [GitHub](https://github.com/hanzoai/chat), where you can engage with other contributors and seek guidance from the community.
|
||||
If you would like to discuss the changes you wish to make, join our [Discord community](https://discord.gg/hanzoai), where you can engage with other contributors and seek guidance from the community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
@@ -26,29 +26,33 @@ Project maintainers have the right and responsibility to remove, edit, or reject
|
||||
|
||||
## 1. Development Setup
|
||||
|
||||
1. Use Node.js v20.19.0+ or ^22.12.0 or >= 23.0.0.
|
||||
2. Run `npm run smart-reinstall` to install dependencies (uses Turborepo). Use `npm run reinstall` for a clean install, or `npm ci` for a fresh lockfile-based install.
|
||||
3. Build all compiled code: `npm run build`.
|
||||
4. Setup and run unit tests:
|
||||
1. Use Node.JS 20.x.
|
||||
2. Install typescript globally: `npm i -g typescript`.
|
||||
3. Run `npm ci` to install dependencies.
|
||||
4. Build the data provider: `npm run build:data-provider`.
|
||||
5. Build data schemas: `npm run build:data-schemas`.
|
||||
6. Build API methods: `npm run build:api`.
|
||||
7. Setup and run unit tests:
|
||||
- Copy `.env.test`: `cp api/test/.env.test.example api/test/.env.test`.
|
||||
- Run backend unit tests: `npm run test:api`.
|
||||
- Run frontend unit tests: `npm run test:client`.
|
||||
5. Setup and run integration tests:
|
||||
8. Setup and run integration tests:
|
||||
- Build client: `cd client && npm run build`.
|
||||
- Create `.env`: `cp .env.example .env`.
|
||||
- Install [MongoDB Community Edition](https://www.mongodb.com/docs/manual/administration/install-community/), ensure that `mongosh` connects to your local instance.
|
||||
- Run: `npx install playwright`, then `npx playwright install`.
|
||||
- Copy `config.local`: `cp e2e/config.local.example.ts e2e/config.local.ts`.
|
||||
- Copy `librechat.yaml`: `cp librechat.example.yaml librechat.yaml`.
|
||||
- Copy `chat.yaml`: `cp chat.example.yaml chat.yaml`.
|
||||
- Run: `npm run e2e`.
|
||||
|
||||
## 2. Development Notes
|
||||
|
||||
1. Before starting work, make sure your main branch has the latest commits with `npm run update`.
|
||||
2. Run linting command to find errors: `npm run lint`. Alternatively, ensure husky pre-commit checks are functioning.
|
||||
3. Run linting command to find errors: `npm run lint`. Alternatively, ensure husky pre-commit checks are functioning.
|
||||
3. After your changes, reinstall packages in your current branch using `npm run reinstall` and ensure everything still works.
|
||||
- Restart the ESLint server ("ESLint: Restart ESLint Server" in VS Code command bar) and your IDE after reinstalling or updating.
|
||||
4. Clear web app localStorage and cookies before and after changes.
|
||||
5. To check for introduced errors, build all compiled code: `npm run build`.
|
||||
5. For frontend changes, compile typescript before and after changes to check for introduced errors: `cd client && npm run build`.
|
||||
6. Run backend unit tests: `npm run test:api`.
|
||||
7. Run frontend unit tests: `npm run test:client`.
|
||||
8. Run integration tests: `npm run e2e`.
|
||||
@@ -114,45 +118,48 @@ Apply the following naming conventions to branches, labels, and other Git-relate
|
||||
- **JS/TS:** Directories and file names: Descriptive and camelCase. First letter uppercased for React files (e.g., `helperFunction.ts, ReactComponent.tsx`).
|
||||
- **Docs:** Directories and file names: Descriptive and snake_case (e.g., `config_files.md`).
|
||||
|
||||
## 7. Coding Standards
|
||||
|
||||
For detailed coding conventions, workspace boundaries, and architecture guidance, refer to the [`AGENTS.md`](../AGENTS.md) file at the project root. It covers code style, type safety, import ordering, iteration/performance expectations, frontend rules, testing, and development commands.
|
||||
|
||||
## 8. TypeScript Conversion
|
||||
## 7. TypeScript Conversion
|
||||
|
||||
1. **Original State**: The project was initially developed entirely in JavaScript (JS).
|
||||
|
||||
2. **Frontend**: Fully transitioned to TypeScript.
|
||||
2. **Frontend Transition**:
|
||||
- We are in the process of transitioning the frontend from JS to TypeScript (TS).
|
||||
- The transition is nearing completion.
|
||||
- This conversion is feasible due to React's capability to intermix JS and TS prior to code compilation. It's standard practice to compile/bundle the code in such scenarios.
|
||||
|
||||
3. **Backend**:
|
||||
- The legacy Express.js server remains in `/api` as JavaScript.
|
||||
- All new backend code is written in TypeScript under `/packages/api`, which is compiled and consumed by `/api`.
|
||||
- Shared database logic lives in `/packages/data-schemas` (TypeScript).
|
||||
- Shared frontend/backend API types and services live in `/packages/data-provider` (TypeScript).
|
||||
- Minimize direct changes to `/api`; prefer adding TypeScript code to `/packages/api` and importing it.
|
||||
3. **Backend Considerations**:
|
||||
- Transitioning the backend to TypeScript would be a more intricate process, especially for an established Express.js server.
|
||||
|
||||
- **Options for Transition**:
|
||||
- **Single Phase Overhaul**: This involves converting the entire backend to TypeScript in one go. It's the most straightforward approach but can be disruptive, especially for larger codebases.
|
||||
|
||||
- **Incremental Transition**: Convert parts of the backend progressively. This can be done by:
|
||||
- Maintaining a separate directory for TypeScript files.
|
||||
- Gradually migrating and testing individual modules or routes.
|
||||
- Using a build tool like `tsc` to compile TypeScript files independently until the entire transition is complete.
|
||||
|
||||
- **Compilation Considerations**:
|
||||
- Introducing a compilation step for the server is an option. This would involve using tools like `ts-node` for development and `tsc` for production builds.
|
||||
- However, this is not a conventional approach for Express.js servers and could introduce added complexity, especially in terms of build and deployment processes.
|
||||
|
||||
- **Current Stance**: At present, this backend transition is of lower priority and might not be pursued.
|
||||
|
||||
## 9. Module Import Conventions
|
||||
## 8. Module Import Conventions
|
||||
|
||||
Imports are organized into three sections (in order):
|
||||
- `npm` packages first,
|
||||
- from shortest line (top) to longest (bottom)
|
||||
|
||||
1. **Package imports** — sorted from shortest to longest line length.
|
||||
- `react` is always the first import.
|
||||
- Multi-line (stacked) imports count their total character length across all lines for sorting.
|
||||
- Followed by typescript types (pertains to data-provider and client workspaces)
|
||||
- longest line (top) to shortest (bottom)
|
||||
- types from package come first
|
||||
|
||||
2. **`import type` imports** — sorted from longest to shortest line length.
|
||||
- Package type imports come first, then local type imports.
|
||||
- Line length sorting resets between the package and local sub-groups.
|
||||
- Lastly, local imports
|
||||
- longest line (top) to shortest (bottom)
|
||||
- imports with alias `~` treated the same as relative import with respect to line length
|
||||
|
||||
3. **Local/project imports** — sorted from longest to shortest line length.
|
||||
- Multi-line (stacked) imports count their total character length across all lines for sorting.
|
||||
- Imports with alias `~` are treated the same as relative imports with respect to line length.
|
||||
---
|
||||
|
||||
- Consolidate value imports from the same module as much as possible.
|
||||
- Always use standalone `import type { ... }` for type imports; never use inline `type` keyword inside value imports (e.g., `import { Foo, type Bar }` is wrong).
|
||||
|
||||
**Note:** ESLint will automatically enforce these import conventions when you run `npm run lint --fix` or through pre-commit hooks.
|
||||
|
||||
For the full set of coding standards, see [`AGENTS.md`](../AGENTS.md).
|
||||
Please ensure that you adapt this summary to fit the specific context and nuances of your project.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
github: [hanzoai]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
@@ -10,4 +10,4 @@ liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: https://buy.stripe.com/9AQ03Kd3P91o0Q8bIS
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
|
||||
@@ -29,7 +29,7 @@ body:
|
||||
description: |
|
||||
If using Docker, please run and provide the output of:
|
||||
```bash
|
||||
docker images | grep hanzo
|
||||
docker images | grep hanzochat
|
||||
```
|
||||
|
||||
If running from source, please run and provide the output of:
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
name: Locize Translation Access Request
|
||||
description: Request access to an additional language in Locize for Hanzo Chat translations.
|
||||
description: Request access to an additional language in Locize for chat translations.
|
||||
title: "Locize Access Request: "
|
||||
labels: ["🌍 i18n", "🔑 access request"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for your interest in contributing to Hanzo Chat translations!
|
||||
Thank you for your interest in contributing to chat translations!
|
||||
Please fill out the form below to request access to an additional language in **Locize**.
|
||||
|
||||
**Note:** Ensure that the requested language is supported before submitting your request.
|
||||
**🔗 Available Languages:** [View the list here](https://docs.hanzo.ai/chat/translation)
|
||||
|
||||
**📌 Note:** Ensure that the requested language is supported before submitting your request.
|
||||
- type: input
|
||||
id: account_name
|
||||
attributes:
|
||||
@@ -23,8 +25,10 @@ body:
|
||||
attributes:
|
||||
label: Language Code (ISO 639-1)
|
||||
description: |
|
||||
Enter the **ISO 639-1** language code for the language you want to translate into.
|
||||
Enter the **ISO 639-1** language code for the language you want to translate into.
|
||||
Example: `es` for Spanish, `zh-Hant` for Traditional Chinese.
|
||||
|
||||
**🔗 Reference:** [Available Languages](https://docs.hanzo.ai/chat/translation)
|
||||
placeholder: e.g., es
|
||||
validations:
|
||||
required: true
|
||||
@@ -34,5 +38,5 @@ body:
|
||||
label: Agreement
|
||||
description: By submitting this request, you confirm that you will contribute responsibly and adhere to the project guidelines.
|
||||
options:
|
||||
- label: I agree to use my access solely for contributing to Hanzo Chat translations.
|
||||
required: true
|
||||
- label: I agree to use my access solely for contributing to chat translations.
|
||||
required: true
|
||||
@@ -1,5 +1,5 @@
|
||||
name: New Language Request
|
||||
description: Request to add a new language for Hanzo Chat translations.
|
||||
description: Request to add a new language for chat translations.
|
||||
title: "New Language Request: "
|
||||
labels: ["✨ enhancement", "🌍 i18n"]
|
||||
body:
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
title: "[Bug]: "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Also tell us, what did you expect to happen?
|
||||
placeholder: Tell us what you see!
|
||||
value: "A bug happened!"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
- type: dropdown
|
||||
id: ml-ops-team
|
||||
attributes:
|
||||
label: Are you a ML Ops Team?
|
||||
description: This helps us prioritize your requests correctly
|
||||
options:
|
||||
- "No"
|
||||
- "Yes"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: What LiteLLM version are you on ?
|
||||
placeholder: v1.53.1
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: contact
|
||||
attributes:
|
||||
label: Twitter / LinkedIn details
|
||||
description: We announce new features on Twitter + LinkedIn. If this issue leads to an announcement, and you'd like a mention, we'll gladly shout you out!
|
||||
placeholder: ex. @krrish_dh / https://www.linkedin.com/in/krish-d/
|
||||
validations:
|
||||
required: false
|
||||
@@ -1,8 +0,0 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Schedule Demo
|
||||
url: https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat
|
||||
about: Speak directly with Krrish and Ishaan, the founders, to discuss issues, share feedback, or explore improvements for LiteLLM
|
||||
- name: Discord
|
||||
url: https://discord.com/invite/wuPM9dRgDw
|
||||
about: Join 250+ LiteLLM community members!
|
||||
@@ -1,42 +0,0 @@
|
||||
name: 🚀 Feature Request
|
||||
description: Submit a proposal/request for a new LiteLLM feature.
|
||||
title: "[Feature]: "
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for making LiteLLM better!
|
||||
- type: textarea
|
||||
id: the-feature
|
||||
attributes:
|
||||
label: The Feature
|
||||
description: A clear and concise description of the feature proposal
|
||||
placeholder: Tell us what you want!
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: motivation
|
||||
attributes:
|
||||
label: Motivation, pitch
|
||||
description: Please outline the motivation for the proposal. Is your feature request related to a specific problem? e.g., "I'm working on X and would like Y to be possible". If this is related to another GitHub issue, please link here too.
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: hiring-interest
|
||||
attributes:
|
||||
label: LiteLLM is hiring a founding backend engineer, are you interested in joining us and shipping to all our users?
|
||||
description: If yes, apply here - https://www.ycombinator.com/companies/litellm/jobs/6uvoBp3-founding-backend-engineer
|
||||
options:
|
||||
- "No"
|
||||
- "Yes"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: contact
|
||||
attributes:
|
||||
label: Twitter / LinkedIn details
|
||||
description: We announce new features on Twitter + LinkedIn. When this is announced, and you'd like a mention, we'll gladly shout you out!
|
||||
placeholder: ex. @krrish_dh / https://www.linkedin.com/in/krish-d/
|
||||
validations:
|
||||
required: false
|
||||
+10
-8
@@ -1,8 +1,8 @@
|
||||
# Security Policy
|
||||
|
||||
At Hanzo, we prioritize the security of our project and value the contributions of security researchers in helping us improve the security of our codebase. If you discover a security vulnerability within our project, we appreciate your responsible disclosure. Please follow the guidelines below to report any vulnerabilities to us:
|
||||
At chat, we prioritize the security of our project and value the contributions of security researchers in helping us improve the security of our codebase. If you discover a security vulnerability within our project, we appreciate your responsible disclosure. Please follow the guidelines below to report any vulnerabilities to us:
|
||||
|
||||
**Note: Only report sensitive vulnerability details via the appropriate private communication channels mentioned below. Public channels, such as GitHub issues, should be used for initiating contact and establishing private communication channels.**
|
||||
**Note: Only report sensitive vulnerability details via the appropriate private communication channels mentioned below. Public channels, such as GitHub issues and Discord, should be used for initiating contact and establishing private communication channels.**
|
||||
|
||||
## Communication Channels
|
||||
|
||||
@@ -12,7 +12,7 @@ When reporting a security vulnerability, you have the following options to reach
|
||||
|
||||
- **Option 2: GitHub Issues**: You can initiate first contact via GitHub Issues. However, please note that initial contact through GitHub Issues should not include any sensitive details.
|
||||
|
||||
- **Option 3: Email**: You can reach us at security@hanzo.ai for security-related reports.
|
||||
- **Option 3: Discord Server**: You can join our [Discord community](https://discord.gg/hanzoai) and initiate first contact in the `#issues` channel. However, please ensure that initial contact through Discord does not include any sensitive details.
|
||||
|
||||
_After the initial contact, we will establish a private communication channel for further discussion._
|
||||
|
||||
@@ -26,10 +26,10 @@ We strive to acknowledge vulnerability reports within 72 hours and will keep you
|
||||
|
||||
## Security Updates and Patching
|
||||
|
||||
We are committed to maintaining the security of our open-source project, Hanzo Chat, and promptly addressing any identified vulnerabilities. To ensure the security of our project, we adhere to the following practices:
|
||||
We are committed to maintaining the security of our open-source project, chat, and promptly addressing any identified vulnerabilities. To ensure the security of our project, we adhere to the following practices:
|
||||
|
||||
- We prioritize security updates for the current major release of our software.
|
||||
- We actively monitor the GitHub Security Advisory system for any vulnerability reports.
|
||||
- We actively monitor the GitHub Security Advisory system and the `#issues` channel on Discord for any vulnerability reports.
|
||||
- We promptly review and validate reported vulnerabilities and take appropriate actions to address them.
|
||||
- We release security patches and updates in a timely manner to mitigate any identified vulnerabilities.
|
||||
|
||||
@@ -39,11 +39,11 @@ Please note that as a security-conscious community, we may not always disclose d
|
||||
|
||||
This security policy applies to the following GitHub repository:
|
||||
|
||||
- Repository: [Hanzo Chat](https://github.com/hanzoai/chat)
|
||||
- Repository: [chat](https://github.com/hanzoai/chat)
|
||||
|
||||
## Contact
|
||||
|
||||
If you have any questions or concerns regarding the security of our project, please reach out to us at security@hanzo.ai or by [opening an issue](https://github.com/hanzoai/chat/issues/new) on GitHub. Please note that the response time may vary depending on the nature and severity of the inquiry.
|
||||
If you have any questions or concerns regarding the security of our project, please join our [Discord community](https://discord.gg/hanzoai) and report them in the appropriate channel. You can also reach out to us by [opening an issue](https://github.com/hanzoai/chat/issues/new) on GitHub. Please note that the response time may vary depending on the nature and severity of the inquiry.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
@@ -51,7 +51,9 @@ We would like to express our gratitude to the security researchers and community
|
||||
|
||||
## Bug Bounty Program
|
||||
|
||||
We currently do not have a bug bounty program in place. However, we welcome and appreciate any security-related contributions through pull requests (PRs) that address vulnerabilities in our codebase. We believe in the power of collaboration to improve the security of our project and invite you to join us in making it more robust.
|
||||
We currently do not have a bug bounty program in place. However, we welcome and appreciate any
|
||||
|
||||
security-related contributions through pull requests (PRs) that address vulnerabilities in our codebase. We believe in the power of collaboration to improve the security of our project and invite you to join us in making it more robust.
|
||||
|
||||
**Reference**
|
||||
- https://cheatsheetseries.owasp.org/cheatsheets/Vulnerability_Disclosure_Cheat_Sheet.html
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
name: Helm OCI Chart Releaser
|
||||
description: Push Helm charts to OCI-based (Docker) registries
|
||||
author: sergeyshaykhullin
|
||||
branding:
|
||||
color: yellow
|
||||
icon: upload-cloud
|
||||
inputs:
|
||||
name:
|
||||
required: true
|
||||
description: Chart name
|
||||
repository:
|
||||
required: true
|
||||
description: Chart repository name
|
||||
tag:
|
||||
required: true
|
||||
description: Chart version
|
||||
app_version:
|
||||
required: true
|
||||
description: App version
|
||||
path:
|
||||
required: false
|
||||
description: Chart path (Default 'charts/{name}')
|
||||
registry:
|
||||
required: true
|
||||
description: OCI registry
|
||||
registry_username:
|
||||
required: true
|
||||
description: OCI registry username
|
||||
registry_password:
|
||||
required: true
|
||||
description: OCI registry password
|
||||
update_dependencies:
|
||||
required: false
|
||||
default: 'false'
|
||||
description: Update chart dependencies before packaging (Default 'false')
|
||||
outputs:
|
||||
image:
|
||||
value: ${{ steps.output.outputs.image }}
|
||||
description: Chart image (Default '{registry}/{repository}/{image}:{tag}')
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Helm | Login
|
||||
shell: bash
|
||||
run: echo ${{ inputs.registry_password }} | helm registry login -u ${{ inputs.registry_username }} --password-stdin ${{ inputs.registry }}
|
||||
env:
|
||||
HELM_EXPERIMENTAL_OCI: '1'
|
||||
|
||||
- name: Helm | Dependency
|
||||
if: inputs.update_dependencies == 'true'
|
||||
shell: bash
|
||||
run: helm dependency update ${{ inputs.path == null && format('{0}/{1}', 'charts', inputs.name) || inputs.path }}
|
||||
env:
|
||||
HELM_EXPERIMENTAL_OCI: '1'
|
||||
|
||||
- name: Helm | Package
|
||||
shell: bash
|
||||
run: helm package ${{ inputs.path == null && format('{0}/{1}', 'charts', inputs.name) || inputs.path }} --version ${{ inputs.tag }} --app-version ${{ inputs.app_version }}
|
||||
env:
|
||||
HELM_EXPERIMENTAL_OCI: '1'
|
||||
|
||||
- name: Helm | Push
|
||||
shell: bash
|
||||
run: helm push ${{ inputs.name }}-${{ inputs.tag }}.tgz oci://${{ inputs.registry }}/${{ inputs.repository }}
|
||||
env:
|
||||
HELM_EXPERIMENTAL_OCI: '1'
|
||||
|
||||
- name: Helm | Logout
|
||||
shell: bash
|
||||
run: helm registry logout ${{ inputs.registry }}
|
||||
env:
|
||||
HELM_EXPERIMENTAL_OCI: '1'
|
||||
|
||||
- name: Helm | Output
|
||||
id: output
|
||||
shell: bash
|
||||
run: echo "image=${{ inputs.registry }}/${{ inputs.repository }}/${{ inputs.name }}:${{ inputs.tag }}" >> $GITHUB_OUTPUT
|
||||
@@ -0,0 +1,51 @@
|
||||
# git-cliff configuration for changelog generation
|
||||
|
||||
[changelog]
|
||||
header = """
|
||||
# Changelog\n
|
||||
All notable changes to this project will be documented in this file.\n
|
||||
"""
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else %}\
|
||||
## [unreleased]
|
||||
{% endif %}\
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | striptags | trim | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
|
||||
{% if commit.breaking %}[**breaking**] {% endif %}\
|
||||
{{ commit.message | upper_first }}
|
||||
{%- endfor %}
|
||||
{% endfor %}\n
|
||||
"""
|
||||
footer = """
|
||||
<!-- generated by git-cliff -->
|
||||
"""
|
||||
trim = true
|
||||
|
||||
[git]
|
||||
conventional_commits = true
|
||||
filter_unconventional = true
|
||||
split_commits = false
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "<!-- 0 -->⛰️ Features" },
|
||||
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
|
||||
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
|
||||
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
|
||||
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
|
||||
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
|
||||
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
|
||||
{ message = "^chore\\(release\\): prepare for", skip = true },
|
||||
{ message = "^chore\\(deps.*\\)", skip = true },
|
||||
{ message = "^chore\\(pr\\)", skip = true },
|
||||
{ message = "^chore\\(pull\\)", skip = true },
|
||||
{ message = "^chore|^ci", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
|
||||
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
|
||||
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
|
||||
]
|
||||
protect_breaking_commits = false
|
||||
filter_commits = false
|
||||
topo_order = false
|
||||
sort_commits = "oldest"
|
||||
@@ -1,10 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -1,9 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="640" viewBox="0 0 1280 640" role="img" aria-label="chat">
|
||||
<rect width="1280" height="640" fill="#0A0A0A"/>
|
||||
<svg x="96" y="215" width="210" height="210" viewBox="0 0 67 67"><path d="M22.21 67V44.6369H0V67H22.21Z" fill="#fff"/><path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="#fff"/><path d="M22.21 0H0V22.3184H22.21V0Z" fill="#fff"/><path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="#fff"/><path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="#fff"/></svg>
|
||||
<text x="378" y="276" font-family="Inter,system-ui,-apple-system,sans-serif" font-size="78" font-weight="800" letter-spacing="-2" fill="#ffffff">chat</text>
|
||||
<text x="378" y="322" font-family="Inter,system-ui,sans-serif" font-size="30" fill="#ffffff" opacity=".66">AI chat with MCP integration and multi-provider support</text>
|
||||
<rect x="378" y="338" width="806" height="3" rx="1.5" fill="#ffffff" opacity=".9"/>
|
||||
<text x="378" y="390" font-family="Inter,system-ui,sans-serif" font-size="24" font-weight="600" fill="#ffffff" opacity=".5">github.com/hanzoai</text>
|
||||
<text x="1184" y="390" text-anchor="end" font-family="Inter,system-ui,sans-serif" font-size="24" font-weight="600" fill="#ffffff" opacity=".5">hanzo.ai</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1,33 +1,41 @@
|
||||
## Title
|
||||
|
||||
<!-- e.g. "Implement user authentication feature" -->
|
||||
|
||||
## Relevant issues
|
||||
|
||||
<!-- e.g. "Fixes #000" -->
|
||||
|
||||
## Pre-Submission checklist
|
||||
|
||||
**Please complete all items before asking a LiteLLM maintainer to review your PR**
|
||||
|
||||
- [ ] I have Added testing in the [`tests/litellm/`](https://github.com/BerriAI/litellm/tree/main/tests/litellm) directory, **Adding at least 1 test is a hard requirement** - [see details](https://docs.litellm.ai/docs/extras/contributing_code)
|
||||
- [ ] I have added a screenshot of my new test passing locally
|
||||
- [ ] My PR passes all unit tests on [`make test-unit`](https://docs.litellm.ai/docs/extras/contributing_code)
|
||||
- [ ] My PR's scope is as isolated as possible, it only solves 1 specific problem
|
||||
|
||||
|
||||
## Type
|
||||
|
||||
<!-- Select the type of Pull Request -->
|
||||
<!-- Keep only the necessary ones -->
|
||||
|
||||
🆕 New Feature
|
||||
🐛 Bug Fix
|
||||
🧹 Refactoring
|
||||
📖 Documentation
|
||||
🚄 Infrastructure
|
||||
✅ Test
|
||||
|
||||
## Changes
|
||||
|
||||
|
||||
# Pull Request Template
|
||||
|
||||
⚠️ Before Submitting a PR, Please Review:
|
||||
- Please ensure that you have thoroughly read and understood the [Contributing Docs](https://github.com/hanzoai/chat/blob/main/.github/CONTRIBUTING.md) before submitting your Pull Request.
|
||||
|
||||
⚠️ Documentation Updates Notice:
|
||||
- Kindly note that documentation updates are managed in this repository: [hanzo.ai](https://github.com/hanzoai/docs)
|
||||
|
||||
## Summary
|
||||
|
||||
Please provide a brief summary of your changes and the related issue. Include any motivation and context that is relevant to your changes. If there are any dependencies necessary for your changes, please list them here.
|
||||
|
||||
## Change Type
|
||||
|
||||
Please delete any irrelevant options.
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] This change requires a documentation update
|
||||
- [ ] Translation update
|
||||
|
||||
## Testing
|
||||
|
||||
Please describe your test process and include instructions so that we can reproduce your test. If there are any important variables for your testing configuration, list them here.
|
||||
|
||||
### **Test Configuration**:
|
||||
|
||||
## Checklist
|
||||
|
||||
Please delete any irrelevant options.
|
||||
|
||||
- [ ] My code adheres to this project's style guidelines
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have commented in any complex areas of my code
|
||||
- [ ] I have made pertinent documentation changes
|
||||
- [ ] My changes do not introduce new warnings
|
||||
- [ ] I have written tests demonstrating that my changes are effective or that my feature works
|
||||
- [ ] Local unit tests pass with my changes
|
||||
- [ ] Any changes dependent on mine have been merged and published in downstream modules.
|
||||
- [ ] A pull request for updating the documentation has been submitted.
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CHART_PATH="${CHART_PATH:-helm/librechat/Chart.yaml}"
|
||||
DEFAULT_BRANCH="${DEFAULT_BRANCH:-main}"
|
||||
BASE_REF="${BASE_REF:-refs/remotes/origin/${DEFAULT_BRANCH}}"
|
||||
BACKFILL_FROM_VERSION="${BACKFILL_FROM_VERSION:-1.9.0}"
|
||||
PUSH_TAGS="${PUSH_TAGS:-false}"
|
||||
TAG_PREFIX="${TAG_PREFIX:-chart-}"
|
||||
GITHUB_SERVER_URL="${GITHUB_SERVER_URL:-https://github.com}"
|
||||
DISPATCH_WORKFLOW="${DISPATCH_WORKFLOW:-}"
|
||||
RELEASE_EXISTING_TAG="${RELEASE_EXISTING_TAG:-}"
|
||||
SEMVER_REGEX='^(0|[1-9][0-9]*)[.](0|[1-9][0-9]*)[.](0|[1-9][0-9]*)(-[0-9A-Za-z-]+([.][0-9A-Za-z-]+)*)?([+][0-9A-Za-z-]+([.][0-9A-Za-z-]+)*)?$'
|
||||
|
||||
fail() {
|
||||
printf '::error::%s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
git_auth_header() {
|
||||
token="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 | tr -d '\n')"
|
||||
printf 'AUTHORIZATION: basic %s' "$token"
|
||||
}
|
||||
|
||||
git_with_auth() {
|
||||
if [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
git -c "http.extraheader=$(git_auth_header)" "$@"
|
||||
return
|
||||
fi
|
||||
|
||||
git "$@"
|
||||
}
|
||||
|
||||
dispatch_release() {
|
||||
tag="$1"
|
||||
|
||||
if [ -z "$DISPATCH_WORKFLOW" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -z "${GITHUB_REPOSITORY:-}" ]; then
|
||||
fail "GITHUB_REPOSITORY is required to dispatch ${DISPATCH_WORKFLOW}"
|
||||
fi
|
||||
|
||||
if [[ ! "$GITHUB_REPOSITORY" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then
|
||||
fail "Unexpected repository name: ${GITHUB_REPOSITORY}"
|
||||
fi
|
||||
|
||||
if [[ ! "$DISPATCH_WORKFLOW" =~ ^[A-Za-z0-9_.-]+[.]ya?ml$ ]]; then
|
||||
fail "Unexpected workflow file: ${DISPATCH_WORKFLOW}"
|
||||
fi
|
||||
|
||||
token="${GH_TOKEN:-${GITHUB_TOKEN:-}}"
|
||||
if [ -z "$token" ]; then
|
||||
fail "GH_TOKEN or GITHUB_TOKEN is required to dispatch ${DISPATCH_WORKFLOW}"
|
||||
fi
|
||||
|
||||
command -v gh >/dev/null ||
|
||||
fail "GitHub CLI is required to dispatch ${DISPATCH_WORKFLOW}"
|
||||
|
||||
GH_TOKEN="$token" gh workflow run "$DISPATCH_WORKFLOW" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--ref "$DEFAULT_BRANCH" \
|
||||
-f "chart_tag=${tag}"
|
||||
}
|
||||
|
||||
version_less_than() {
|
||||
left="${1%%[-+]*}"
|
||||
right="${2%%[-+]*}"
|
||||
|
||||
IFS=. read -r left_major left_minor left_patch <<<"$left"
|
||||
IFS=. read -r right_major right_minor right_patch <<<"$right"
|
||||
|
||||
if (( left_major != right_major )); then
|
||||
(( left_major < right_major ))
|
||||
return
|
||||
fi
|
||||
|
||||
if (( left_minor != right_minor )); then
|
||||
(( left_minor < right_minor ))
|
||||
return
|
||||
fi
|
||||
|
||||
(( left_patch < right_patch ))
|
||||
}
|
||||
|
||||
validate_chart_tag() {
|
||||
tag="$1"
|
||||
version="${tag#${TAG_PREFIX}}"
|
||||
|
||||
git check-ref-format "refs/tags/${tag}" >/dev/null ||
|
||||
fail "Refusing to use invalid tag ${tag}"
|
||||
|
||||
if [[ "$tag" != "${TAG_PREFIX}"* || ! "$version" =~ $SEMVER_REGEX ]]; then
|
||||
fail "Chart tags must use the form ${TAG_PREFIX}<semver>, for example ${TAG_PREFIX}2.0.5"
|
||||
fi
|
||||
}
|
||||
|
||||
dispatch_existing_tag() {
|
||||
tag="$1"
|
||||
|
||||
if [ -z "$tag" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
validate_chart_tag "$tag"
|
||||
|
||||
if [ "$PUSH_TAGS" != "true" ]; then
|
||||
printf 'Would dispatch release workflow for existing %s.\n' "$tag"
|
||||
return
|
||||
fi
|
||||
|
||||
if ! git_with_auth ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
|
||||
fail "Remote tag ${tag} does not exist"
|
||||
fi
|
||||
|
||||
printf 'Dispatching release workflow for existing %s.\n' "$tag"
|
||||
dispatch_release "$tag"
|
||||
}
|
||||
|
||||
chart_version_at() {
|
||||
git show "${1}:${CHART_PATH}" 2>/dev/null | awk '
|
||||
/^version:[[:space:]]*/ {
|
||||
value = $0
|
||||
sub(/^version:[[:space:]]*/, "", value)
|
||||
sub(/[[:space:]]*#.*/, "", value)
|
||||
gsub(/^[[:space:]"'\''"]+|[[:space:]"'\''"]+$/, "", value)
|
||||
print value
|
||||
exit
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
case "$PUSH_TAGS" in
|
||||
true | false) ;;
|
||||
*) fail "PUSH_TAGS must be true or false" ;;
|
||||
esac
|
||||
|
||||
if [[ ! "$BACKFILL_FROM_VERSION" =~ $SEMVER_REGEX ]]; then
|
||||
fail "BACKFILL_FROM_VERSION must be a valid SemVer value"
|
||||
fi
|
||||
|
||||
git rev-parse --verify "${BASE_REF}^{commit}" >/dev/null ||
|
||||
fail "Unable to resolve ${BASE_REF}; fetch ${DEFAULT_BRANCH} before running this script"
|
||||
|
||||
history_file="$(mktemp)"
|
||||
versions_file="$(mktemp)"
|
||||
seen_file="$(mktemp)"
|
||||
missing_file="$(mktemp)"
|
||||
cleanup() {
|
||||
rm -f "$history_file" "$versions_file" "$seen_file" "$missing_file"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
git log --first-parent --reverse --format=%H "$BASE_REF" -- "$CHART_PATH" >"$history_file"
|
||||
|
||||
if [ ! -s "$history_file" ]; then
|
||||
fail "No history found for ${CHART_PATH} on ${BASE_REF}"
|
||||
fi
|
||||
|
||||
while IFS= read -r commit; do
|
||||
version="$(chart_version_at "$commit")"
|
||||
|
||||
if [ -z "$version" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ ! "$version" =~ $SEMVER_REGEX ]]; then
|
||||
fail "${CHART_PATH} has invalid SemVer '${version}' at ${commit}"
|
||||
fi
|
||||
|
||||
if version_less_than "$version" "$BACKFILL_FROM_VERSION"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if grep -Fqx "$version" "$seen_file"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
printf '%s\n' "$version" >>"$seen_file"
|
||||
printf '%s\t%s\n' "$version" "$commit" >>"$versions_file"
|
||||
done <"$history_file"
|
||||
|
||||
if [ ! -s "$versions_file" ]; then
|
||||
fail "No chart versions found in ${CHART_PATH}"
|
||||
fi
|
||||
|
||||
while IFS="$(printf '\t')" read -r version commit; do
|
||||
tag="${TAG_PREFIX}${version}"
|
||||
|
||||
validate_chart_tag "$tag"
|
||||
|
||||
if git rev-parse --quiet --verify "refs/tags/${tag}" >/dev/null; then
|
||||
continue
|
||||
fi
|
||||
|
||||
printf '%s\t%s\n' "$tag" "$commit" >>"$missing_file"
|
||||
done <"$versions_file"
|
||||
|
||||
if [ ! -s "$missing_file" ]; then
|
||||
printf 'All chart versions on %s already have %s tags.\n' "$BASE_REF" "$TAG_PREFIX"
|
||||
dispatch_existing_tag "$RELEASE_EXISTING_TAG"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
while IFS="$(printf '\t')" read -r tag commit; do
|
||||
short_commit="$(git rev-parse --short "$commit")"
|
||||
|
||||
if [ "$PUSH_TAGS" != "true" ]; then
|
||||
printf 'Would create %s at %s.\n' "$tag" "$short_commit"
|
||||
continue
|
||||
fi
|
||||
|
||||
if git_with_auth ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
|
||||
printf 'Remote tag %s already exists; dispatching release workflow.\n' "$tag"
|
||||
dispatch_release "$tag"
|
||||
continue
|
||||
fi
|
||||
|
||||
git tag "$tag" "$commit"
|
||||
|
||||
if git_with_auth push origin "refs/tags/${tag}"; then
|
||||
printf 'Created %s at %s.\n' "$tag" "$short_commit"
|
||||
dispatch_release "$tag"
|
||||
continue
|
||||
fi
|
||||
|
||||
if git_with_auth ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
|
||||
printf 'Remote tag %s was created concurrently; dispatching release workflow.\n' "$tag"
|
||||
dispatch_release "$tag"
|
||||
continue
|
||||
fi
|
||||
|
||||
fail "Failed to push ${tag}"
|
||||
done <"$missing_file"
|
||||
|
||||
dispatch_existing_tag "$RELEASE_EXISTING_TAG"
|
||||
@@ -1,94 +0,0 @@
|
||||
AWSTemplateFormatVersion: '2010-09-09'
|
||||
Transform: AWS::Serverless-2016-10-31
|
||||
Description: >
|
||||
llmlite-service
|
||||
|
||||
SAM Template for llmlite-service
|
||||
|
||||
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
|
||||
Globals:
|
||||
Function:
|
||||
Timeout: 600
|
||||
MemorySize: 128
|
||||
Environment:
|
||||
Variables:
|
||||
WORKER_CONFIG: !Ref WorkerConfigParameter
|
||||
|
||||
Parameters:
|
||||
AliasParameter:
|
||||
Type: String
|
||||
Default: live
|
||||
WorkerConfigParameter:
|
||||
Type: String
|
||||
Description: Sample environment variable
|
||||
Default: '{"model": null, "alias": null, "api_base": null, "api_version": "2023-07-01-preview", "debug": false, "temperature": null, "max_tokens": null, "request_timeout": 600, "max_budget": null, "telemetry": true, "drop_params": false, "add_function_to_prompt": false, "headers": null, "save": false, "config": null, "use_queue": false}'
|
||||
|
||||
Resources:
|
||||
MyUrlFunctionPermissions:
|
||||
Type: AWS::Lambda::Permission
|
||||
Properties:
|
||||
FunctionName: !Ref URL
|
||||
Action: lambda:InvokeFunctionUrl
|
||||
Principal: "*"
|
||||
FunctionUrlAuthType: NONE
|
||||
|
||||
Function:
|
||||
Type: AWS::Serverless::Function
|
||||
Properties:
|
||||
FunctionName: !Sub "${AWS::StackName}-function"
|
||||
CodeUri: "./litellm"
|
||||
Handler: proxy/lambda.handler
|
||||
Runtime: python3.11
|
||||
AutoPublishAlias: !Ref AliasParameter
|
||||
Architectures:
|
||||
- x86_64
|
||||
DeploymentPreference:
|
||||
Type: AllAtOnce
|
||||
Alarms:
|
||||
- !Ref NewVersionErrorMetricGreaterThanZeroAlarm
|
||||
|
||||
NewVersionErrorMetricGreaterThanZeroAlarm:
|
||||
Type: "AWS::CloudWatch::Alarm"
|
||||
Properties:
|
||||
AlarmDescription: Lambda Function Error > 0
|
||||
ComparisonOperator: GreaterThanThreshold
|
||||
Dimensions:
|
||||
- Name: Resource
|
||||
Value: !Sub "${Function}:live"
|
||||
- Name: FunctionName
|
||||
Value: !Ref Function
|
||||
- Name: ExecutedVersion
|
||||
Value: !GetAtt Function.Version.Version
|
||||
EvaluationPeriods: 1
|
||||
Unit: Count
|
||||
MetricName: Errors
|
||||
Namespace: AWS/Lambda
|
||||
Period: 60
|
||||
Statistic: Sum
|
||||
Threshold: 0
|
||||
|
||||
URL:
|
||||
Type: AWS::Lambda::Url
|
||||
DependsOn: FunctionAliaslive
|
||||
Properties:
|
||||
AuthType: NONE
|
||||
Qualifier: live
|
||||
TargetFunctionArn: !GetAtt Function.Arn
|
||||
|
||||
Outputs:
|
||||
FunctionARN:
|
||||
Description: "Lambda Function ARN"
|
||||
Value: !GetAtt Function.Arn
|
||||
|
||||
FunctionUrl:
|
||||
Description: "Lambda Function URL Endpoint"
|
||||
Value:
|
||||
Fn::GetAtt: URL.FunctionUrl
|
||||
|
||||
FunctionVersion:
|
||||
Description: "Lambda Function Version"
|
||||
Value: !GetAtt Function.Version.Version
|
||||
|
||||
FunctionNewAlarmARN:
|
||||
Description: "Lambda Function New Alarm ARN"
|
||||
Value: !GetAtt NewVersionErrorMetricGreaterThanZeroAlarm.Arn
|
||||
@@ -13,13 +13,13 @@ on:
|
||||
|
||||
jobs:
|
||||
axe-linter:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'hanzoai/chat') ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.run_workflow == 'true')
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dequelabs/axe-linter-action@v1
|
||||
with:
|
||||
api_key: ${{ secrets.AXE_LINTER_API_KEY }}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
name: Updates model_prices_and_context_window.json and Create Pull Request
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * 0" # Run every Sundays at midnight
|
||||
#- cron: "0 0 * * *" # Run daily at midnight
|
||||
|
||||
jobs:
|
||||
auto_update_price_and_context_window:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
pip install aiohttp
|
||||
- name: Update JSON Data
|
||||
run: |
|
||||
python ".github/workflows/auto_update_price_and_context_window_file.py"
|
||||
- name: Create Pull Request
|
||||
run: |
|
||||
git add model_prices_and_context_window.json
|
||||
git commit -m "Update model_prices_and_context_window.json file: $(date +'%Y-%m-%d')"
|
||||
gh pr create --title "Update model_prices_and_context_window.json file" \
|
||||
--body "Automated update for model_prices_and_context_window.json" \
|
||||
--head auto-update-price-and-context-window-$(date +'%Y-%m-%d') \
|
||||
--base main
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
@@ -1,121 +0,0 @@
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import json
|
||||
|
||||
# Asynchronously fetch data from a given URL
|
||||
async def fetch_data(url):
|
||||
try:
|
||||
# Create an asynchronous session
|
||||
async with aiohttp.ClientSession() as session:
|
||||
# Send a GET request to the URL
|
||||
async with session.get(url) as resp:
|
||||
# Raise an error if the response status is not OK
|
||||
resp.raise_for_status()
|
||||
# Parse the response JSON
|
||||
resp_json = await resp.json()
|
||||
print("Fetch the data from URL.")
|
||||
# Return the 'data' field from the JSON response
|
||||
return resp_json['data']
|
||||
except Exception as e:
|
||||
# Print an error message if fetching data fails
|
||||
print("Error fetching data from URL:", e)
|
||||
return None
|
||||
|
||||
# Synchronize local data with remote data
|
||||
def sync_local_data_with_remote(local_data, remote_data):
|
||||
# Update existing keys in local_data with values from remote_data
|
||||
for key in (set(local_data) & set(remote_data)):
|
||||
local_data[key].update(remote_data[key])
|
||||
|
||||
# Add new keys from remote_data to local_data
|
||||
for key in (set(remote_data) - set(local_data)):
|
||||
local_data[key] = remote_data[key]
|
||||
|
||||
# Write data to the json file
|
||||
def write_to_file(file_path, data):
|
||||
try:
|
||||
# Open the file in write mode
|
||||
with open(file_path, "w") as file:
|
||||
# Dump the data as JSON into the file
|
||||
json.dump(data, file, indent=4)
|
||||
print("Values updated successfully.")
|
||||
except Exception as e:
|
||||
# Print an error message if writing to file fails
|
||||
print("Error updating JSON file:", e)
|
||||
|
||||
# Update the existing models and add the missing models
|
||||
def transform_remote_data(data):
|
||||
transformed = {}
|
||||
for row in data:
|
||||
# Add the fields 'max_tokens' and 'input_cost_per_token'
|
||||
obj = {
|
||||
"max_tokens": row["context_length"],
|
||||
"input_cost_per_token": float(row["pricing"]["prompt"]),
|
||||
}
|
||||
|
||||
# Add 'max_output_tokens' as a field if it is not None
|
||||
if "top_provider" in row and "max_completion_tokens" in row["top_provider"] and row["top_provider"]["max_completion_tokens"] is not None:
|
||||
obj['max_output_tokens'] = int(row["top_provider"]["max_completion_tokens"])
|
||||
|
||||
# Add the field 'output_cost_per_token'
|
||||
obj.update({
|
||||
"output_cost_per_token": float(row["pricing"]["completion"]),
|
||||
})
|
||||
|
||||
# Add field 'input_cost_per_image' if it exists and is non-zero
|
||||
if "pricing" in row and "image" in row["pricing"] and float(row["pricing"]["image"]) != 0.0:
|
||||
obj['input_cost_per_image'] = float(row["pricing"]["image"])
|
||||
|
||||
# Add the fields 'litellm_provider' and 'mode'
|
||||
obj.update({
|
||||
"litellm_provider": "openrouter",
|
||||
"mode": "chat"
|
||||
})
|
||||
|
||||
# Add the 'supports_vision' field if the modality is 'multimodal'
|
||||
if row.get('architecture', {}).get('modality') == 'multimodal':
|
||||
obj['supports_vision'] = True
|
||||
|
||||
# Use a composite key to store the transformed object
|
||||
transformed[f'openrouter/{row["id"]}'] = obj
|
||||
|
||||
return transformed
|
||||
|
||||
|
||||
# Load local data from a specified file
|
||||
def load_local_data(file_path):
|
||||
try:
|
||||
# Open the file in read mode
|
||||
with open(file_path, "r") as file:
|
||||
# Load and return the JSON data
|
||||
return json.load(file)
|
||||
except FileNotFoundError:
|
||||
# Print an error message if the file is not found
|
||||
print("File not found:", file_path)
|
||||
return None
|
||||
except json.JSONDecodeError as e:
|
||||
# Print an error message if JSON decoding fails
|
||||
print("Error decoding JSON:", e)
|
||||
return None
|
||||
|
||||
def main():
|
||||
local_file_path = "model_prices_and_context_window.json" # Path to the local data file
|
||||
url = "https://openrouter.ai/api/v1/models" # URL to fetch remote data
|
||||
|
||||
# Load local data from file
|
||||
local_data = load_local_data(local_file_path)
|
||||
# Fetch remote data asynchronously
|
||||
remote_data = asyncio.run(fetch_data(url))
|
||||
# Transform the fetched remote data
|
||||
remote_data = transform_remote_data(remote_data)
|
||||
|
||||
# If both local and remote data are available, synchronize and save
|
||||
if local_data and remote_data:
|
||||
sync_local_data_with_remote(local_data, remote_data)
|
||||
write_to_file(local_file_path, local_data)
|
||||
else:
|
||||
print("Failed to fetch model data from either local file or URL.")
|
||||
|
||||
# Entry point of the script
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -4,16 +4,15 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- dev-staging
|
||||
- release/*
|
||||
paths:
|
||||
- 'api/**'
|
||||
- 'packages/**'
|
||||
- 'packages/api/**'
|
||||
jobs:
|
||||
tests_Backend:
|
||||
name: Run Backend unit tests
|
||||
timeout-minutes: 60
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
MONGO_URI: ${{ secrets.MONGO_URI }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
@@ -24,27 +23,25 @@ jobs:
|
||||
BAN_DURATION: ${{ secrets.BAN_DURATION }}
|
||||
BAN_INTERVAL: ${{ secrets.BAN_INTERVAL }}
|
||||
NODE_ENV: CI
|
||||
NODE_OPTIONS: '--max-old-space-size=${{ secrets.NODE_MAX_OLD_SPACE_SIZE || 6144 }}'
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 20.x
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: npm ci
|
||||
|
||||
- name: Install Data Provider Package
|
||||
run: pnpm run build:data-provider
|
||||
run: npm run build:data-provider
|
||||
|
||||
- name: Install Data Schemas Package
|
||||
run: pnpm run build:data-schemas
|
||||
run: npm run build:data-schemas
|
||||
|
||||
- name: Install API Package
|
||||
run: pnpm run build:api
|
||||
run: npm run build:api
|
||||
|
||||
- name: Create empty auth.json file
|
||||
run: |
|
||||
@@ -54,7 +51,7 @@ jobs:
|
||||
- name: Check for Circular dependency in rollup
|
||||
working-directory: ./packages/data-provider
|
||||
run: |
|
||||
output=$(pnpm run rollup:api)
|
||||
output=$(npm run rollup:api)
|
||||
echo "$output"
|
||||
if echo "$output" | grep -q "Circular dependency"; then
|
||||
echo "Error: Circular dependency detected!"
|
||||
@@ -65,13 +62,13 @@ jobs:
|
||||
run: cp api/test/.env.test.example api/test/.env.test
|
||||
|
||||
- name: Run unit tests
|
||||
run: cd api && pnpm run test:ci
|
||||
run: cd api && npm run test:ci
|
||||
|
||||
- name: Run librechat-data-provider unit tests
|
||||
run: cd packages/data-provider && pnpm run test:ci
|
||||
- name: Run hanzochat-data-provider unit tests
|
||||
run: cd packages/data-provider && npm run test:ci
|
||||
|
||||
- name: Run @librechat/data-schemas unit tests
|
||||
run: cd packages/data-schemas && pnpm run test:ci
|
||||
- name: Run @hanzochat/data-schemas unit tests
|
||||
run: cd packages/data-schemas && npm run test:ci
|
||||
|
||||
- name: Run @librechat/api unit tests
|
||||
run: cd packages/api && pnpm run test:ci
|
||||
- name: Run @hanzochat/api unit tests
|
||||
run: cd packages/api && npm run test:ci
|
||||
@@ -8,19 +8,19 @@ env:
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# checkout the repo
|
||||
- name: 'Checkout GitHub Action'
|
||||
uses: actions/checkout@main
|
||||
|
||||
- name: 'Login via Azure CLI'
|
||||
uses: azure/login@v2
|
||||
uses: azure/login@v1
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||
|
||||
- name: 'Build GitHub Runner container image'
|
||||
uses: azure/docker-login@v2
|
||||
uses: azure/docker-login@v1
|
||||
with:
|
||||
login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
docker build --build-arg RUNNER_VERSION=${{ env.RUNNER_VERSION }} -t ${{ secrets.REGISTRY_LOGIN_SERVER }}/pwd9000-github-runner-lin:${{ env.RUNNER_VERSION }} .
|
||||
|
||||
- name: 'Push container image to ACR'
|
||||
uses: azure/docker-login@v2
|
||||
uses: azure/docker-login@v1
|
||||
with:
|
||||
login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
name: Cache Integration Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- dev-staging
|
||||
- release/*
|
||||
paths:
|
||||
- 'packages/api/src/cache/**'
|
||||
- 'packages/api/src/cluster/**'
|
||||
- 'packages/api/src/mcp/**'
|
||||
- 'packages/api/src/stream/**'
|
||||
- 'redis-config/**'
|
||||
- '.github/workflows/cache-integration-tests.yml'
|
||||
|
||||
jobs:
|
||||
cache_integration_tests:
|
||||
name: Integration Tests that use actual Redis Cache
|
||||
timeout-minutes: 30
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Use Node.js 20.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install Redis tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y redis-server redis-tools
|
||||
|
||||
- name: Start Single Redis Instance
|
||||
run: |
|
||||
redis-server --daemonize yes --port 6379
|
||||
sleep 2
|
||||
# Verify single Redis is running
|
||||
redis-cli -p 6379 ping || exit 1
|
||||
|
||||
- name: Start Redis Cluster
|
||||
working-directory: redis-config
|
||||
run: |
|
||||
chmod +x start-cluster.sh stop-cluster.sh
|
||||
./start-cluster.sh
|
||||
sleep 10
|
||||
# Verify cluster is running
|
||||
redis-cli -p 7001 cluster info || exit 1
|
||||
redis-cli -p 7002 cluster info || exit 1
|
||||
redis-cli -p 7003 cluster info || exit 1
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build packages
|
||||
run: |
|
||||
pnpm run build:data-provider
|
||||
pnpm run build:data-schemas
|
||||
pnpm run build:api
|
||||
|
||||
- name: Run all cache integration tests (Single Redis Node)
|
||||
working-directory: packages/api
|
||||
env:
|
||||
NODE_ENV: test
|
||||
USE_REDIS: true
|
||||
USE_REDIS_CLUSTER: false
|
||||
REDIS_URI: redis://127.0.0.1:6379
|
||||
run: pnpm run test:cache-integration
|
||||
|
||||
- name: Run all cache integration tests (Redis Cluster)
|
||||
working-directory: packages/api
|
||||
env:
|
||||
NODE_ENV: test
|
||||
USE_REDIS: true
|
||||
USE_REDIS_CLUSTER: true
|
||||
REDIS_URI: redis://127.0.0.1:7001,redis://127.0.0.1:7002,redis://127.0.0.1:7003
|
||||
run: pnpm run test:cache-integration
|
||||
|
||||
- name: Stop Redis Cluster
|
||||
if: always()
|
||||
working-directory: redis-config
|
||||
run: ./stop-cluster.sh || true
|
||||
|
||||
- name: Stop Single Redis Instance
|
||||
if: always()
|
||||
run: redis-cli -p 6379 shutdown || true
|
||||
@@ -0,0 +1,244 @@
|
||||
name: CI/CD Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
- 'release/**'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
deploy_environment:
|
||||
description: 'Environment to deploy to'
|
||||
required: false
|
||||
default: 'staging'
|
||||
type: choice
|
||||
options:
|
||||
- staging
|
||||
- production
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
NODE_VERSION: '20'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
test-type: [api, client, packages]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build packages
|
||||
if: matrix.test-type != 'packages'
|
||||
run: |
|
||||
npm run build:data-provider
|
||||
npm run build:data-schemas
|
||||
npm run build:api
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
if [ "${{ matrix.test-type }}" = "api" ]; then
|
||||
npm run test:api
|
||||
elif [ "${{ matrix.test-type }}" = "client" ]; then
|
||||
npm run test:client
|
||||
else
|
||||
cd packages/data-provider && npm test
|
||||
cd ../data-schemas && npm test
|
||||
cd ../api && npm test
|
||||
fi
|
||||
|
||||
- name: Upload coverage
|
||||
if: always()
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
flags: ${{ matrix.test-type }}
|
||||
|
||||
lint:
|
||||
name: Lint & Format Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run ESLint
|
||||
run: npm run lint
|
||||
|
||||
- name: Check formatting
|
||||
run: npm run format:check
|
||||
|
||||
build-docker:
|
||||
name: Build Docker Image
|
||||
needs: [test, lint]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
outputs:
|
||||
image-digest: ${{ steps.build.outputs.digest }}
|
||||
image-tags: ${{ steps.meta.outputs.tags }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
type=raw,value=vendor-mode,enable={{is_default_branch}}
|
||||
type=sha,prefix={{branch}}-
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: |
|
||||
BUILD_DATE=${{ github.event.head_commit.timestamp }}
|
||||
VCS_REF=${{ github.sha }}
|
||||
VERSION=${{ github.ref_name }}
|
||||
|
||||
- name: Generate SBOM
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
|
||||
format: spdx-json
|
||||
output-file: sbom.spdx.json
|
||||
|
||||
- name: Upload SBOM
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sbom
|
||||
path: sbom.spdx.json
|
||||
|
||||
security-scan:
|
||||
name: Security Scan
|
||||
needs: build-docker
|
||||
if: github.event_name != 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build-docker.outputs.image-digest }}
|
||||
format: 'sarif'
|
||||
output: 'trivy-results.sarif'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
|
||||
deploy-staging:
|
||||
name: Deploy to Staging
|
||||
needs: [build-docker, security-scan]
|
||||
if: github.ref == 'refs/heads/develop' && github.event_name != 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: staging
|
||||
url: https://staging.hanzo.ai
|
||||
|
||||
steps:
|
||||
- name: Deploy to staging
|
||||
run: |
|
||||
echo "Deploying ${{ needs.build-docker.outputs.image-digest }} to staging"
|
||||
# Add your deployment logic here
|
||||
# Example: kubectl set image deployment/chat chat=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build-docker.outputs.image-digest }}
|
||||
|
||||
deploy-production:
|
||||
name: Deploy to Production
|
||||
needs: [build-docker, security-scan]
|
||||
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: production
|
||||
url: https://chat.hanzo.ai
|
||||
|
||||
steps:
|
||||
- name: Deploy to production
|
||||
run: |
|
||||
echo "Deploying ${{ needs.build-docker.outputs.image-digest }} to production"
|
||||
# Add your deployment logic here
|
||||
# Example: kubectl set image deployment/chat chat=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build-docker.outputs.image-digest }}
|
||||
|
||||
- name: Create deployment record
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
await github.rest.repos.createDeployment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: context.sha,
|
||||
environment: 'production',
|
||||
required_contexts: [],
|
||||
auto_merge: false,
|
||||
production_environment: true,
|
||||
});
|
||||
@@ -0,0 +1,220 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
pull_request:
|
||||
branches: [ main, develop ]
|
||||
|
||||
env:
|
||||
NODE_ENV: test
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test Suite
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.x]
|
||||
mongodb-version: ['7.0']
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Start MongoDB
|
||||
uses: supercharge/mongodb-github-action@1.10.0
|
||||
with:
|
||||
mongodb-version: ${{ matrix.mongodb-version }}
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.npm
|
||||
node_modules
|
||||
*/node_modules
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Cache build artifacts
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
packages/*/dist
|
||||
api/lib
|
||||
client/dist
|
||||
key: ${{ runner.os }}-build-${{ hashFiles('packages/**/*.ts', 'packages/**/*.js') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build packages
|
||||
run: |
|
||||
npm run build:data-provider
|
||||
npm run build:data-schemas
|
||||
npm run build:api
|
||||
|
||||
- name: Create test environment
|
||||
run: |
|
||||
cat > .env.test << EOF
|
||||
NODE_ENV=test
|
||||
MONGO_URI=mongodb://localhost:27017/HanzoChat_test
|
||||
MEILI_HOST=
|
||||
RAG_API_URL=
|
||||
ALLOW_REGISTRATION=true
|
||||
ALLOW_UNVERIFIED_EMAIL_LOGIN=true
|
||||
RATE_LIMIT_ENABLED=false
|
||||
CREDS_KEY=test-creds-key-32-chars-long-string
|
||||
CREDS_IV=test-creds-iv-16
|
||||
JWT_SECRET=test-jwt-secret-very-secure
|
||||
JWT_REFRESH_SECRET=test-jwt-refresh-secret
|
||||
GOOGLE_ANALYTICS_ID=
|
||||
SENTRY_DSN=
|
||||
DOMAIN_CLIENT=http://localhost:3080
|
||||
DOMAIN_SERVER=http://localhost:3080
|
||||
TRUST_PROXY=0
|
||||
DEBUG_LOGGING=false
|
||||
CONSOLE_JSON=false
|
||||
EOF
|
||||
|
||||
- name: Run Client Tests
|
||||
run: NODE_ENV=test npm run test:client -- --coverage --coverageReporters=json
|
||||
continue-on-error: false
|
||||
|
||||
- name: Run API Tests
|
||||
run: NODE_ENV=test npm run test:api -- --coverage --coverageReporters=json --maxWorkers=2
|
||||
continue-on-error: false
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-results
|
||||
path: |
|
||||
client/coverage/
|
||||
api/coverage/
|
||||
|
||||
- name: Test Report
|
||||
uses: dorny/test-reporter@v1
|
||||
if: always()
|
||||
with:
|
||||
name: Jest Tests
|
||||
path: '**/junit.xml'
|
||||
reporter: jest-junit
|
||||
|
||||
- name: Coverage Report
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./client/coverage/coverage-final.json,./api/coverage/coverage-final.json
|
||||
flags: unittests
|
||||
name: codecov-umbrella
|
||||
fail_ci_if_error: false
|
||||
|
||||
lint:
|
||||
name: Code Quality
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.npm
|
||||
node_modules
|
||||
*/node_modules
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run ESLint
|
||||
run: npm run lint
|
||||
|
||||
- name: Run Prettier Check
|
||||
run: npm run format:check || true
|
||||
|
||||
security:
|
||||
name: Security Scan
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run npm audit
|
||||
run: npm audit --production || true
|
||||
|
||||
- name: Run Snyk Security Scan
|
||||
uses: snyk/actions/node@master
|
||||
continue-on-error: true
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
|
||||
build:
|
||||
name: Build Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.npm
|
||||
node_modules
|
||||
*/node_modules
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build all packages
|
||||
run: |
|
||||
npm run build:data-provider
|
||||
npm run build:data-schemas
|
||||
npm run build:api
|
||||
|
||||
- name: Build client
|
||||
run: npm run build:client
|
||||
env:
|
||||
DOMAIN_CLIENT: https://chat.hanzo.ai
|
||||
DOMAIN_SERVER: https://chat.hanzo.ai
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-artifacts
|
||||
path: |
|
||||
client/dist/
|
||||
api/lib/
|
||||
packages/*/dist/
|
||||
@@ -1,67 +0,0 @@
|
||||
name: Publish `@librechat/client` to NPM
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'packages/client/package.json'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
reason:
|
||||
description: 'Reason for manual trigger'
|
||||
required: false
|
||||
default: 'Manual publish requested'
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for OIDC trusted publishing
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-and-publish:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
environment: publish # Must match npm trusted publisher config
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20.x'
|
||||
cache: 'pnpm'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Update npm for OIDC support
|
||||
run: npm install -g npm@latest # Must be 11.5.1+ for provenance
|
||||
|
||||
- name: Install client dependencies
|
||||
run: cd packages/client && pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build client
|
||||
run: cd packages/client && pnpm run build
|
||||
|
||||
- name: Check version change
|
||||
id: check
|
||||
working-directory: packages/client
|
||||
run: |
|
||||
PACKAGE_VERSION=$(node -p "require('./package.json').version")
|
||||
PUBLISHED_VERSION=$(npm view @librechat/client version 2>/dev/null || echo "0.0.0")
|
||||
if [ "$PACKAGE_VERSION" = "$PUBLISHED_VERSION" ]; then
|
||||
echo "No version change, skipping publish"
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Version changed, proceeding with publish"
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Pack package
|
||||
if: steps.check.outputs.skip != 'true'
|
||||
working-directory: packages/client
|
||||
run: npm pack
|
||||
|
||||
- name: Publish
|
||||
if: steps.check.outputs.skip != 'true'
|
||||
working-directory: packages/client
|
||||
run: npm publish *.tgz --access public --provenance
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish `librechat-data-provider` to NPM
|
||||
name: Node.js Package
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -6,46 +6,29 @@ on:
|
||||
- main
|
||||
paths:
|
||||
- 'packages/data-provider/package.json'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
reason:
|
||||
description: 'Reason for manual trigger'
|
||||
required: false
|
||||
default: 'Manual publish requested'
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for OIDC trusted publishing
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- run: cd packages/data-provider && pnpm install --frozen-lockfile
|
||||
- run: cd packages/data-provider && pnpm run build
|
||||
node-version: 16
|
||||
- run: cd packages/data-provider && npm ci
|
||||
- run: cd packages/data-provider && npm run build
|
||||
|
||||
publish-npm:
|
||||
needs: build
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
environment: publish # Must match npm trusted publisher config
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
node-version: 16
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Update npm for OIDC support
|
||||
run: npm install -g npm@latest # Must be 11.5.1+ for provenance
|
||||
|
||||
- run: cd packages/data-provider && pnpm install --frozen-lockfile
|
||||
- run: cd packages/data-provider && pnpm run build
|
||||
- run: cd packages/data-provider && npm publish --provenance
|
||||
- run: cd packages/data-provider && npm ci
|
||||
- run: cd packages/data-provider && npm run build
|
||||
- run: cd packages/data-provider && npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish `@librechat/data-schemas` to NPM
|
||||
name: Publish `@hanzochat/data-schemas` to NPM
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -13,41 +13,32 @@ on:
|
||||
required: false
|
||||
default: 'Manual publish requested'
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for OIDC trusted publishing
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-and-publish:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
environment: publish # Must match npm trusted publisher config
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
cache: 'pnpm'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Update npm for OIDC support
|
||||
run: npm install -g npm@latest # Must be 11.5.1+ for provenance
|
||||
|
||||
node-version: '18.x'
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd packages/data-schemas && pnpm install --frozen-lockfile
|
||||
|
||||
run: cd packages/data-schemas && npm ci
|
||||
|
||||
- name: Build
|
||||
run: cd packages/data-schemas && pnpm run build
|
||||
run: cd packages/data-schemas && npm run build
|
||||
|
||||
- name: Set up npm authentication
|
||||
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.PUBLISH_NPM_TOKEN }}" > ~/.npmrc
|
||||
|
||||
- name: Check version change
|
||||
id: check
|
||||
working-directory: packages/data-schemas
|
||||
run: |
|
||||
PACKAGE_VERSION=$(node -p "require('./package.json').version")
|
||||
PUBLISHED_VERSION=$(npm view @librechat/data-schemas version 2>/dev/null || echo "0.0.0")
|
||||
PUBLISHED_VERSION=$(npm view @hanzochat/data-schemas version 2>/dev/null || echo "0.0.0")
|
||||
if [ "$PACKAGE_VERSION" = "$PUBLISHED_VERSION" ]; then
|
||||
echo "No version change, skipping publish"
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
@@ -64,4 +55,4 @@ jobs:
|
||||
- name: Publish
|
||||
if: steps.check.outputs.skip != 'true'
|
||||
working-directory: packages/data-schemas
|
||||
run: npm publish *.tgz --access public --provenance
|
||||
run: npm publish *.tgz --access public
|
||||
@@ -7,14 +7,11 @@ on:
|
||||
- completed
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: hanzo-deploy-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'danny-avila/LibreChat' &&
|
||||
github.repository == 'hanzoai/chat' &&
|
||||
(github.event_name == 'workflow_dispatch' ||
|
||||
(github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'dev'))
|
||||
steps:
|
||||
@@ -32,12 +29,12 @@ jobs:
|
||||
DO_HOST: ${{ secrets.DO_HOST }}
|
||||
DO_USER: ${{ secrets.DO_USER }}
|
||||
run: |
|
||||
ssh ${DO_USER}@${DO_HOST} << EOF
|
||||
ssh -o StrictHostKeyChecking=no ${DO_USER}@${DO_HOST} << EOF
|
||||
sudo -i -u danny bash << 'EEOF'
|
||||
cd ~/LibreChat && \
|
||||
cd ~/chat && \
|
||||
git fetch origin main && \
|
||||
sudo npm run stop:deployed && \
|
||||
sudo docker images --format "{{.Repository}}:{{.ID}}" | grep -E "lc-dev|librechat" | cut -d: -f2 | xargs -r sudo docker rmi -f || true && \
|
||||
sudo docker images --format "{{.Repository}}:{{.ID}}" | grep -E "lc-dev|hanzochat" | cut -d: -f2 | xargs -r sudo docker rmi -f || true && \
|
||||
sudo npm run update:deployed && \
|
||||
git checkout dev && \
|
||||
git pull origin dev && \
|
||||
|
||||
@@ -13,14 +13,14 @@ env:
|
||||
|
||||
jobs:
|
||||
deploy-gh-runner-aci:
|
||||
runs-on: hanzo-deploy-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# checkout the repo
|
||||
- name: 'Checkout GitHub Action'
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 'Login via Azure CLI'
|
||||
uses: azure/login@v2
|
||||
uses: azure/login@v1
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||
|
||||
|
||||
@@ -9,23 +9,10 @@ on:
|
||||
- 'api/**'
|
||||
- 'client/**'
|
||||
- 'packages/**'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'Dockerfile'
|
||||
- 'Dockerfile.multi'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
timeout-minutes: 130
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -51,7 +38,7 @@ jobs:
|
||||
|
||||
# Log in to GitHub Container Registry
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -69,12 +56,6 @@ jobs:
|
||||
run: |
|
||||
cp .env.example .env
|
||||
|
||||
- name: Compute build metadata
|
||||
run: |
|
||||
echo "BUILD_COMMIT=${{ github.sha }}" >> $GITHUB_ENV
|
||||
echo "BUILD_BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
|
||||
|
||||
# Build and push Docker images for each target
|
||||
- name: Build and push Docker images
|
||||
uses: docker/build-push-action@v5
|
||||
@@ -88,8 +69,4 @@ jobs:
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:${{ github.sha }}
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
target: ${{ matrix.target }}
|
||||
build-args: |
|
||||
BUILD_COMMIT=${{ env.BUILD_COMMIT }}
|
||||
BUILD_BRANCH=${{ env.BUILD_BRANCH }}
|
||||
BUILD_DATE=${{ env.BUILD_DATE }}
|
||||
target: ${{ matrix.target }}
|
||||
@@ -9,28 +9,19 @@ on:
|
||||
- 'api/**'
|
||||
- 'client/**'
|
||||
- 'packages/**'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'Dockerfile'
|
||||
- 'Dockerfile.multi'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
timeout-minutes: 130
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: api-build
|
||||
file: Dockerfile.multi
|
||||
image_name: librechat-dev-api
|
||||
image_name: hanzochat-dev-api
|
||||
- target: node
|
||||
file: Dockerfile
|
||||
image_name: librechat-dev
|
||||
image_name: hanzochat-dev
|
||||
|
||||
steps:
|
||||
# Check out the repository
|
||||
@@ -47,7 +38,7 @@ jobs:
|
||||
|
||||
# Log in to GitHub Container Registry
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -65,12 +56,6 @@ jobs:
|
||||
run: |
|
||||
cp .env.example .env
|
||||
|
||||
- name: Compute build metadata
|
||||
run: |
|
||||
echo "BUILD_COMMIT=${{ github.sha }}" >> $GITHUB_ENV
|
||||
echo "BUILD_BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
|
||||
|
||||
# Build and push Docker images for each target
|
||||
- name: Build and push Docker images
|
||||
uses: docker/build-push-action@v5
|
||||
@@ -85,7 +70,3 @@ jobs:
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
target: ${{ matrix.target }}
|
||||
build-args: |
|
||||
BUILD_COMMIT=${{ env.BUILD_COMMIT }}
|
||||
BUILD_BRANCH=${{ env.BUILD_BRANCH }}
|
||||
BUILD_DATE=${{ env.BUILD_DATE }}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
name: Docker Dev Staging Images Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: api-build
|
||||
file: Dockerfile.multi
|
||||
image_name: lc-dev-staging-api
|
||||
- target: node
|
||||
file: Dockerfile
|
||||
image_name: lc-dev-staging
|
||||
|
||||
steps:
|
||||
# Check out the repository
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Set up QEMU
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
# Set up Docker Buildx
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# Log in to GitHub Container Registry
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Login to Docker Hub
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# Prepare the environment
|
||||
- name: Prepare environment
|
||||
run: |
|
||||
cp .env.example .env
|
||||
|
||||
- name: Compute build metadata
|
||||
run: |
|
||||
echo "BUILD_COMMIT=${{ github.sha }}" >> $GITHUB_ENV
|
||||
echo "BUILD_BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
|
||||
|
||||
# Build and push Docker images for each target
|
||||
- name: Build and push Docker images
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.file }}
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:${{ github.sha }}
|
||||
ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:latest
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:${{ github.sha }}
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
target: ${{ matrix.target }}
|
||||
build-args: |
|
||||
BUILD_COMMIT=${{ env.BUILD_COMMIT }}
|
||||
BUILD_BRANCH=${{ env.BUILD_BRANCH }}
|
||||
BUILD_DATE=${{ env.BUILD_DATE }}
|
||||
@@ -0,0 +1,109 @@
|
||||
name: Build and Push Docker Images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: |
|
||||
COMMIT_SHA=${{ github.sha }}
|
||||
BUILD_DATE=${{ steps.meta.outputs.created }}
|
||||
|
||||
build-and-push-rag:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata for RAG API
|
||||
id: meta-rag
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/hanzoai/chat-rag-api
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push RAG API Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./rag.Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta-rag.outputs.tags }}
|
||||
labels: ${{ steps.meta-rag.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
@@ -0,0 +1,107 @@
|
||||
name: Build and Push Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
# For main branch, tag as 'latest'
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
# For develop branch, tag as 'develop'
|
||||
type=raw,value=develop,enable=${{ github.ref == 'refs/heads/develop' }}
|
||||
# For tags, use the tag name
|
||||
type=ref,event=tag
|
||||
# For PRs, use pr-NUMBER
|
||||
type=ref,event=pr
|
||||
# SHA short
|
||||
type=sha,prefix={{branch}}-
|
||||
# Vendor mode tag for main
|
||||
type=raw,value=vendor-mode,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Create Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
body: |
|
||||
## Docker Image
|
||||
|
||||
🐳 Docker image is available at:
|
||||
```
|
||||
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||
```
|
||||
|
||||
### Vendor Mode Usage
|
||||
|
||||
To run with Hanzo AI branding (default):
|
||||
```bash
|
||||
docker run -d \
|
||||
-p 3080:3080 \
|
||||
--env-file .env \
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||
```
|
||||
|
||||
To run with custom branding:
|
||||
```bash
|
||||
docker run -d \
|
||||
-p 3080:3080 \
|
||||
-e VENDOR_MODE=true \
|
||||
-e VENDOR_NAME="Your Company" \
|
||||
-e VENDOR_MODEL_DEFAULT="Your Model" \
|
||||
-e VENDOR_MODEL_PRO="Your Model Pro" \
|
||||
-e APP_TITLE="Your Platform" \
|
||||
-e VENDOR_BACKEND_ENDPOINT=anthropic \
|
||||
-e VENDOR_BACKEND_API_KEY=$YOUR_API_KEY \
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||
```
|
||||
generate_release_notes: true
|
||||
@@ -0,0 +1,56 @@
|
||||
name: Docker Build Cache
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run every Monday at 3 AM UTC to refresh caches
|
||||
- cron: '0 3 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-cache:
|
||||
name: Build and Cache Base Images
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: builder
|
||||
cache-tag: cache-builder
|
||||
- target: stage-1
|
||||
cache-tag: cache-deps
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push cache image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
target: ${{ matrix.target }}
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ matrix.cache-tag }}
|
||||
cache-from: |
|
||||
type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ matrix.cache-tag }}
|
||||
type=gha
|
||||
cache-to: |
|
||||
type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ matrix.cache-tag }},mode=max
|
||||
type=gha,mode=max
|
||||
platforms: linux/amd64,linux/arm64
|
||||
@@ -1,71 +0,0 @@
|
||||
# Hanzo Chat — image build (amd64, self-hosted hanzoai pool).
|
||||
#
|
||||
# chat is a PUBLIC repo, so it cannot reuse the private hanzoai/.github
|
||||
# reusable workflow (GitHub blocks public->private workflow reuse).
|
||||
# This is therefore self-contained.
|
||||
#
|
||||
# Builds ghcr.io/hanzoai/chat on:
|
||||
# - push to main -> immutable sha-<sha7> tag
|
||||
# - v* tags -> semver image tag (e.g. v0.7.10 -> 0.7.10)
|
||||
# No :latest, no floating tags (semver-only policy). amd64 only
|
||||
# (DOKS is all amd64 — no arm64 runners).
|
||||
name: Docker Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ["v*"]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: docker-release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-amd64:
|
||||
# hanzoai org pool — ARC routes by runnerScaleSetName, not labels.
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
driver: docker-container
|
||||
driver-opts: network=host
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/hanzoai/chat
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=sha,prefix=sha-,format=short
|
||||
flavor: |
|
||||
latest=false
|
||||
|
||||
- name: Build and push amd64 image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=registry,ref=ghcr.io/hanzoai/chat-cache:amd64
|
||||
cache-to: type=registry,ref=ghcr.io/hanzoai/chat-cache:amd64,mode=max
|
||||
provenance: false
|
||||
@@ -1,36 +0,0 @@
|
||||
name: Docker Build Smoke Tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/docker-smoke.yml'
|
||||
- '.dockerignore'
|
||||
- 'Dockerfile.multi'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'packages/client/**'
|
||||
- 'packages/data-provider/**'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
client-package-target:
|
||||
name: Build Docker client package target
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build client package target
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile.multi
|
||||
platforms: linux/amd64
|
||||
push: false
|
||||
target: client-package-build
|
||||
@@ -1,71 +0,0 @@
|
||||
name: E2E Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'client/**'
|
||||
- 'api/**'
|
||||
- 'packages/**'
|
||||
- 'e2e/**'
|
||||
|
||||
concurrency:
|
||||
group: e2e-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
e2e:
|
||||
name: Playwright E2E
|
||||
if: github.event.pull_request.head.repo.full_name == 'hanzoai/chat'
|
||||
timeout-minutes: 30
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: CI
|
||||
SEARCH: false
|
||||
MONGO_URI: mongodb://localhost:27017/librechat-test
|
||||
JWT_SECRET: e2e-test-jwt-secret
|
||||
JWT_REFRESH_SECRET: e2e-test-jwt-refresh-secret
|
||||
CREDS_KEY: e2e-test-creds-key-32chars00000000
|
||||
CREDS_IV: e2e-test-creds-iv-16c
|
||||
DOMAIN_CLIENT: http://localhost:3080
|
||||
DOMAIN_SERVER: http://localhost:3080
|
||||
ALLOW_REGISTRATION: true
|
||||
TITLE_CONVO: false
|
||||
|
||||
services:
|
||||
mongodb:
|
||||
image: mongo:7
|
||||
ports:
|
||||
- 27017:27017
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build client
|
||||
run: pnpm run frontend
|
||||
|
||||
- name: Install Playwright
|
||||
run: |
|
||||
npx playwright install --with-deps chromium
|
||||
|
||||
- name: Run E2E tests
|
||||
run: pnpm run e2e:ci
|
||||
|
||||
- name: Upload report
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: e2e/playwright-report/
|
||||
retention-days: 14
|
||||
@@ -5,7 +5,6 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- dev-staging
|
||||
- release/*
|
||||
paths:
|
||||
- 'api/**'
|
||||
@@ -14,7 +13,7 @@ on:
|
||||
jobs:
|
||||
eslint_checks:
|
||||
name: Run ESLint Linting
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
@@ -25,19 +24,19 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Set up Node.js 20.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: pnpm
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: npm ci
|
||||
|
||||
# Run ESLint on changed files within the api/ and client/ directories.
|
||||
- name: Run ESLint on changed files
|
||||
env:
|
||||
SARIF_ESLINT_IGNORE_SUPPRESSED: "true"
|
||||
run: |
|
||||
# Extract the base commit SHA from the pull_request event payload.
|
||||
BASE_SHA=$(jq --raw-output .pull_request.base.sha "$GITHUB_EVENT_PATH")
|
||||
@@ -53,10 +52,22 @@ jobs:
|
||||
# Ensure there are files to lint before running ESLint
|
||||
if [[ -z "$CHANGED_FILES" ]]; then
|
||||
echo "No matching files changed. Skipping ESLint."
|
||||
echo "UPLOAD_SARIF=false" >> $GITHUB_ENV
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Set variable to allow SARIF upload
|
||||
echo "UPLOAD_SARIF=true" >> $GITHUB_ENV
|
||||
|
||||
# Run ESLint
|
||||
npx eslint --no-error-on-unmatched-pattern \
|
||||
--config eslint.config.mjs \
|
||||
$CHANGED_FILES
|
||||
--format @microsoft/eslint-formatter-sarif \
|
||||
--output-file eslint-results.sarif $CHANGED_FILES || true
|
||||
|
||||
- name: Upload analysis results to GitHub
|
||||
if: env.UPLOAD_SARIF == 'true'
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: eslint-results.sarif
|
||||
wait-for-processing: true
|
||||
@@ -5,59 +5,52 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- dev-staging
|
||||
- release/*
|
||||
paths:
|
||||
- 'client/**'
|
||||
- 'packages/data-provider/**'
|
||||
- 'packages/**'
|
||||
|
||||
jobs:
|
||||
tests_frontend_ubuntu:
|
||||
name: Run frontend unit tests on Ubuntu
|
||||
timeout-minutes: 60
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
env:
|
||||
NODE_OPTIONS: '--max-old-space-size=${{ secrets.NODE_MAX_OLD_SPACE_SIZE || 6144 }}'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 20.x
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: npm ci
|
||||
|
||||
- name: Build Client
|
||||
run: pnpm run frontend:ci
|
||||
run: npm run frontend:ci
|
||||
|
||||
- name: Run unit tests
|
||||
run: pnpm run test:ci --verbose
|
||||
run: npm run test:ci --verbose
|
||||
working-directory: client
|
||||
|
||||
tests_frontend_windows:
|
||||
name: Run frontend unit tests on Windows
|
||||
timeout-minutes: 60
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
NODE_OPTIONS: '--max-old-space-size=${{ secrets.NODE_MAX_OLD_SPACE_SIZE || 6144 }}'
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 20.x
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: npm ci
|
||||
|
||||
- name: Build Client
|
||||
run: pnpm run frontend:ci
|
||||
run: npm run frontend:ci
|
||||
|
||||
- name: Run unit tests
|
||||
run: pnpm run test:ci --verbose
|
||||
run: npm run test:ci --verbose
|
||||
working-directory: client
|
||||
@@ -0,0 +1,95 @@
|
||||
name: Generate Release Changelog PR
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
generate-release-changelog-pr:
|
||||
permissions:
|
||||
contents: write # Needed for pushing commits and creating branches.
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# 1. Checkout the repository (with full history).
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# 2. Generate the release changelog using our custom configuration.
|
||||
- name: Generate Release Changelog
|
||||
id: generate_release
|
||||
uses: mikepenz/release-changelog-builder-action@v5.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
configuration: ".github/configuration-release.json"
|
||||
owner: ${{ github.repository_owner }}
|
||||
repo: ${{ github.event.repository.name }}
|
||||
outputFile: CHANGELOG-release.md
|
||||
|
||||
# 3. Update the main CHANGELOG.md:
|
||||
# - If it doesn't exist, create it with a basic header.
|
||||
# - Remove the "Unreleased" section (if present).
|
||||
# - Prepend the new release changelog above previous releases.
|
||||
# - Remove all temporary files before committing.
|
||||
- name: Update CHANGELOG.md
|
||||
run: |
|
||||
# Determine the release tag, e.g. "v1.2.3"
|
||||
TAG=${GITHUB_REF##*/}
|
||||
echo "Using release tag: $TAG"
|
||||
|
||||
# Ensure CHANGELOG.md exists; if not, create a basic header.
|
||||
if [ ! -f CHANGELOG.md ]; then
|
||||
echo "# Changelog" > CHANGELOG.md
|
||||
echo "" >> CHANGELOG.md
|
||||
echo "All notable changes to this project will be documented in this file." >> CHANGELOG.md
|
||||
echo "" >> CHANGELOG.md
|
||||
fi
|
||||
|
||||
echo "Updating CHANGELOG.md…"
|
||||
|
||||
# Remove the "Unreleased" section (from "## [Unreleased]" until the first occurrence of '---') if it exists.
|
||||
if grep -q "^## \[Unreleased\]" CHANGELOG.md; then
|
||||
awk '/^## \[Unreleased\]/{flag=1} flag && /^---/{flag=0; next} !flag' CHANGELOG.md > CHANGELOG.cleaned
|
||||
else
|
||||
cp CHANGELOG.md CHANGELOG.cleaned
|
||||
fi
|
||||
|
||||
# Split the cleaned file into:
|
||||
# - header.md: content before the first release header ("## [v...").
|
||||
# - tail.md: content from the first release header onward.
|
||||
awk '/^## \[v/{exit} {print}' CHANGELOG.cleaned > header.md
|
||||
awk 'f{print} /^## \[v/{f=1; print}' CHANGELOG.cleaned > tail.md
|
||||
|
||||
# Combine header, the new release changelog, and the tail.
|
||||
echo "Combining updated changelog parts..."
|
||||
cat header.md CHANGELOG-release.md > CHANGELOG.md.new
|
||||
echo "" >> CHANGELOG.md.new
|
||||
cat tail.md >> CHANGELOG.md.new
|
||||
|
||||
mv CHANGELOG.md.new CHANGELOG.md
|
||||
|
||||
# Remove temporary files.
|
||||
rm -f CHANGELOG.cleaned header.md tail.md CHANGELOG-release.md
|
||||
|
||||
echo "Final CHANGELOG.md content:"
|
||||
cat CHANGELOG.md
|
||||
|
||||
# 4. Create (or update) the Pull Request with the updated CHANGELOG.md.
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
sign-commits: true
|
||||
commit-message: "chore: update CHANGELOG for release ${{ github.ref_name }}"
|
||||
base: main
|
||||
branch: "changelog/${{ github.ref_name }}"
|
||||
reviewers: hanzoai
|
||||
title: "📜 docs: Changelog for release ${{ github.ref_name }}"
|
||||
body: |
|
||||
**Description**:
|
||||
- This PR updates the CHANGELOG.md by removing the "Unreleased" section and adding new release notes for release ${{ github.ref_name }} above previous releases.
|
||||
@@ -0,0 +1,107 @@
|
||||
name: Generate Unreleased Changelog PR
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * 1" # Runs every Monday at 00:00 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
generate-unreleased-changelog-pr:
|
||||
permissions:
|
||||
contents: write # Needed for pushing commits and creating branches.
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# 1. Checkout the repository on main.
|
||||
- name: Checkout Repository on Main
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
|
||||
# 4. Get the latest version tag.
|
||||
- name: Get Latest Tag
|
||||
id: get_latest_tag
|
||||
run: |
|
||||
LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1) || echo "none")
|
||||
echo "Latest tag: $LATEST_TAG"
|
||||
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
# 5. Generate the Unreleased changelog.
|
||||
- name: Generate Unreleased Changelog
|
||||
id: generate_unreleased
|
||||
uses: mikepenz/release-changelog-builder-action@v5.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
configuration: ".github/configuration-unreleased.json"
|
||||
owner: ${{ github.repository_owner }}
|
||||
repo: ${{ github.event.repository.name }}
|
||||
outputFile: CHANGELOG-unreleased.md
|
||||
fromTag: ${{ steps.get_latest_tag.outputs.tag }}
|
||||
toTag: main
|
||||
|
||||
# 7. Update CHANGELOG.md with the new Unreleased section.
|
||||
- name: Update CHANGELOG.md
|
||||
id: update_changelog
|
||||
run: |
|
||||
# Create CHANGELOG.md if it doesn't exist.
|
||||
if [ ! -f CHANGELOG.md ]; then
|
||||
echo "# Changelog" > CHANGELOG.md
|
||||
echo "" >> CHANGELOG.md
|
||||
echo "All notable changes to this project will be documented in this file." >> CHANGELOG.md
|
||||
echo "" >> CHANGELOG.md
|
||||
fi
|
||||
|
||||
echo "Updating CHANGELOG.md…"
|
||||
|
||||
# Extract content before the "## [Unreleased]" (or first version header if missing).
|
||||
if grep -q "^## \[Unreleased\]" CHANGELOG.md; then
|
||||
awk '/^## \[Unreleased\]/{exit} {print}' CHANGELOG.md > CHANGELOG_TMP.md
|
||||
else
|
||||
awk '/^## \[v/{exit} {print}' CHANGELOG.md > CHANGELOG_TMP.md
|
||||
fi
|
||||
|
||||
# Append the generated Unreleased changelog.
|
||||
echo "" >> CHANGELOG_TMP.md
|
||||
cat CHANGELOG-unreleased.md >> CHANGELOG_TMP.md
|
||||
echo "" >> CHANGELOG_TMP.md
|
||||
|
||||
# Append the remainder of the original changelog (starting from the first version header).
|
||||
awk 'f{print} /^## \[v/{f=1; print}' CHANGELOG.md >> CHANGELOG_TMP.md
|
||||
|
||||
# Replace the old file with the updated file.
|
||||
mv CHANGELOG_TMP.md CHANGELOG.md
|
||||
|
||||
# Remove the temporary generated file.
|
||||
rm -f CHANGELOG-unreleased.md
|
||||
|
||||
echo "Final CHANGELOG.md:"
|
||||
cat CHANGELOG.md
|
||||
|
||||
# 8. Check if CHANGELOG.md has any updates.
|
||||
- name: Check for CHANGELOG.md changes
|
||||
id: changelog_changes
|
||||
run: |
|
||||
if git diff --quiet CHANGELOG.md; then
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
# 9. Create (or update) the Pull Request only if there are changes.
|
||||
- name: Create Pull Request
|
||||
if: steps.changelog_changes.outputs.has_changes == 'true'
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
base: main
|
||||
branch: "changelog/unreleased-update"
|
||||
sign-commits: true
|
||||
commit-message: "action: update Unreleased changelog"
|
||||
title: "📜 docs: Unreleased Changelog"
|
||||
body: |
|
||||
**Description**:
|
||||
- This PR updates the Unreleased section in CHANGELOG.md.
|
||||
- It compares the current main branch with the latest version tag (determined as ${{ steps.get_latest_tag.outputs.tag }}),
|
||||
regenerates the Unreleased changelog, removes any old Unreleased block, and inserts the new content.
|
||||
@@ -9,10 +9,10 @@ on:
|
||||
|
||||
jobs:
|
||||
generate:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: supabase/embeddings-generator@v0.0.6
|
||||
- uses: actions/checkout@v3
|
||||
- uses: supabase/embeddings-generator@v0.0.5
|
||||
with:
|
||||
supabase-url: ${{ secrets.SUPABASE_URL }}
|
||||
supabase-service-role-key: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
# Removes a PR's GitNexus index from the droplet when the PR is closed
|
||||
# (merged or not). The deploy workflow also prunes stale folders as a
|
||||
# safety net, but this gives us immediate cleanup without waiting for
|
||||
# the next deploy trigger.
|
||||
|
||||
name: GitNexus Cleanup PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
concurrency:
|
||||
group: gitnexus-cleanup-pr-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
# Skip fork PRs entirely. GitHub withholds repository secrets from
|
||||
# pull_request events originating on forks, so an SSH deploy job run
|
||||
# from a fork close would fail noisily. The deploy workflow's stale-
|
||||
# folder pruning step catches any fork-contributor indexes that
|
||||
# actually made it onto the droplet.
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
# Skip the SSH round-trip entirely when no index artifact was ever
|
||||
# built for this PR (docs-only PRs, paths-ignored PRs, PRs closed
|
||||
# before indexing finished, etc). Eliminates ~95% of no-op SSH
|
||||
# sessions on a busy repo.
|
||||
- name: Check for index artifact
|
||||
id: check
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const { data } = await github.rest.actions.listArtifactsForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: `gitnexus-index-pr-${context.payload.pull_request.number}`,
|
||||
per_page: 1,
|
||||
});
|
||||
const hasArtifact = data.total_count > 0;
|
||||
core.info(`Artifact exists: ${hasArtifact}`);
|
||||
core.setOutput('has_artifact', hasArtifact ? 'true' : 'false');
|
||||
|
||||
- name: Setup SSH
|
||||
if: steps.check.outputs.has_artifact == 'true'
|
||||
env:
|
||||
SSH_KEY: ${{ secrets.GITNEXUS_DO_SSH_KEY }}
|
||||
KNOWN_HOST: ${{ secrets.GITNEXUS_DO_KNOWN_HOST }}
|
||||
run: |
|
||||
set -e
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
printf '%s\n' "$SSH_KEY" > ~/.ssh/deploy_key
|
||||
chmod 600 ~/.ssh/deploy_key
|
||||
if [ -z "$KNOWN_HOST" ]; then
|
||||
echo "::error::GITNEXUS_DO_KNOWN_HOST secret is empty"
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$KNOWN_HOST" > ~/.ssh/known_hosts
|
||||
chmod 600 ~/.ssh/known_hosts
|
||||
|
||||
- name: Remove PR index from droplet
|
||||
if: steps.check.outputs.has_artifact == 'true'
|
||||
env:
|
||||
SSH_USER: ${{ secrets.GITNEXUS_DO_USER }}
|
||||
SSH_HOST: ${{ secrets.GITNEXUS_DO_HOST }}
|
||||
PR_NUM: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
ssh -i ~/.ssh/deploy_key "$SSH_USER@$SSH_HOST" PR_NUM="$PR_NUM" bash <<'REMOTE'
|
||||
set -e
|
||||
TARGET="/opt/gitnexus/indexes/LibreChat-pr-$PR_NUM"
|
||||
if [ -d "$TARGET" ]; then
|
||||
echo "Removing $TARGET"
|
||||
rm -rf "$TARGET"
|
||||
cd /opt/gitnexus
|
||||
docker compose up -d --force-recreate gitnexus
|
||||
echo "GitNexus restarted without PR #$PR_NUM"
|
||||
else
|
||||
echo "No index to clean up for PR #$PR_NUM (artifact existed but droplet folder did not)"
|
||||
fi
|
||||
REMOTE
|
||||
|
||||
- name: Cleanup SSH key
|
||||
if: always()
|
||||
run: rm -f ~/.ssh/deploy_key
|
||||
@@ -1,600 +0,0 @@
|
||||
# Deploys GitNexus indexes to a droplet via SSH + rsync.
|
||||
#
|
||||
# Architecture:
|
||||
# GitHub Actions (deploy)
|
||||
# 1. Resolves latest successful index runs for main, dev, and every
|
||||
# open PR that already has an index artifact (contributor-gated
|
||||
# upstream by the index workflow's author_association check)
|
||||
# 2. Downloads each matching .gitnexus/ artifact
|
||||
# 3. Rsyncs them into /opt/gitnexus/indexes/<name>/ on the droplet
|
||||
# 4. Removes any stale folders on the droplet for PRs that closed
|
||||
# (even though gitnexus-cleanup-pr.yml also handles that path,
|
||||
# this is a safety net in case the close event was missed)
|
||||
# 5. Pulls latest image, force-recreates gitnexus, reloads Caddy,
|
||||
# and polls docker health until the container reports healthy
|
||||
# The caddy container is untouched — no TLS churn.
|
||||
#
|
||||
# First-time droplet bootstrap (run once, manually):
|
||||
# 1. Create 2GB+ Ubuntu 24.04 droplet, add SSH key
|
||||
# 2. Point DNS A record for your subdomain at the droplet IP
|
||||
# 3. SSH in and run:
|
||||
# curl -fsSL https://get.docker.com | sh
|
||||
# systemctl enable --now docker
|
||||
# mkdir -p /opt/gitnexus/indexes
|
||||
# useradd -m -s /bin/bash deploy
|
||||
# usermod -aG docker deploy
|
||||
# mkdir -p /home/deploy/.ssh
|
||||
# # Add deploy pubkey to /home/deploy/.ssh/authorized_keys
|
||||
# chown -R deploy:deploy /home/deploy/.ssh /opt/gitnexus
|
||||
# chmod 700 /home/deploy/.ssh
|
||||
# ufw allow 22,80,443/tcp
|
||||
# ufw --force enable
|
||||
# 4. Copy .do/gitnexus/docker-compose.yml and Caddyfile into /opt/gitnexus/
|
||||
# 5. Create /opt/gitnexus/.env with: GITNEXUS_DOMAIN=... and API_TOKEN=...
|
||||
# 6. cd /opt/gitnexus && docker compose up -d
|
||||
#
|
||||
# Then capture the droplet's SSH host key from your workstation and
|
||||
# save it as the GITNEXUS_DO_KNOWN_HOST secret (below) so CI can pin it:
|
||||
# ssh-keyscan -H gitnexus.yourdomain.com
|
||||
#
|
||||
# GHCR image: the workflow runs `docker login ghcr.io` on the droplet
|
||||
# on every deploy using GITHUB_TOKEN, so the package can stay private.
|
||||
# If you'd rather not have CI manage droplet auth, make the package
|
||||
# public under repo Settings -> Packages.
|
||||
#
|
||||
# Required GitHub secrets:
|
||||
# GITNEXUS_DO_HOST — droplet IP or hostname
|
||||
# GITNEXUS_DO_USER — SSH user (e.g. "deploy")
|
||||
# GITNEXUS_DO_SSH_KEY — private key matching the authorized pubkey
|
||||
# GITNEXUS_DO_KNOWN_HOST — output of `ssh-keyscan -H <host>` pinning the
|
||||
# droplet's host keys (prevents MITM/TOFU risk)
|
||||
|
||||
name: GitNexus Deploy
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['GitNexus Index']
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: 'Optional PR number to post completion comment on (set by bot-triggered dispatches from gitnexus-index.yml)'
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
pull-requests: write # post completion comments on served PR indexes
|
||||
|
||||
# Global serialization. Earlier versions used per-ref concurrency with
|
||||
# cancel-in-progress so rapid pushes to the same ref coalesced but deploys
|
||||
# targeting different refs ran in parallel. That had a data race: the
|
||||
# prune-stale-indexes step computes its active_names up front, so if
|
||||
# deploy A is rsyncing /opt/gitnexus/indexes/LibreChat-pr-12580 while
|
||||
# deploy B (started slightly later with a different ref) prunes, B can
|
||||
# rm -rf a folder A is still uploading into.
|
||||
#
|
||||
# All deploys now queue behind a single group. cancel-in-progress is
|
||||
# false so a running rsync/docker-compose restart never gets killed
|
||||
# mid-operation (which would leave the droplet in a partial state).
|
||||
# The 20-minute job timeout bounds total queue depth.
|
||||
concurrency:
|
||||
group: gitnexus-deploy
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
GITNEXUS_VERSION: '1.5.3'
|
||||
IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/librechat-gitnexus
|
||||
|
||||
jobs:
|
||||
# Rebuilds the long-lived image only when Dockerfile/entrypoint/extensions
|
||||
# change. Skipped on every other run, so index-only deploys are fast.
|
||||
build-image:
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write # push image to GHCR
|
||||
outputs:
|
||||
image_tag: ${{ steps.tag.outputs.value }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Detect image changes
|
||||
id: changes
|
||||
run: |
|
||||
# Default to rebuild when we can't cleanly diff (first commit,
|
||||
# workflow_run from a PR branch where HEAD isn't the trigger, etc).
|
||||
# Rebuild on miss > skip when we should have rebuilt.
|
||||
if git rev-parse --verify HEAD~1 >/dev/null 2>&1 && \
|
||||
git diff --quiet HEAD~1 HEAD -- .do/gitnexus/Dockerfile .do/gitnexus/entrypoint.sh .do/gitnexus/install-extensions.js; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Compute image tag
|
||||
id: tag
|
||||
run: echo "value=v${{ env.GITNEXUS_VERSION }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.changes.outputs.changed == 'true' || github.event_name == 'workflow_dispatch'
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GHCR
|
||||
if: steps.changes.outputs.changed == 'true' || github.event_name == 'workflow_dispatch'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push image
|
||||
if: steps.changes.outputs.changed == 'true' || github.event_name == 'workflow_dispatch'
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .do/gitnexus
|
||||
file: .do/gitnexus/Dockerfile
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.IMAGE_NAME }}:latest
|
||||
${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.value }}
|
||||
build-args: |
|
||||
GITNEXUS_VERSION=${{ env.GITNEXUS_VERSION }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
deploy:
|
||||
needs: build-image
|
||||
runs-on: hanzo-deploy-linux-amd64
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
pull-requests: write # post deploy-complete comments on served PR indexes
|
||||
steps:
|
||||
- name: Checkout deploy config
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: .do/gitnexus
|
||||
fetch-depth: 1
|
||||
|
||||
# Resolve every index to serve. All resolutions go through
|
||||
# listArtifactsForRepo keyed by the expected artifact name, so a
|
||||
# run's branch or event type doesn't matter — we always pick the
|
||||
# freshest artifact that actually exists.
|
||||
#
|
||||
# Why this matters: a /gitnexus index command dispatches
|
||||
# gitnexus-index.yml with ref=main and an input pr_number, which
|
||||
# produces a run whose head_branch is "main" but whose artifact
|
||||
# is gitnexus-index-pr-<N>. listWorkflowRuns(branch='main') would
|
||||
# happily return that run, and we'd then try to download a
|
||||
# nonexistent gitnexus-index-main artifact from it. Querying by
|
||||
# artifact name directly avoids the whole mess.
|
||||
- name: Resolve indexes to serve
|
||||
id: resolve
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const serve = []; // [{ name, artifactName, runId }]
|
||||
|
||||
// Helper — pick the newest non-expired artifact matching a name.
|
||||
const latestArtifact = async (artifactName) => {
|
||||
const { data } = await github.rest.actions.listArtifactsForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: artifactName,
|
||||
per_page: 10,
|
||||
});
|
||||
return data.artifacts
|
||||
.filter((a) => !a.expired)
|
||||
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at))[0];
|
||||
};
|
||||
|
||||
// --- main and dev branches ---
|
||||
for (const [branch, name] of [
|
||||
['main', 'LibreChat'],
|
||||
['dev', 'LibreChat-dev'],
|
||||
]) {
|
||||
const artifactName = `gitnexus-index-${branch}`;
|
||||
const fresh = await latestArtifact(artifactName);
|
||||
if (!fresh) {
|
||||
core.warning(`No artifact found for ${branch} (expected ${artifactName})`);
|
||||
continue;
|
||||
}
|
||||
serve.push({
|
||||
name,
|
||||
artifactName,
|
||||
runId: fresh.workflow_run.id,
|
||||
});
|
||||
core.info(`${branch}: run ${fresh.workflow_run.id} -> ${name}`);
|
||||
}
|
||||
|
||||
// --- open PRs with at least one successful index run ---
|
||||
// github.paginate handles the 100-per-page ceiling automatically
|
||||
// so the resolution works on repos with 200+ concurrent open PRs.
|
||||
const openPrs = await github.paginate(github.rest.pulls.list, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
});
|
||||
core.info(`Found ${openPrs.length} open PRs`);
|
||||
|
||||
// Parallelize artifact lookups in fixed-size batches so the
|
||||
// resolve step runs in seconds instead of minutes on big repos,
|
||||
// without burning the GitHub API rate limit all at once.
|
||||
const BATCH_SIZE = 10;
|
||||
const prMatches = [];
|
||||
for (let i = 0; i < openPrs.length; i += BATCH_SIZE) {
|
||||
const batch = openPrs.slice(i, i + BATCH_SIZE);
|
||||
const results = await Promise.all(
|
||||
batch.map(async (pr) => {
|
||||
const artifactName = `gitnexus-index-pr-${pr.number}`;
|
||||
const fresh = await latestArtifact(artifactName);
|
||||
return fresh ? { pr, artifactName, fresh } : null;
|
||||
}),
|
||||
);
|
||||
for (const hit of results) {
|
||||
if (hit) prMatches.push(hit);
|
||||
}
|
||||
}
|
||||
|
||||
// Cap to the N most recent PR indexes by artifact creation time.
|
||||
// On a 10GB droplet each index is ~130MB; 3 PRs + main + dev ≈
|
||||
// 650MB of index data, leaving headroom for the ~700MB Docker image
|
||||
// and OS. Older PR indexes are evicted by the prune step.
|
||||
const MAX_PR_INDEXES = 3;
|
||||
prMatches.sort(
|
||||
(a, b) => new Date(b.fresh.created_at) - new Date(a.fresh.created_at),
|
||||
);
|
||||
const keptPrs = prMatches.slice(0, MAX_PR_INDEXES);
|
||||
const evictedPrs = prMatches.slice(MAX_PR_INDEXES);
|
||||
|
||||
for (const { pr, artifactName, fresh } of keptPrs) {
|
||||
serve.push({
|
||||
name: `LibreChat-pr-${pr.number}`,
|
||||
artifactName,
|
||||
runId: fresh.workflow_run.id,
|
||||
});
|
||||
core.info(`PR #${pr.number}: run ${fresh.workflow_run.id} -> LibreChat-pr-${pr.number}`);
|
||||
}
|
||||
if (evictedPrs.length) {
|
||||
core.info(
|
||||
`Evicted ${evictedPrs.length} older PR indexes (cap=${MAX_PR_INDEXES}): ` +
|
||||
evictedPrs.map((e) => `#${e.pr.number}`).join(', '),
|
||||
);
|
||||
}
|
||||
core.info(`Serving ${keptPrs.length} PR indexes out of ${prMatches.length} with artifacts (${openPrs.length} open PRs total)`);
|
||||
|
||||
if (!serve.length) {
|
||||
core.setFailed('No indexes to serve');
|
||||
return;
|
||||
}
|
||||
|
||||
core.setOutput('matrix', JSON.stringify(serve));
|
||||
core.setOutput('active_names', serve.map((s) => s.name).join(','));
|
||||
|
||||
- name: Download each index artifact
|
||||
env:
|
||||
MATRIX: ${{ steps.resolve.outputs.matrix }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -e
|
||||
mkdir -p staging
|
||||
# main/dev artifact download failures are fatal — a missing
|
||||
# main/dev index is a real deploy failure. PR artifact failures
|
||||
# are soft — a PR artifact deleted mid-deploy shouldn't abort
|
||||
# the whole deploy and take main/dev down with it.
|
||||
echo "$MATRIX" | jq -c '.[]' | while read -r entry; do
|
||||
name=$(echo "$entry" | jq -r '.name')
|
||||
artifact=$(echo "$entry" | jq -r '.artifactName')
|
||||
runId=$(echo "$entry" | jq -r '.runId')
|
||||
target="staging/${name}/.gitnexus"
|
||||
echo "Downloading $artifact from run $runId -> $target"
|
||||
mkdir -p "$target"
|
||||
if ! gh run download "$runId" \
|
||||
--repo "${{ github.repository }}" \
|
||||
--name "$artifact" \
|
||||
--dir "$target"; then
|
||||
case "$name" in
|
||||
LibreChat|LibreChat-dev)
|
||||
echo "::error::Failed to download critical artifact $artifact"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
# The name stays in active_names so the prune step
|
||||
# won't remove the droplet's existing copy. The old
|
||||
# index keeps being served instead of being wiped to
|
||||
# nothing — stale beats empty — but observability
|
||||
# requires an explicit notice since this path is
|
||||
# invisible in the happy-path deploy log.
|
||||
echo "::warning::Failed to download PR artifact $artifact — skipping fresh sync; previous index (if any) will continue being served from the droplet"
|
||||
rm -rf "staging/${name}"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
echo "Staged for rsync:"
|
||||
du -sh staging/*/.gitnexus/ 2>/dev/null || echo "(none)"
|
||||
|
||||
- name: Setup SSH
|
||||
env:
|
||||
SSH_KEY: ${{ secrets.GITNEXUS_DO_SSH_KEY }}
|
||||
KNOWN_HOST: ${{ secrets.GITNEXUS_DO_KNOWN_HOST }}
|
||||
run: |
|
||||
set -e
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
printf '%s\n' "$SSH_KEY" > ~/.ssh/deploy_key
|
||||
chmod 600 ~/.ssh/deploy_key
|
||||
# Pin the droplet's SSH host key from a repository secret instead
|
||||
# of trusting whatever ssh-keyscan returns at deploy time. The
|
||||
# secret is populated from `ssh-keyscan -H <host>` at bootstrap.
|
||||
if [ -z "$KNOWN_HOST" ]; then
|
||||
echo "::error::GITNEXUS_DO_KNOWN_HOST secret is empty. Run ssh-keyscan -H <host> and paste the output as this secret."
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$KNOWN_HOST" > ~/.ssh/known_hosts
|
||||
chmod 600 ~/.ssh/known_hosts
|
||||
|
||||
- name: Authenticate droplet with GHCR
|
||||
# GHCR packages pushed by GITHUB_TOKEN start private. The droplet
|
||||
# pulls the image on every deploy, so we re-authenticate it here
|
||||
# using the same short-lived token. If the package is public, this
|
||||
# step is redundant but harmless.
|
||||
#
|
||||
# The token MUST travel through SSH stdin (not as a command arg)
|
||||
# so it's never visible in the droplet's process table via
|
||||
# /proc/<pid>/cmdline. `printf '%s'` is preferred over `echo`
|
||||
# so the exact byte sequence sent is explicit — docker login
|
||||
# tolerates a trailing newline but `printf` makes the intent
|
||||
# obvious and portable across shells.
|
||||
env:
|
||||
SSH_USER: ${{ secrets.GITNEXUS_DO_USER }}
|
||||
SSH_HOST: ${{ secrets.GITNEXUS_DO_HOST }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_ACTOR: ${{ github.actor }}
|
||||
run: |
|
||||
printf '%s' "$GH_TOKEN" | ssh -i ~/.ssh/deploy_key "$SSH_USER@$SSH_HOST" \
|
||||
"docker login ghcr.io -u '$GH_ACTOR' --password-stdin"
|
||||
|
||||
- name: Upload config files
|
||||
env:
|
||||
SSH_USER: ${{ secrets.GITNEXUS_DO_USER }}
|
||||
SSH_HOST: ${{ secrets.GITNEXUS_DO_HOST }}
|
||||
run: |
|
||||
rsync -az -e "ssh -i ~/.ssh/deploy_key" \
|
||||
.do/gitnexus/docker-compose.yml \
|
||||
.do/gitnexus/Caddyfile \
|
||||
"$SSH_USER@$SSH_HOST:/opt/gitnexus/"
|
||||
|
||||
- name: Prune stale indexes then sync fresh ones
|
||||
env:
|
||||
SSH_USER: ${{ secrets.GITNEXUS_DO_USER }}
|
||||
SSH_HOST: ${{ secrets.GITNEXUS_DO_HOST }}
|
||||
ACTIVE_NAMES: ${{ steps.resolve.outputs.active_names }}
|
||||
run: |
|
||||
set -e
|
||||
# ── Step 1: prune FIRST ────────────────────────────────
|
||||
# Remove any folders on the droplet that aren't in the active set.
|
||||
# This frees disk BEFORE rsyncing new data, which matters on a
|
||||
# 10GB disk where each index is ~130MB.
|
||||
echo "Pruning stale indexes (keeping: $ACTIVE_NAMES)"
|
||||
ssh -i ~/.ssh/deploy_key "$SSH_USER@$SSH_HOST" \
|
||||
ACTIVE_NAMES="$ACTIVE_NAMES" bash <<'REMOTE'
|
||||
set -e
|
||||
cd /opt/gitnexus/indexes || exit 0
|
||||
shopt -s nullglob
|
||||
IFS=',' read -ra ACTIVE <<< "$ACTIVE_NAMES"
|
||||
for dir in */; do
|
||||
dir="${dir%/}"
|
||||
keep=false
|
||||
for a in "${ACTIVE[@]}"; do
|
||||
if [ "$dir" = "$a" ]; then keep=true; break; fi
|
||||
done
|
||||
if [ "$keep" = false ]; then
|
||||
echo "Removing stale index: $dir"
|
||||
rm -rf "$dir"
|
||||
fi
|
||||
done
|
||||
echo "Disk after prune:"
|
||||
df -h / | tail -1
|
||||
REMOTE
|
||||
|
||||
# ── Step 2: rsync-then-swap ─────────────────────────────
|
||||
# Upload each index to a temp directory, then atomically swap
|
||||
# it into place. If rsync fails, the old index survives intact
|
||||
# and the partial temp dir is cleaned up — no production data
|
||||
# is lost. The brief period where both old + new exist costs
|
||||
# ~130MB of extra disk, but the prune step already freed
|
||||
# space from evicted PR indexes so this fits on a 10GB disk.
|
||||
for dir in staging/*/; do
|
||||
[ -d "$dir" ] || continue
|
||||
name=$(basename "$dir")
|
||||
echo "Syncing $name (rsync-then-swap)"
|
||||
ssh -i ~/.ssh/deploy_key "$SSH_USER@$SSH_HOST" \
|
||||
"mkdir -p /opt/gitnexus/indexes/${name}.new"
|
||||
if rsync -az -e "ssh -i ~/.ssh/deploy_key" \
|
||||
"$dir" \
|
||||
"$SSH_USER@$SSH_HOST:/opt/gitnexus/indexes/${name}.new/"; then
|
||||
# Swap: remove old, rename new into place
|
||||
ssh -i ~/.ssh/deploy_key "$SSH_USER@$SSH_HOST" \
|
||||
"rm -rf /opt/gitnexus/indexes/$name && mv /opt/gitnexus/indexes/${name}.new /opt/gitnexus/indexes/$name"
|
||||
echo " $name swapped successfully"
|
||||
else
|
||||
# Clean up the partial temp dir
|
||||
ssh -i ~/.ssh/deploy_key "$SSH_USER@$SSH_HOST" \
|
||||
"rm -rf /opt/gitnexus/indexes/${name}.new"
|
||||
# main/dev are critical — abort the deploy so the failure
|
||||
# is visible and the container isn't restarted with stale
|
||||
# or missing data. PR indexes are best-effort.
|
||||
case "$name" in
|
||||
LibreChat|LibreChat-dev)
|
||||
echo "::error::rsync failed for critical index $name — aborting deploy"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "::warning::rsync failed for PR index $name — keeping previous index"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Pull image, restart gitnexus, reload Caddy, wait for healthy
|
||||
env:
|
||||
SSH_USER: ${{ secrets.GITNEXUS_DO_USER }}
|
||||
SSH_HOST: ${{ secrets.GITNEXUS_DO_HOST }}
|
||||
run: |
|
||||
ssh -i ~/.ssh/deploy_key "$SSH_USER@$SSH_HOST" bash <<'REMOTE'
|
||||
set -e
|
||||
cd /opt/gitnexus
|
||||
|
||||
# ── Disk cleanup ──────────────────────────────────────
|
||||
# Docker accumulates old image layers, dangling images, and
|
||||
# build cache across deploys. On a 60GB droplet with a 700MB+
|
||||
# gitnexus image, this fills the disk after ~40 deploys.
|
||||
# Prune everything not used by currently-running containers
|
||||
# BEFORE pulling the new image so the extract has room.
|
||||
echo "Disk before cleanup:"
|
||||
df -h / | tail -1
|
||||
# Omit --volumes: Caddy's caddy-data and caddy-config volumes
|
||||
# hold TLS certificates and ACME state. If Caddy happens to be
|
||||
# stopped when this runs (the workflow handles that case later),
|
||||
# --volumes would wipe them, forcing Let's Encrypt re-issuance
|
||||
# and risking rate-limit lockout (5 certs/domain/week).
|
||||
docker system prune -af 2>/dev/null || true
|
||||
echo "Disk after cleanup:"
|
||||
df -h / | tail -1
|
||||
|
||||
# Fail fast if disk is critically low even after prune
|
||||
AVAIL_MB=$(df --output=avail -m / | tail -1 | tr -d ' ')
|
||||
if [ "$AVAIL_MB" -lt 2048 ]; then
|
||||
echo "::error::Disk critically low (${AVAIL_MB}MB free). Aborting deploy."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker compose pull gitnexus
|
||||
docker compose up -d --force-recreate gitnexus
|
||||
|
||||
# Reload Caddy in-place so a changed Caddyfile takes effect
|
||||
# without losing TLS certs or restarting connections. If caddy
|
||||
# isn't running yet (first-time bootstrap), bring it up.
|
||||
if docker compose ps --status running caddy 2>/dev/null | grep -q caddy; then
|
||||
echo "Reloading Caddy config"
|
||||
docker compose exec -T caddy caddy reload --config /etc/caddy/Caddyfile || {
|
||||
echo "Caddy reload failed — forcing restart"
|
||||
docker compose up -d --force-recreate caddy
|
||||
}
|
||||
else
|
||||
echo "Caddy not running — starting"
|
||||
docker compose up -d caddy
|
||||
fi
|
||||
|
||||
# Poll gitnexus health until ready or timeout. Docker's own
|
||||
# unhealthy detection takes up to 150s (start_period 60s +
|
||||
# retries 3 * interval 30s), so the poll ceiling must clear
|
||||
# that to avoid false negatives when gitnexus legitimately
|
||||
# takes ~2.5 min to warm up.
|
||||
# Max wait = 36 sleeps * 5s = 180s (final iteration exits
|
||||
# before its sleep on failure, so 37 iterations is the
|
||||
# correct upper bound for a true 180s ceiling).
|
||||
echo "Waiting for gitnexus to report healthy..."
|
||||
for i in $(seq 1 37); do
|
||||
STATUS=$(docker inspect --format='{{.State.Health.Status}}' gitnexus 2>/dev/null || echo unknown)
|
||||
echo "[$i/37] gitnexus health: $STATUS"
|
||||
if [ "$STATUS" = "healthy" ]; then
|
||||
echo "gitnexus is healthy"
|
||||
break
|
||||
fi
|
||||
if [ "$i" -eq 37 ]; then
|
||||
echo "ERROR: gitnexus failed to become healthy after 180s"
|
||||
docker compose ps
|
||||
docker compose logs --tail 80 gitnexus
|
||||
exit 1
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
docker compose ps
|
||||
echo "--- Caddy logs (last 20 lines) ---"
|
||||
docker compose logs --tail 20 caddy || true
|
||||
echo "--- GitNexus logs (last 30 lines) ---"
|
||||
docker compose logs --tail 30 gitnexus || true
|
||||
REMOTE
|
||||
|
||||
# When the deploy was triggered by a PR command path, post a
|
||||
# terminal status comment on that one PR only. Two sub-cases:
|
||||
#
|
||||
# 1. workflow_run trigger: the PR's native auto-index run fired
|
||||
# workflow_run, so github.event.workflow_run.id is the trigger.
|
||||
# Find the matching PR via the matrix entry whose runId matches.
|
||||
#
|
||||
# 2. workflow_dispatch trigger with inputs.pr_number set: the
|
||||
# index workflow's bot-fallback path dispatched us directly
|
||||
# because workflow_run is suppressed for GITHUB_TOKEN triggers.
|
||||
# Use inputs.pr_number as the comment target.
|
||||
#
|
||||
# Broadcast-commenting on every active PR would be noise — only the
|
||||
# PR that asked for a fresh index gets a reply.
|
||||
- name: Comment on PR — deploy complete
|
||||
if: always()
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
MATRIX: ${{ steps.resolve.outputs.matrix }}
|
||||
TRIGGER_RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
DISPATCH_PR_NUMBER: ${{ github.event.inputs.pr_number }}
|
||||
DEPLOY_STATUS: ${{ job.status }}
|
||||
with:
|
||||
script: |
|
||||
let prNum = null;
|
||||
|
||||
// Case 1: dispatched directly with pr_number (bot-fallback path)
|
||||
if (process.env.DISPATCH_PR_NUMBER && process.env.DISPATCH_PR_NUMBER !== '') {
|
||||
prNum = parseInt(process.env.DISPATCH_PR_NUMBER, 10);
|
||||
}
|
||||
// Case 2: workflow_run trigger from a PR index run
|
||||
else if (context.eventName === 'workflow_run') {
|
||||
const matrix = JSON.parse(process.env.MATRIX || '[]');
|
||||
const triggerRunId = Number(process.env.TRIGGER_RUN_ID);
|
||||
const match = matrix.find(
|
||||
(m) => m.runId === triggerRunId && m.name.startsWith('LibreChat-pr-'),
|
||||
);
|
||||
if (match) {
|
||||
prNum = parseInt(match.name.replace('LibreChat-pr-', ''), 10);
|
||||
}
|
||||
}
|
||||
|
||||
if (!prNum) {
|
||||
core.info('No PR to comment on (trigger was not a PR-scoped index); skipping.');
|
||||
return;
|
||||
}
|
||||
|
||||
const deployUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
const ok = process.env.DEPLOY_STATUS === 'success';
|
||||
const body = [
|
||||
`### GitNexus: ${ok ? '🚀 deployed' : '❌ deploy failed'}`,
|
||||
'',
|
||||
ok
|
||||
? `The \`LibreChat-pr-${prNum}\` index is now live on the MCP server.`
|
||||
: `The deploy failed — the previous index (if any) continues to be served.`,
|
||||
`[Deploy run](${deployUrl})`,
|
||||
].join('\n');
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNum,
|
||||
body,
|
||||
});
|
||||
|
||||
- name: Cleanup SSH key
|
||||
if: always()
|
||||
run: rm -f ~/.ssh/deploy_key
|
||||
@@ -1,310 +0,0 @@
|
||||
name: GitNexus Index
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, dev]
|
||||
paths-ignore: ['**.md', 'docs/**', 'LICENSE', '.github/**']
|
||||
pull_request:
|
||||
branches: [main, dev]
|
||||
paths-ignore: ['**.md', 'docs/**', 'LICENSE', '.github/**']
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
embeddings:
|
||||
description: 'Enable embedding generation (slow, increases index size)'
|
||||
type: boolean
|
||||
default: false
|
||||
force:
|
||||
description: 'Force full re-index'
|
||||
type: boolean
|
||||
default: false
|
||||
# When invoked from the /gitnexus index PR command, the command
|
||||
# workflow fills these so the index is built from the PR's head
|
||||
# ref and uploaded under the PR-numbered artifact name.
|
||||
pr_number:
|
||||
description: 'PR number to index (set by /gitnexus command)'
|
||||
type: string
|
||||
default: ''
|
||||
pr_ref:
|
||||
description: 'Optional PR head ref to check out; defaults to refs/pull/<pr_number>/head when pr_number is set'
|
||||
type: string
|
||||
default: ''
|
||||
deploy_after:
|
||||
description: 'Dispatch GitNexus Deploy after a successful index run'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
# When triggered by the /gitnexus command, group by PR number so rapid
|
||||
# re-runs coalesce. Otherwise group by git ref as before.
|
||||
group: gitnexus-${{ inputs.pr_number != '' && format('pr-{0}', inputs.pr_number) || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GITNEXUS_VERSION: '1.5.3'
|
||||
|
||||
jobs:
|
||||
index:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read # read changed files to decide whether embeddings are needed
|
||||
# Push + dispatch run unconditionally. Native pull_request events
|
||||
# are restricted to PRs authored by danny-avila only — this keeps
|
||||
# automatic CI spend low on a repo with 200+ open PRs.
|
||||
#
|
||||
# Other contributors' PRs can still be indexed on demand:
|
||||
# - /gitnexus index (PR comment command, contributor-gated)
|
||||
# - workflow_dispatch (manual dispatch from Actions UI)
|
||||
# Both bypass this filter because they arrive as workflow_dispatch,
|
||||
# not pull_request.
|
||||
if: |
|
||||
github.event_name != 'pull_request' ||
|
||||
github.event.pull_request.user.login == 'danny-avila'
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Validate dispatch inputs
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
PR_NUMBER: ${{ inputs.pr_number }}
|
||||
PR_REF: ${{ inputs.pr_ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -n "$PR_NUMBER" ]; then
|
||||
if [[ ! "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
|
||||
echo "::error::pr_number must be numeric"
|
||||
exit 1
|
||||
fi
|
||||
EXPECTED_REF="refs/pull/${PR_NUMBER}/head"
|
||||
if [ -n "$PR_REF" ] && [ "$PR_REF" != "$EXPECTED_REF" ]; then
|
||||
echo "::error::pr_ref must match ${EXPECTED_REF}"
|
||||
exit 1
|
||||
fi
|
||||
elif [ -n "$PR_REF" ]; then
|
||||
echo "::error::pr_ref requires pr_number"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Resolve GitNexus flags
|
||||
id: flags
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
ENABLE_EMBEDDINGS_INPUT: ${{ inputs.embeddings }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUM: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Decide whether to generate embeddings. Rules:
|
||||
# push (main/dev) -> always embed
|
||||
# pull_request -> embed ONLY when the PR changes files
|
||||
# under paths that also trigger backend
|
||||
# or frontend unit tests (api/, client/,
|
||||
# packages/). Docs/config-only PRs skip
|
||||
# embeddings to save ~3-5 min of CI.
|
||||
# workflow_dispatch -> respect the explicit `embeddings` input
|
||||
# (default false). This also covers the
|
||||
# /gitnexus index [embeddings] command.
|
||||
ENABLE_EMBEDDINGS=false
|
||||
case "$EVENT_NAME" in
|
||||
workflow_dispatch)
|
||||
[ "$ENABLE_EMBEDDINGS_INPUT" = "true" ] && ENABLE_EMBEDDINGS=true
|
||||
;;
|
||||
push)
|
||||
ENABLE_EMBEDDINGS=true
|
||||
;;
|
||||
pull_request)
|
||||
CHANGED=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUM/files" \
|
||||
--paginate --jq '.[].filename' 2>/dev/null || echo "")
|
||||
if printf '%s\n' "$CHANGED" | grep -qE '^(api/|client/|packages/)'; then
|
||||
echo "PR #$PR_NUM touches unit-test paths (api|client|packages) — enabling embeddings"
|
||||
ENABLE_EMBEDDINGS=true
|
||||
else
|
||||
echo "PR #$PR_NUM does not touch unit-test paths — graph-only index"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$ENABLE_EMBEDDINGS" = "true" ]; then
|
||||
echo "enable_embeddings=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "enable_embeddings=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24.16.0'
|
||||
|
||||
- name: Install GitNexus CLI
|
||||
working-directory: ${{ runner.temp }}
|
||||
env:
|
||||
NPM_CONFIG_AUDIT: false
|
||||
NPM_CONFIG_CACHE: ${{ runner.temp }}/gitnexus-npm-cache
|
||||
NPM_CONFIG_FUND: false
|
||||
NPM_CONFIG_GLOBALCONFIG: ${{ runner.temp }}/gitnexus-cli/global-npmrc
|
||||
NPM_CONFIG_REGISTRY: https://registry.npmjs.org/
|
||||
NPM_CONFIG_USERCONFIG: ${{ runner.temp }}/gitnexus-cli/.npmrc
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p "$RUNNER_TEMP/gitnexus-cli" "$RUNNER_TEMP/gitnexus-npm-cache"
|
||||
: > "$RUNNER_TEMP/gitnexus-cli/global-npmrc"
|
||||
printf '%s\n' \
|
||||
'registry=https://registry.npmjs.org/' \
|
||||
'audit=false' \
|
||||
'fund=false' \
|
||||
> "$RUNNER_TEMP/gitnexus-cli/.npmrc"
|
||||
# Keep GitNexus' native DB dependency deterministic in fresh CI installs.
|
||||
npm install \
|
||||
--prefix "$RUNNER_TEMP/gitnexus-cli" \
|
||||
--no-save \
|
||||
--no-package-lock \
|
||||
"gitnexus@${{ env.GITNEXUS_VERSION }}" \
|
||||
"@ladybugdb/core@0.15.2"
|
||||
test -x "$RUNNER_TEMP/gitnexus-cli/node_modules/.bin/gitnexus"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# When the /gitnexus command dispatches us with a pr_ref, it's
|
||||
# a refs/pull/<N>/head ref that GitHub mirrors into the base
|
||||
# repo for every PR, so checkout works for fork PRs too. When
|
||||
# pr_ref is empty (native push/pull_request), fall back to the
|
||||
# default ref actions/checkout would use.
|
||||
ref: ${{ inputs.pr_ref || (inputs.pr_number != '' && format('refs/pull/{0}/head', inputs.pr_number) || '') }}
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run GitNexus Analyze
|
||||
working-directory: ${{ runner.temp }}
|
||||
env:
|
||||
ENABLE_EMBEDDINGS: ${{ steps.flags.outputs.enable_embeddings }}
|
||||
FORCE: ${{ inputs.force }}
|
||||
GITNEXUS_BIN: ${{ runner.temp }}/gitnexus-cli/node_modules/.bin/gitnexus
|
||||
NPM_CONFIG_AUDIT: false
|
||||
NPM_CONFIG_CACHE: ${{ runner.temp }}/gitnexus-npm-cache
|
||||
NPM_CONFIG_FUND: false
|
||||
NPM_CONFIG_GLOBALCONFIG: ${{ runner.temp }}/gitnexus-cli/global-npmrc
|
||||
NPM_CONFIG_REGISTRY: https://registry.npmjs.org/
|
||||
NPM_CONFIG_USERCONFIG: ${{ runner.temp }}/gitnexus-cli/.npmrc
|
||||
run: |
|
||||
set -euo pipefail
|
||||
FLAGS=(--skip-agents-md --verbose)
|
||||
|
||||
if [ "$ENABLE_EMBEDDINGS" = "true" ]; then
|
||||
FLAGS+=(--embeddings)
|
||||
fi
|
||||
if [ "$FORCE" = "true" ]; then
|
||||
FLAGS+=(--force)
|
||||
fi
|
||||
"$GITNEXUS_BIN" analyze "$GITHUB_WORKSPACE" "${FLAGS[@]}"
|
||||
|
||||
- name: Verify index
|
||||
run: |
|
||||
if [ ! -d ".gitnexus" ] || [ ! -f ".gitnexus/meta.json" ]; then
|
||||
echo "::error::GitNexus index was not created"
|
||||
exit 1
|
||||
fi
|
||||
echo "::group::Index metadata"
|
||||
cat .gitnexus/meta.json
|
||||
echo ""
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Upload GitNexus index
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
# Artifact naming order of precedence:
|
||||
# 1. /gitnexus command dispatch: inputs.pr_number -> pr-<N>
|
||||
# 2. Native pull_request event: github.event.pull_request.number
|
||||
# 3. Push or manual dispatch without pr_number: github.ref_name
|
||||
name: >-
|
||||
gitnexus-index-${{
|
||||
inputs.pr_number != ''
|
||||
&& format('pr-{0}', inputs.pr_number)
|
||||
|| (github.event_name == 'pull_request'
|
||||
&& format('pr-{0}', github.event.pull_request.number)
|
||||
|| github.ref_name)
|
||||
}}
|
||||
path: .gitnexus/
|
||||
include-hidden-files: true
|
||||
retention-days: 30
|
||||
|
||||
post-index:
|
||||
needs: index
|
||||
if: |
|
||||
always() &&
|
||||
(inputs.pr_number != '' ||
|
||||
inputs.deploy_after)
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write # dispatch gitnexus-deploy.yml when deploy_after is set
|
||||
pull-requests: write # post completion comments for /gitnexus command runs
|
||||
steps:
|
||||
# GitHub suppresses workflow_run events for workflow runs triggered
|
||||
# by GITHUB_TOKEN (to prevent recursive chaining). Command-triggered
|
||||
# index runs opt into a deploy by setting deploy_after=true.
|
||||
- name: Trigger deploy workflow after command-triggered runs
|
||||
if: inputs.deploy_after && needs.index.result == 'success'
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
PR_NUMBER: ${{ inputs.pr_number }}
|
||||
with:
|
||||
script: |
|
||||
core.info('deploy_after=true; dispatching gitnexus-deploy.yml manually.');
|
||||
// Pass pr_number through so the deploy workflow knows which
|
||||
// PR to post its completion comment on (for /gitnexus
|
||||
// command runs this will be set; for other bot dispatches
|
||||
// it's empty and the deploy step falls back to matrix match).
|
||||
await github.rest.actions.createWorkflowDispatch({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
workflow_id: 'gitnexus-deploy.yml',
|
||||
ref: 'main',
|
||||
inputs: {
|
||||
pr_number: process.env.PR_NUMBER || '',
|
||||
},
|
||||
});
|
||||
|
||||
# Reply on the PR when the /gitnexus command path runs so the
|
||||
# requester knows the index step finished. This fires when
|
||||
# inputs.pr_number is set and reports the index job result. A
|
||||
# separate comment posts from the deploy workflow when the live
|
||||
# server has the fresh index.
|
||||
- name: Comment on PR — index complete
|
||||
if: inputs.pr_number != ''
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
EMBEDDINGS_INPUT: ${{ inputs.embeddings }}
|
||||
INDEX_RESULT: ${{ needs.index.result }}
|
||||
PR_NUMBER: ${{ inputs.pr_number }}
|
||||
with:
|
||||
script: |
|
||||
const indexSucceeded = process.env.INDEX_RESULT === 'success';
|
||||
const outcome = indexSucceeded ? '✅ indexed' : '❌ index failed';
|
||||
const prNum = parseInt(process.env.PR_NUMBER || '', 10);
|
||||
if (!Number.isSafeInteger(prNum)) {
|
||||
core.setFailed(`Invalid PR number: ${process.env.PR_NUMBER}`);
|
||||
return;
|
||||
}
|
||||
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
const embeddingsFlag = process.env.EMBEDDINGS_INPUT === 'true' ? 'with embeddings' : 'graph-only';
|
||||
const body = [
|
||||
`### GitNexus: ${outcome}`,
|
||||
``,
|
||||
`PR #${prNum} was indexed ${embeddingsFlag}.`,
|
||||
`[Index run](${runUrl})`,
|
||||
'',
|
||||
indexSucceeded
|
||||
? '⏳ Waiting for deploy to serve the fresh index…'
|
||||
: '_Index run failed — the previous index (if any) continues to be served._',
|
||||
].join('\n');
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNum,
|
||||
body,
|
||||
});
|
||||
@@ -1,141 +0,0 @@
|
||||
# Responds to `/gitnexus index` comments on pull requests.
|
||||
#
|
||||
# Gated to the same author_association roles (OWNER, MEMBER, COLLABORATOR)
|
||||
# as the automatic PR index trigger, but applied to the COMMENTER, not
|
||||
# the PR author. This intentionally lets a contributor index a PR from
|
||||
# a non-contributor / first-time fork author — the contributor takes
|
||||
# responsibility for the trust boundary by typing the command.
|
||||
#
|
||||
# When a matching comment lands on a PR, this workflow dispatches
|
||||
# `gitnexus-index.yml` with the PR number and the `refs/pull/<N>/head`
|
||||
# ref so indexing works for fork PRs too (GitHub mirrors every PR's
|
||||
# head ref into the base repo regardless of which fork it originated
|
||||
# from, so actions/checkout can always resolve it).
|
||||
#
|
||||
# Use cases:
|
||||
# - Re-index a PR after a rebase without pushing a new commit
|
||||
# - Index a docs-only PR that was skipped by paths-ignore
|
||||
# - Index a non-contributor (fork) PR that the auto-trigger skipped
|
||||
# - Re-run a failed index
|
||||
#
|
||||
# Supported commands:
|
||||
# /gitnexus index — index the PR with embeddings (default)
|
||||
# /gitnexus index embeddings — explicit form of the above; same effect
|
||||
# /gitnexus index fast — graph-only index (skip embeddings), for
|
||||
# a quick re-index without waiting ~5 min
|
||||
# of embedding generation
|
||||
|
||||
name: GitNexus PR Command
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
actions: write # needed to dispatch gitnexus-index.yml
|
||||
|
||||
concurrency:
|
||||
group: gitnexus-pr-command-${{ github.event.issue.number }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
dispatch:
|
||||
# Only run for PR comments that start with /gitnexus from trusted
|
||||
# commenters. Intentionally checks the COMMENTER's association so a
|
||||
# contributor can index a non-contributor's PR on demand.
|
||||
if: |
|
||||
github.event.issue.pull_request != null &&
|
||||
startsWith(github.event.comment.body, '/gitnexus') &&
|
||||
(github.event.comment.author_association == 'OWNER' ||
|
||||
github.event.comment.author_association == 'MEMBER' ||
|
||||
github.event.comment.author_association == 'COLLABORATOR')
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Parse command and resolve PR head ref
|
||||
id: parse
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const body = context.payload.comment.body.trim();
|
||||
const match = body.match(/^\/gitnexus\s+(\w+)(?:\s+(\w+))?/);
|
||||
if (!match) {
|
||||
core.setFailed(`Unrecognized command: ${body}. Try: /gitnexus index [fast]`);
|
||||
return;
|
||||
}
|
||||
const [, subcommand, modifier] = match;
|
||||
if (subcommand !== 'index') {
|
||||
core.setFailed(`Unknown subcommand: ${subcommand}. Only 'index' is supported.`);
|
||||
return;
|
||||
}
|
||||
// Default to embeddings on — a contributor typing the command
|
||||
// has already decided they want a full re-index. The `fast`
|
||||
// modifier is the explicit opt-out for graph-only runs.
|
||||
// `embeddings` is accepted as a no-op alias for backwards
|
||||
// compat with the previous command form.
|
||||
let embeddings = 'true';
|
||||
if (modifier === 'fast' || modifier === 'graph-only' || modifier === 'no-embeddings') {
|
||||
embeddings = 'false';
|
||||
}
|
||||
|
||||
// Use refs/pull/<N>/head instead of the raw head SHA. GitHub
|
||||
// mirrors every PR's head into the base repo as this ref, so
|
||||
// actions/checkout can always resolve it — even for PRs from
|
||||
// forks whose raw SHAs don't exist in the base repo.
|
||||
const prNum = context.payload.issue.number;
|
||||
core.setOutput('pr_number', String(prNum));
|
||||
core.setOutput('pr_ref', `refs/pull/${prNum}/head`);
|
||||
core.setOutput('embeddings', embeddings);
|
||||
core.info(
|
||||
`Dispatching index for PR #${prNum} at refs/pull/${prNum}/head (embeddings=${embeddings}, modifier=${modifier || '(none)'})`,
|
||||
);
|
||||
|
||||
- name: Dispatch gitnexus-index workflow
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
EMBEDDINGS: ${{ steps.parse.outputs.embeddings }}
|
||||
PR_NUMBER: ${{ steps.parse.outputs.pr_number }}
|
||||
PR_REF: ${{ steps.parse.outputs.pr_ref }}
|
||||
with:
|
||||
script: |
|
||||
const prNumber = process.env.PR_NUMBER || '';
|
||||
const prRef = process.env.PR_REF || '';
|
||||
const embeddings = process.env.EMBEDDINGS || 'false';
|
||||
if (!/^[0-9]+$/.test(prNumber)) {
|
||||
core.setFailed(`Invalid PR number: ${prNumber}`);
|
||||
return;
|
||||
}
|
||||
if (prRef !== `refs/pull/${prNumber}/head`) {
|
||||
core.setFailed(`Invalid PR ref: ${prRef}`);
|
||||
return;
|
||||
}
|
||||
if (!['true', 'false'].includes(embeddings)) {
|
||||
core.setFailed(`Invalid embeddings value: ${embeddings}`);
|
||||
return;
|
||||
}
|
||||
await github.rest.actions.createWorkflowDispatch({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
workflow_id: 'gitnexus-index.yml',
|
||||
ref: 'main',
|
||||
inputs: {
|
||||
pr_number: prNumber,
|
||||
pr_ref: prRef,
|
||||
embeddings,
|
||||
force: 'false',
|
||||
deploy_after: 'true',
|
||||
},
|
||||
});
|
||||
|
||||
- name: React to the comment
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
await github.rest.reactions.createForIssueComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: context.payload.comment.id,
|
||||
content: 'rocket',
|
||||
});
|
||||
@@ -4,17 +4,16 @@ name: Build Helm Charts on Tag
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "chart-*"
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
runs-on: hanzo-deploy-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -27,49 +26,15 @@ jobs:
|
||||
uses: azure/setup-helm@v4
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
- name: Build Subchart Deps
|
||||
run: |
|
||||
cd helm/librechat
|
||||
helm dependency build
|
||||
cd ../librechat-rag-api
|
||||
helm dependency build
|
||||
cd helm/hanzochat-rag-api
|
||||
helm dependency build
|
||||
|
||||
- name: Get Chart Version
|
||||
id: chart-version
|
||||
run: |
|
||||
CHART_VERSION=$(echo "${{ github.ref_name }}" | cut -d'-' -f2)
|
||||
echo "CHART_VERSION=${CHART_VERSION}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Log in to GitHub Container Registry
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
- name: Run chart-releaser
|
||||
uses: helm/chart-releaser-action@v1.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Run Helm OCI Charts Releaser
|
||||
# This is for the librechat chart
|
||||
- name: Release Helm OCI Charts for librechat
|
||||
uses: appany/helm-oci-chart-releaser@v0.5.0
|
||||
with:
|
||||
name: librechat
|
||||
repository: ${{ github.actor }}/librechat-chart
|
||||
tag: ${{ steps.chart-version.outputs.CHART_VERSION }}
|
||||
path: helm/librechat
|
||||
registry: ghcr.io
|
||||
registry_username: ${{ github.actor }}
|
||||
registry_password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# this is for the librechat-rag-api chart
|
||||
- name: Release Helm OCI Charts for librechat-rag-api
|
||||
uses: appany/helm-oci-chart-releaser@v0.5.0
|
||||
with:
|
||||
name: librechat-rag-api
|
||||
repository: ${{ github.actor }}/librechat-chart
|
||||
tag: ${{ steps.chart-version.outputs.CHART_VERSION }}
|
||||
path: helm/librechat-rag-api
|
||||
registry: ghcr.io
|
||||
registry_username: ${{ github.actor }}
|
||||
registry_password: ${{ secrets.GITHUB_TOKEN }}
|
||||
charts_dir: helm
|
||||
skip_existing: true
|
||||
env:
|
||||
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
@@ -1,27 +1,20 @@
|
||||
name: Detect Unused i18next Strings
|
||||
|
||||
# This workflow checks for unused i18n keys in translation files.
|
||||
# It has special handling for:
|
||||
# - com_ui_special_var_* keys that are dynamically constructed
|
||||
# - com_agents_category_* keys that are stored in the database and used dynamically
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "client/src/**"
|
||||
- "api/**"
|
||||
- "packages/data-provider/src/**"
|
||||
- "packages/client/**"
|
||||
- "packages/data-schemas/src/**"
|
||||
|
||||
jobs:
|
||||
detect-unused-i18n-keys:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Find unused i18next keys
|
||||
id: find-unused
|
||||
@@ -30,7 +23,7 @@ jobs:
|
||||
|
||||
# Define paths
|
||||
I18N_FILE="client/src/locales/en/translation.json"
|
||||
SOURCE_DIRS=("client/src" "api" "packages/data-provider/src" "packages/client" "packages/data-schemas/src")
|
||||
SOURCE_DIRS=("client/src" "api" "packages/data-provider/src")
|
||||
|
||||
# Check if translation file exists
|
||||
if [[ ! -f "$I18N_FILE" ]]; then
|
||||
@@ -58,31 +51,6 @@ jobs:
|
||||
fi
|
||||
done
|
||||
|
||||
# Also check if the key is directly used somewhere
|
||||
if [[ "$FOUND" == false ]]; then
|
||||
for DIR in "${SOURCE_DIRS[@]}"; do
|
||||
if grep -r --include=\*.{js,jsx,ts,tsx} -q "$KEY" "$DIR"; then
|
||||
FOUND=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
# Special case for agent category keys that are dynamically used from database
|
||||
elif [[ "$KEY" == com_agents_category_* ]]; then
|
||||
# Check if agent category localization is being used
|
||||
for DIR in "${SOURCE_DIRS[@]}"; do
|
||||
# Check for dynamic category label/description usage
|
||||
if grep -r --include=\*.{js,jsx,ts,tsx} -E "category\.(label|description).*startsWith.*['\"]com_" "$DIR" > /dev/null 2>&1 || \
|
||||
# Check for the method that defines these keys
|
||||
grep -r --include=\*.{js,jsx,ts,tsx} "ensureDefaultCategories" "$DIR" > /dev/null 2>&1 || \
|
||||
# Check for direct usage in agentCategory.ts
|
||||
grep -r --include=\*.ts -E "label:.*['\"]$KEY['\"]" "$DIR" > /dev/null 2>&1 || \
|
||||
grep -r --include=\*.ts -E "description:.*['\"]$KEY['\"]" "$DIR" > /dev/null 2>&1; then
|
||||
FOUND=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Also check if the key is directly used somewhere
|
||||
if [[ "$FOUND" == false ]]; then
|
||||
for DIR in "${SOURCE_DIRS[@]}"; do
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
import csv
|
||||
import os
|
||||
from github import Github
|
||||
|
||||
|
||||
def interpret_results(csv_file):
|
||||
with open(csv_file, newline="") as csvfile:
|
||||
csvreader = csv.DictReader(csvfile)
|
||||
rows = list(csvreader)
|
||||
"""
|
||||
in this csv reader
|
||||
- Create 1 new column "Status"
|
||||
- if a row has a median response time < 300 and an average response time < 300, Status = "Passed ✅"
|
||||
- if a row has a median response time >= 300 or an average response time >= 300, Status = "Failed ❌"
|
||||
- Order the table in this order Name, Status, Median Response Time, Average Response Time, Requests/s,Failures/s, Min Response Time, Max Response Time, all other columns
|
||||
"""
|
||||
|
||||
# Add a new column "Status"
|
||||
for row in rows:
|
||||
median_response_time = float(
|
||||
row["Median Response Time"].strip().rstrip("ms")
|
||||
)
|
||||
average_response_time = float(
|
||||
row["Average Response Time"].strip().rstrip("s")
|
||||
)
|
||||
|
||||
request_count = int(row["Request Count"])
|
||||
failure_count = int(row["Failure Count"])
|
||||
|
||||
failure_percent = round((failure_count / request_count) * 100, 2)
|
||||
|
||||
# Determine status based on conditions
|
||||
if (
|
||||
median_response_time < 300
|
||||
and average_response_time < 300
|
||||
and failure_percent < 5
|
||||
):
|
||||
row["Status"] = "Passed ✅"
|
||||
else:
|
||||
row["Status"] = "Failed ❌"
|
||||
|
||||
# Construct Markdown table header
|
||||
markdown_table = "| Name | Status | Median Response Time (ms) | Average Response Time (ms) | Requests/s | Failures/s | Request Count | Failure Count | Min Response Time (ms) | Max Response Time (ms) |"
|
||||
markdown_table += (
|
||||
"\n| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |"
|
||||
)
|
||||
|
||||
# Construct Markdown table rows
|
||||
for row in rows:
|
||||
markdown_table += f"\n| {row['Name']} | {row['Status']} | {row['Median Response Time']} | {row['Average Response Time']} | {row['Requests/s']} | {row['Failures/s']} | {row['Request Count']} | {row['Failure Count']} | {row['Min Response Time']} | {row['Max Response Time']} |"
|
||||
print("markdown table: ", markdown_table)
|
||||
return markdown_table
|
||||
|
||||
|
||||
def _get_docker_run_command_stable_release(release_version):
|
||||
return f"""
|
||||
\n\n
|
||||
## Docker Run LiteLLM Proxy
|
||||
|
||||
```
|
||||
docker run \\
|
||||
-e STORE_MODEL_IN_DB=True \\
|
||||
-p 4000:4000 \\
|
||||
ghcr.io/berriai/litellm:litellm_stable_release_branch-{release_version}
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
def _get_docker_run_command(release_version):
|
||||
return f"""
|
||||
\n\n
|
||||
## Docker Run LiteLLM Proxy
|
||||
|
||||
```
|
||||
docker run \\
|
||||
-e STORE_MODEL_IN_DB=True \\
|
||||
-p 4000:4000 \\
|
||||
ghcr.io/berriai/litellm:main-{release_version}
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
def get_docker_run_command(release_version):
|
||||
if "stable" in release_version:
|
||||
return _get_docker_run_command_stable_release(release_version)
|
||||
else:
|
||||
return _get_docker_run_command(release_version)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
csv_file = "load_test_stats.csv" # Change this to the path of your CSV file
|
||||
markdown_table = interpret_results(csv_file)
|
||||
|
||||
# Update release body with interpreted results
|
||||
github_token = os.getenv("GITHUB_TOKEN")
|
||||
g = Github(github_token)
|
||||
repo = g.get_repo(
|
||||
"BerriAI/litellm"
|
||||
) # Replace with your repository's username and name
|
||||
latest_release = repo.get_latest_release()
|
||||
print("got latest release: ", latest_release)
|
||||
print(latest_release.title)
|
||||
print(latest_release.tag_name)
|
||||
|
||||
release_version = latest_release.title
|
||||
|
||||
print("latest release body: ", latest_release.body)
|
||||
print("markdown table: ", markdown_table)
|
||||
|
||||
# check if "Load Test LiteLLM Proxy Results" exists
|
||||
existing_release_body = latest_release.body
|
||||
if "Load Test LiteLLM Proxy Results" in latest_release.body:
|
||||
# find the "Load Test LiteLLM Proxy Results" section and delete it
|
||||
start_index = latest_release.body.find("Load Test LiteLLM Proxy Results")
|
||||
existing_release_body = latest_release.body[:start_index]
|
||||
|
||||
docker_run_command = get_docker_run_command(release_version)
|
||||
print("docker run command: ", docker_run_command)
|
||||
|
||||
new_release_body = (
|
||||
existing_release_body
|
||||
+ docker_run_command
|
||||
+ "\n\n"
|
||||
+ "### Don't want to maintain your internal proxy? get in touch 🎉"
|
||||
+ "\nHosted Proxy Alpha: https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat"
|
||||
+ "\n\n"
|
||||
+ "## Load Test LiteLLM Proxy Results"
|
||||
+ "\n\n"
|
||||
+ markdown_table
|
||||
)
|
||||
print("new release body: ", new_release_body)
|
||||
try:
|
||||
latest_release.update_release(
|
||||
name=latest_release.tag_name,
|
||||
message=new_release_body,
|
||||
)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
@@ -1,17 +0,0 @@
|
||||
name: Label ML Ops Team Issues
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
|
||||
jobs:
|
||||
add-mlops-label:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
steps:
|
||||
- name: Check if ML Ops Team is selected
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
if: contains(github.event.issue.body, '### Are you a ML Ops Team?') && contains(github.event.issue.body, 'Yes')
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
labels: "mlops user request"
|
||||
@@ -1,89 +0,0 @@
|
||||
name: LLM Translation Tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_candidate_tag:
|
||||
description: 'Release candidate tag/version'
|
||||
required: true
|
||||
type: string
|
||||
push:
|
||||
tags:
|
||||
- 'v*-rc*' # Triggers on release candidate tags like v1.0.0-rc1
|
||||
|
||||
jobs:
|
||||
run-llm-translation-tests:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.inputs.release_candidate_tag || github.ref }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: latest
|
||||
virtualenvs-create: true
|
||||
virtualenvs-in-project: true
|
||||
|
||||
- name: Cache Poetry dependencies
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pypoetry
|
||||
.venv
|
||||
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-poetry-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
poetry install --with dev
|
||||
poetry run pip install pytest-xdist pytest-timeout
|
||||
|
||||
- name: Create test results directory
|
||||
run: mkdir -p test-results
|
||||
|
||||
- name: Run LLM Translation Tests
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
AZURE_API_KEY: ${{ secrets.AZURE_API_KEY }}
|
||||
AZURE_API_BASE: ${{ secrets.AZURE_API_BASE }}
|
||||
AZURE_API_VERSION: ${{ secrets.AZURE_API_VERSION }}
|
||||
# Add other API keys as needed
|
||||
run: |
|
||||
python .github/workflows/run_llm_translation_tests.py \
|
||||
--tag "${{ github.event.inputs.release_candidate_tag || github.ref_name }}" \
|
||||
--commit "${{ github.sha }}" \
|
||||
|| true # Continue even if tests fail
|
||||
|
||||
- name: Display test summary
|
||||
if: always()
|
||||
run: |
|
||||
if [ -f "test-results/llm_translation_report.md" ]; then
|
||||
echo "Test report generated successfully!"
|
||||
echo "Artifact will contain:"
|
||||
echo "- test-results/junit.xml (JUnit XML results)"
|
||||
echo "- test-results/llm_translation_report.md (Beautiful markdown report)"
|
||||
else
|
||||
echo "Warning: Test report was not generated"
|
||||
fi
|
||||
|
||||
- name: Upload test artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
if: always()
|
||||
with:
|
||||
name: LLM-Translation-Artifact-${{ github.event.inputs.release_candidate_tag || github.ref_name }}
|
||||
path: test-results/
|
||||
retention-days: 30
|
||||
@@ -9,13 +9,13 @@ on:
|
||||
jobs:
|
||||
sync-translations:
|
||||
name: Sync Translation Keys with Locize
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set Up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
create-pull-request:
|
||||
name: Create Translation PR on Version Published
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
needs: sync-translations
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -44,25 +44,25 @@ jobs:
|
||||
steps:
|
||||
# 1. Check out the repository.
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# 2. Download translation files from locize.
|
||||
- name: Download Translations from locize
|
||||
uses: locize/download@v3
|
||||
uses: locize/download@v1
|
||||
with:
|
||||
project-id: ${{ secrets.LOCIZE_PROJECT_ID }}
|
||||
path: "client/src/locales"
|
||||
|
||||
# 3. Create a Pull Request using built-in functionality.
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
sign-commits: true
|
||||
commit-message: "🌍 i18n: Update translation.json with latest translations"
|
||||
base: main
|
||||
branch: i18n/locize-translation-update
|
||||
reviewers: danny-avila
|
||||
reviewers: hanzoai
|
||||
title: "🌍 i18n: Update translation.json with latest translations"
|
||||
body: |
|
||||
**Description**:
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
from locust import HttpUser, task, between
|
||||
|
||||
|
||||
class MyUser(HttpUser):
|
||||
wait_time = between(1, 5)
|
||||
|
||||
@task
|
||||
def chat_completion(self):
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer sk-8N1tLOOyH8TIxwOLahhIVg",
|
||||
# Include any additional headers you may need for authentication, etc.
|
||||
}
|
||||
|
||||
# Customize the payload with "model" and "messages" keys
|
||||
payload = {
|
||||
"model": "fake-openai-endpoint",
|
||||
"messages": [
|
||||
{"role": "system", "content": "You are a chat bot."},
|
||||
{"role": "user", "content": "Hello, how are you?"},
|
||||
],
|
||||
# Add more data as necessary
|
||||
}
|
||||
|
||||
# Make a POST request to the "chat/completions" endpoint
|
||||
response = self.client.post("chat/completions", json=payload, headers=headers)
|
||||
|
||||
# Print or log the response if needed
|
||||
@@ -3,46 +3,27 @@ name: Docker Compose Build Latest Main Image Tag (Manual Dispatch)
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: api-build
|
||||
file: Dockerfile.multi
|
||||
image_name: librechat-api
|
||||
image_name: hanzochat-api
|
||||
- target: node
|
||||
file: Dockerfile
|
||||
image_name: librechat
|
||||
image_name: hanzochat
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Fetch tags and set the latest tag
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch --tags --force
|
||||
LATEST_TAG=$(git tag --list 'v[0-9]*' --sort=-v:refname | grep -E '^v[0-9]+[.][0-9]+[.][0-9]+$' | head -n 1)
|
||||
if [ -z "$LATEST_TAG" ]; then
|
||||
echo "::error::No stable v<semver> tag found"
|
||||
exit 1
|
||||
fi
|
||||
printf 'LATEST_TAG=%s\n' "$LATEST_TAG" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Compute build metadata
|
||||
run: |
|
||||
printf 'BUILD_COMMIT=%s\n' "$(git rev-parse HEAD)" >> "$GITHUB_ENV"
|
||||
printf 'BUILD_BRANCH=main\n' >> "$GITHUB_ENV"
|
||||
printf 'BUILD_DATE=%s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_ENV"
|
||||
git fetch --tags
|
||||
echo "LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)" >> $GITHUB_ENV
|
||||
|
||||
# Set up QEMU
|
||||
- name: Set up QEMU
|
||||
@@ -54,7 +35,7 @@ jobs:
|
||||
|
||||
# Log in to GitHub Container Registry
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -86,7 +67,3 @@ jobs:
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
target: ${{ matrix.target }}
|
||||
build-args: |
|
||||
BUILD_COMMIT=${{ env.BUILD_COMMIT }}
|
||||
BUILD_BRANCH=${{ env.BUILD_BRANCH }}
|
||||
BUILD_DATE=${{ env.BUILD_DATE }}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
name: Publish Dev Release to PyPI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish-dev-release:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.8 # Adjust the Python version as needed
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install toml twine
|
||||
|
||||
- name: Read version from pyproject.toml
|
||||
id: read-version
|
||||
run: |
|
||||
version=$(python -c 'import toml; print(toml.load("pyproject.toml")["tool"]["commitizen"]["version"])')
|
||||
printf "LITELLM_VERSION=%s" "$version" >> $GITHUB_ENV
|
||||
|
||||
- name: Check if version exists on PyPI
|
||||
id: check-version
|
||||
run: |
|
||||
set -e
|
||||
if twine check --repository-url https://pypi.org/simple/ "litellm==$LITELLM_VERSION" >/dev/null 2>&1; then
|
||||
echo "Version $LITELLM_VERSION already exists on PyPI. Skipping publish."
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
name: PR Checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
name: Detect Changes
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
client: ${{ steps.filter.outputs.client }}
|
||||
api: ${{ steps.filter.outputs.api }}
|
||||
packages: ${{ steps.filter.outputs.packages }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
client:
|
||||
- 'client/**'
|
||||
- 'package.json'
|
||||
api:
|
||||
- 'api/**'
|
||||
- 'package.json'
|
||||
packages:
|
||||
- 'packages/**'
|
||||
|
||||
test-client:
|
||||
name: Client Tests
|
||||
needs: changes
|
||||
if: needs.changes.outputs.client == 'true' || needs.changes.outputs.packages == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build dependencies
|
||||
run: |
|
||||
npm run build:data-provider
|
||||
npm run build:data-schemas
|
||||
|
||||
- name: Run client tests
|
||||
run: NODE_ENV=test npm run test:client -- --passWithNoTests
|
||||
|
||||
- name: Comment PR (Client)
|
||||
uses: actions/github-script@v7
|
||||
if: always()
|
||||
with:
|
||||
script: |
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
});
|
||||
const botComment = comments.find(comment =>
|
||||
comment.user.type === 'Bot' && comment.body.includes('Client Test Results')
|
||||
);
|
||||
|
||||
const body = `### 🧪 Client Test Results
|
||||
|
||||
${context.job.status === 'success' ? '✅ All client tests passed!' : '❌ Client tests failed'}
|
||||
|
||||
[View full test results](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})`;
|
||||
|
||||
if (botComment) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: botComment.id,
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
test-api:
|
||||
name: API Tests
|
||||
needs: changes
|
||||
if: needs.changes.outputs.api == 'true' || needs.changes.outputs.packages == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
mongodb:
|
||||
image: mongo:7.0
|
||||
ports:
|
||||
- 27017:27017
|
||||
options: >-
|
||||
--health-cmd "mongosh --eval 'db.adminCommand(\"ping\")'"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build dependencies
|
||||
run: |
|
||||
npm run build:data-provider
|
||||
npm run build:data-schemas
|
||||
npm run build:api
|
||||
|
||||
- name: Setup test environment
|
||||
run: |
|
||||
echo "NODE_ENV=test" > .env.test
|
||||
echo "MONGO_URI=mongodb://localhost:27017/HanzoChat_test" >> .env.test
|
||||
echo "MEILI_HOST=" >> .env.test
|
||||
echo "RAG_API_URL=" >> .env.test
|
||||
echo "ALLOW_REGISTRATION=true" >> .env.test
|
||||
echo "DEBUG_LOGGING=false" >> .env.test
|
||||
echo "CREDS_KEY=test-creds-key-32-chars-long-string" >> .env.test
|
||||
echo "CREDS_IV=test-creds-iv-16" >> .env.test
|
||||
echo "JWT_SECRET=test-jwt-secret-very-secure" >> .env.test
|
||||
echo "JWT_REFRESH_SECRET=test-jwt-refresh-secret" >> .env.test
|
||||
|
||||
- name: Run API tests
|
||||
run: NODE_ENV=test npm run test:api -- --maxWorkers=2 --passWithNoTests
|
||||
|
||||
size-check:
|
||||
name: Bundle Size Check
|
||||
needs: changes
|
||||
if: needs.changes.outputs.client == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build client
|
||||
run: npm run build:client
|
||||
env:
|
||||
DOMAIN_CLIENT: https://chat.hanzo.ai
|
||||
DOMAIN_SERVER: https://chat.hanzo.ai
|
||||
|
||||
- name: Check bundle size
|
||||
uses: andresz1/size-limit-action@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
directory: client/dist
|
||||
pattern: "**/*.{js,css}"
|
||||
|
||||
type-check:
|
||||
name: TypeScript Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: TypeScript check
|
||||
run: |
|
||||
cd client && npx tsc --noEmit || true
|
||||
cd ../api && npx tsc --noEmit || true
|
||||
@@ -1,21 +0,0 @@
|
||||
name: PR Preview
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, closed]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
preview:
|
||||
uses: hanzoai/.github/.github/workflows/pr-preview.yml@main
|
||||
with:
|
||||
service: chat
|
||||
image: ghcr.io/hanzoai/chat
|
||||
port: "3080"
|
||||
health-path: /api/health
|
||||
e2e-dir: e2e
|
||||
secrets: inherit
|
||||
@@ -1,20 +0,0 @@
|
||||
"""
|
||||
|
||||
redeploy_proxy.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import requests
|
||||
import time
|
||||
|
||||
# send a get request to this endpoint
|
||||
deploy_hook1 = os.getenv("LOAD_TEST_REDEPLOY_URL1")
|
||||
response = requests.get(deploy_hook1, timeout=20)
|
||||
|
||||
|
||||
deploy_hook2 = os.getenv("LOAD_TEST_REDEPLOY_URL2")
|
||||
response = requests.get(deploy_hook2, timeout=20)
|
||||
|
||||
print("SENT GET REQUESTS to re-deploy proxy")
|
||||
print("sleeeping.... for 60s")
|
||||
time.sleep(60)
|
||||
@@ -0,0 +1,135 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Create Release
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build all packages
|
||||
run: |
|
||||
npm run build:data-provider
|
||||
npm run build:data-schemas
|
||||
npm run build:api
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
NODE_ENV=test npm run test:client
|
||||
NODE_ENV=test npm run test:api -- --maxWorkers=2
|
||||
|
||||
- name: Build production
|
||||
run: npm run build:client
|
||||
env:
|
||||
DOMAIN_CLIENT: https://chat.hanzo.ai
|
||||
DOMAIN_SERVER: https://chat.hanzo.ai
|
||||
NODE_ENV: production
|
||||
|
||||
- name: Generate changelog
|
||||
id: changelog
|
||||
uses: TriPSs/conventional-changelog-action@v5
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
skip-version-file: true
|
||||
output-file: false
|
||||
|
||||
- name: Create Release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
body: ${{ steps.changelog.outputs.clean_changelog }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Build Docker image
|
||||
run: |
|
||||
docker build -t ghcr.io/hanzoai/chat:${{ github.ref_name }} .
|
||||
docker tag ghcr.io/hanzoai/chat:${{ github.ref_name }} ghcr.io/hanzoai/chat:latest
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Push Docker image
|
||||
run: |
|
||||
docker push ghcr.io/hanzoai/chat:${{ github.ref_name }}
|
||||
docker push ghcr.io/hanzoai/chat:latest
|
||||
|
||||
publish-npm:
|
||||
name: Publish to NPM
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build packages
|
||||
run: |
|
||||
npm run build:data-provider
|
||||
npm run build:data-schemas
|
||||
npm run build:api
|
||||
|
||||
- name: Publish packages
|
||||
run: |
|
||||
cd packages/data-provider && npm publish --access public || true
|
||||
cd ../data-schemas && npm publish --access public || true
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
deploy:
|
||||
name: Deploy to Production
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
|
||||
steps:
|
||||
- name: Deploy to Hanzo Cloud
|
||||
run: |
|
||||
echo "Deploying version ${{ github.ref_name }} to production"
|
||||
# Add your deployment script here
|
||||
# Example: kubectl set image deployment/chat chat=ghcr.io/hanzoai/chat:${{ github.ref_name }}
|
||||
|
||||
- name: Notify Slack
|
||||
uses: 8398a7/action-slack@v3
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
text: 'Hanzo Chat ${{ github.ref_name }} deployed to production'
|
||||
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
|
||||
if: always()
|
||||
@@ -1,27 +0,0 @@
|
||||
Date,"Ben
|
||||
Ashley",Tom Brooks,Jimmy Cooney,"Sue
|
||||
Daniels",Berlinda Fong,Terry Jones,Angelina Little,Linda Smith
|
||||
10/1,FALSE,TRUE,TRUE,TRUE,TRUE,TRUE,FALSE,TRUE
|
||||
10/2,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/3,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/4,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/5,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/6,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/7,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/8,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/9,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/10,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/11,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/12,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/13,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/14,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/15,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/16,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/17,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/18,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/19,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/20,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/21,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/22,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
10/23,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
|
||||
Total,0,1,1,1,1,1,0,1
|
||||
|
@@ -1,439 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Run LLM Translation Tests and Generate Beautiful Markdown Report
|
||||
|
||||
This script runs the LLM translation tests and generates a comprehensive
|
||||
markdown report with provider-specific breakdowns and test statistics.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import xml.etree.ElementTree as ET
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import json
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
|
||||
# ANSI color codes for terminal output
|
||||
class Colors:
|
||||
GREEN = '\033[92m'
|
||||
RED = '\033[91m'
|
||||
YELLOW = '\033[93m'
|
||||
BLUE = '\033[94m'
|
||||
PURPLE = '\033[95m'
|
||||
CYAN = '\033[96m'
|
||||
RESET = '\033[0m'
|
||||
BOLD = '\033[1m'
|
||||
|
||||
def print_colored(message: str, color: str = Colors.RESET):
|
||||
"""Print colored message to terminal"""
|
||||
print(f"{color}{message}{Colors.RESET}")
|
||||
|
||||
def get_provider_from_test_file(test_file: str) -> str:
|
||||
"""Map test file names to provider names"""
|
||||
provider_mapping = {
|
||||
'test_anthropic': 'Anthropic',
|
||||
'test_azure': 'Azure',
|
||||
'test_bedrock': 'AWS Bedrock',
|
||||
'test_openai': 'OpenAI',
|
||||
'test_vertex': 'Google Vertex AI',
|
||||
'test_gemini': 'Google Vertex AI',
|
||||
'test_cohere': 'Cohere',
|
||||
'test_databricks': 'Databricks',
|
||||
'test_groq': 'Groq',
|
||||
'test_together': 'Together AI',
|
||||
'test_mistral': 'Mistral',
|
||||
'test_deepseek': 'DeepSeek',
|
||||
'test_replicate': 'Replicate',
|
||||
'test_huggingface': 'HuggingFace',
|
||||
'test_fireworks': 'Fireworks AI',
|
||||
'test_perplexity': 'Perplexity',
|
||||
'test_cloudflare': 'Cloudflare',
|
||||
'test_voyage': 'Voyage AI',
|
||||
'test_xai': 'xAI',
|
||||
'test_nvidia': 'NVIDIA',
|
||||
'test_watsonx': 'IBM watsonx',
|
||||
'test_azure_ai': 'Azure AI',
|
||||
'test_snowflake': 'Snowflake',
|
||||
'test_infinity': 'Infinity',
|
||||
'test_jina': 'Jina AI',
|
||||
'test_deepgram': 'Deepgram',
|
||||
'test_clarifai': 'Clarifai',
|
||||
'test_triton': 'Triton',
|
||||
}
|
||||
|
||||
for key, provider in provider_mapping.items():
|
||||
if key in test_file:
|
||||
return provider
|
||||
|
||||
# For cross-provider test files
|
||||
if any(name in test_file for name in ['test_optional_params', 'test_prompt_factory',
|
||||
'test_router', 'test_text_completion']):
|
||||
return f'Cross-Provider Tests ({test_file})'
|
||||
|
||||
return 'Other Tests'
|
||||
|
||||
def format_duration(seconds: float) -> str:
|
||||
"""Format duration in human-readable format"""
|
||||
if seconds < 60:
|
||||
return f"{seconds:.2f}s"
|
||||
elif seconds < 3600:
|
||||
minutes = int(seconds // 60)
|
||||
secs = seconds % 60
|
||||
return f"{minutes}m {secs:.0f}s"
|
||||
else:
|
||||
hours = int(seconds // 3600)
|
||||
minutes = int((seconds % 3600) // 60)
|
||||
return f"{hours}h {minutes}m"
|
||||
|
||||
|
||||
def generate_markdown_report(junit_xml_path: str, output_path: str, tag: str = None, commit: str = None):
|
||||
"""Generate a beautiful markdown report from JUnit XML"""
|
||||
try:
|
||||
tree = ET.parse(junit_xml_path)
|
||||
root = tree.getroot()
|
||||
|
||||
# Handle both testsuite and testsuites root
|
||||
if root.tag == 'testsuites':
|
||||
suites = root.findall('testsuite')
|
||||
else:
|
||||
suites = [root]
|
||||
|
||||
# Overall statistics
|
||||
total_tests = 0
|
||||
total_failures = 0
|
||||
total_errors = 0
|
||||
total_skipped = 0
|
||||
total_time = 0.0
|
||||
|
||||
# Provider breakdown
|
||||
provider_stats = defaultdict(lambda: {'passed': 0, 'failed': 0, 'skipped': 0, 'errors': 0, 'time': 0.0})
|
||||
provider_tests = defaultdict(list)
|
||||
|
||||
for suite in suites:
|
||||
total_tests += int(suite.get('tests', 0))
|
||||
total_failures += int(suite.get('failures', 0))
|
||||
total_errors += int(suite.get('errors', 0))
|
||||
total_skipped += int(suite.get('skipped', 0))
|
||||
total_time += float(suite.get('time', 0))
|
||||
|
||||
for testcase in suite.findall('testcase'):
|
||||
classname = testcase.get('classname', '')
|
||||
test_name = testcase.get('name', '')
|
||||
test_time = float(testcase.get('time', 0))
|
||||
|
||||
# Extract test file name from classname
|
||||
if '.' in classname:
|
||||
parts = classname.split('.')
|
||||
test_file = parts[-2] if len(parts) > 1 else 'unknown'
|
||||
else:
|
||||
test_file = 'unknown'
|
||||
|
||||
provider = get_provider_from_test_file(test_file)
|
||||
provider_stats[provider]['time'] += test_time
|
||||
|
||||
# Check test status
|
||||
if testcase.find('failure') is not None:
|
||||
provider_stats[provider]['failed'] += 1
|
||||
failure = testcase.find('failure')
|
||||
failure_msg = failure.get('message', '') if failure is not None else ''
|
||||
provider_tests[provider].append({
|
||||
'name': test_name,
|
||||
'status': 'FAILED',
|
||||
'time': test_time,
|
||||
'message': failure_msg
|
||||
})
|
||||
elif testcase.find('error') is not None:
|
||||
provider_stats[provider]['errors'] += 1
|
||||
error = testcase.find('error')
|
||||
error_msg = error.get('message', '') if error is not None else ''
|
||||
provider_tests[provider].append({
|
||||
'name': test_name,
|
||||
'status': 'ERROR',
|
||||
'time': test_time,
|
||||
'message': error_msg
|
||||
})
|
||||
elif testcase.find('skipped') is not None:
|
||||
provider_stats[provider]['skipped'] += 1
|
||||
skip = testcase.find('skipped')
|
||||
skip_msg = skip.get('message', '') if skip is not None else ''
|
||||
provider_tests[provider].append({
|
||||
'name': test_name,
|
||||
'status': 'SKIPPED',
|
||||
'time': test_time,
|
||||
'message': skip_msg
|
||||
})
|
||||
else:
|
||||
provider_stats[provider]['passed'] += 1
|
||||
provider_tests[provider].append({
|
||||
'name': test_name,
|
||||
'status': 'PASSED',
|
||||
'time': test_time,
|
||||
'message': ''
|
||||
})
|
||||
|
||||
passed = total_tests - total_failures - total_errors - total_skipped
|
||||
|
||||
# Generate the markdown report
|
||||
with open(output_path, 'w') as f:
|
||||
# Header
|
||||
f.write("# LLM Translation Test Results\n\n")
|
||||
|
||||
# Metadata table
|
||||
f.write("## Test Run Information\n\n")
|
||||
f.write("| Field | Value |\n")
|
||||
f.write("|-------|-------|\n")
|
||||
f.write(f"| **Tag** | `{tag or 'N/A'}` |\n")
|
||||
f.write(f"| **Date** | {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S UTC')} |\n")
|
||||
f.write(f"| **Commit** | `{commit or 'N/A'}` |\n")
|
||||
f.write(f"| **Duration** | {format_duration(total_time)} |\n")
|
||||
f.write("\n")
|
||||
|
||||
# Overall statistics with visual elements
|
||||
f.write("## Overall Statistics\n\n")
|
||||
|
||||
# Summary box
|
||||
f.write("```\n")
|
||||
f.write(f"Total Tests: {total_tests}\n")
|
||||
f.write(f"├── Passed: {passed:>4} ({(passed/total_tests)*100 if total_tests > 0 else 0:.1f}%)\n")
|
||||
f.write(f"├── Failed: {total_failures:>4} ({(total_failures/total_tests)*100 if total_tests > 0 else 0:.1f}%)\n")
|
||||
f.write(f"├── Errors: {total_errors:>4} ({(total_errors/total_tests)*100 if total_tests > 0 else 0:.1f}%)\n")
|
||||
f.write(f"└── Skipped: {total_skipped:>4} ({(total_skipped/total_tests)*100 if total_tests > 0 else 0:.1f}%)\n")
|
||||
f.write("```\n\n")
|
||||
|
||||
|
||||
# Provider summary table
|
||||
f.write("## Results by Provider\n\n")
|
||||
f.write("| Provider | Total | Pass | Fail | Error | Skip | Pass Rate | Duration |\n")
|
||||
f.write("|----------|-------|------|------|-------|------|-----------|----------|")
|
||||
|
||||
# Sort providers: specific providers first, then cross-provider tests
|
||||
sorted_providers = []
|
||||
cross_provider = []
|
||||
for p in sorted(provider_stats.keys()):
|
||||
if 'Cross-Provider' in p or p == 'Other Tests':
|
||||
cross_provider.append(p)
|
||||
else:
|
||||
sorted_providers.append(p)
|
||||
|
||||
all_providers = sorted_providers + cross_provider
|
||||
|
||||
for provider in all_providers:
|
||||
stats = provider_stats[provider]
|
||||
total = stats['passed'] + stats['failed'] + stats['errors'] + stats['skipped']
|
||||
pass_rate = (stats['passed'] / total * 100) if total > 0 else 0
|
||||
|
||||
f.write(f"\n| {provider} | {total} | {stats['passed']} | {stats['failed']} | ")
|
||||
f.write(f"{stats['errors']} | {stats['skipped']} | {pass_rate:.1f}% | ")
|
||||
f.write(f"{format_duration(stats['time'])} |")
|
||||
|
||||
# Detailed test results by provider
|
||||
f.write("\n\n## Detailed Test Results\n\n")
|
||||
|
||||
for provider in sorted_providers:
|
||||
if provider_tests[provider]:
|
||||
stats = provider_stats[provider]
|
||||
total = stats['passed'] + stats['failed'] + stats['errors'] + stats['skipped']
|
||||
|
||||
f.write(f"### {provider}\n\n")
|
||||
f.write(f"**Summary:** {stats['passed']}/{total} passed ")
|
||||
f.write(f"({(stats['passed']/total)*100 if total > 0 else 0:.1f}%) ")
|
||||
f.write(f"in {format_duration(stats['time'])}\n\n")
|
||||
|
||||
# Group tests by status
|
||||
tests_by_status = defaultdict(list)
|
||||
for test in provider_tests[provider]:
|
||||
tests_by_status[test['status']].append(test)
|
||||
|
||||
# Show failed tests first (if any)
|
||||
if tests_by_status['FAILED']:
|
||||
f.write("<details>\n<summary>Failed Tests</summary>\n\n")
|
||||
for test in tests_by_status['FAILED']:
|
||||
f.write(f"- `{test['name']}` ({test['time']:.2f}s)\n")
|
||||
if test['message']:
|
||||
# Truncate long error messages
|
||||
msg = test['message'][:200] + '...' if len(test['message']) > 200 else test['message']
|
||||
f.write(f" > {msg}\n")
|
||||
f.write("\n</details>\n\n")
|
||||
|
||||
# Show errors (if any)
|
||||
if tests_by_status['ERROR']:
|
||||
f.write("<details>\n<summary>Error Tests</summary>\n\n")
|
||||
for test in tests_by_status['ERROR']:
|
||||
f.write(f"- `{test['name']}` ({test['time']:.2f}s)\n")
|
||||
f.write("\n</details>\n\n")
|
||||
|
||||
# Show passed tests in collapsible section
|
||||
if tests_by_status['PASSED']:
|
||||
f.write("<details>\n<summary>Passed Tests</summary>\n\n")
|
||||
for test in tests_by_status['PASSED']:
|
||||
f.write(f"- `{test['name']}` ({test['time']:.2f}s)\n")
|
||||
f.write("\n</details>\n\n")
|
||||
|
||||
# Show skipped tests (if any)
|
||||
if tests_by_status['SKIPPED']:
|
||||
f.write("<details>\n<summary>Skipped Tests</summary>\n\n")
|
||||
for test in tests_by_status['SKIPPED']:
|
||||
f.write(f"- `{test['name']}`\n")
|
||||
f.write("\n</details>\n\n")
|
||||
|
||||
# Cross-provider tests in a separate section
|
||||
if cross_provider:
|
||||
f.write("### Cross-Provider Tests\n\n")
|
||||
for provider in cross_provider:
|
||||
if provider_tests[provider]:
|
||||
stats = provider_stats[provider]
|
||||
total = stats['passed'] + stats['failed'] + stats['errors'] + stats['skipped']
|
||||
|
||||
f.write(f"#### {provider}\n\n")
|
||||
f.write(f"**Summary:** {stats['passed']}/{total} passed ")
|
||||
f.write(f"({(stats['passed']/total)*100 if total > 0 else 0:.1f}%)\n\n")
|
||||
|
||||
# For cross-provider tests, just show counts
|
||||
f.write(f"- Passed: {stats['passed']}\n")
|
||||
if stats['failed'] > 0:
|
||||
f.write(f"- Failed: {stats['failed']}\n")
|
||||
if stats['errors'] > 0:
|
||||
f.write(f"- Errors: {stats['errors']}\n")
|
||||
if stats['skipped'] > 0:
|
||||
f.write(f"- Skipped: {stats['skipped']}\n")
|
||||
f.write("\n")
|
||||
|
||||
|
||||
print_colored(f"Report generated: {output_path}", Colors.GREEN)
|
||||
|
||||
except Exception as e:
|
||||
print_colored(f"Error generating report: {e}", Colors.RED)
|
||||
raise
|
||||
|
||||
def run_tests(test_path: str = "tests/llm_translation/",
|
||||
junit_xml: str = "test-results/junit.xml",
|
||||
report_path: str = "test-results/llm_translation_report.md",
|
||||
tag: str = None,
|
||||
commit: str = None) -> int:
|
||||
"""Run the LLM translation tests and generate report"""
|
||||
|
||||
# Create test results directory
|
||||
os.makedirs(os.path.dirname(junit_xml), exist_ok=True)
|
||||
|
||||
print_colored("Starting LLM Translation Tests", Colors.BOLD + Colors.BLUE)
|
||||
print_colored(f"Test directory: {test_path}", Colors.CYAN)
|
||||
print_colored(f"Output: {junit_xml}", Colors.CYAN)
|
||||
print()
|
||||
|
||||
# Run pytest
|
||||
cmd = [
|
||||
"poetry", "run", "pytest", test_path,
|
||||
f"--junitxml={junit_xml}",
|
||||
"-v",
|
||||
"--tb=short",
|
||||
"--maxfail=500",
|
||||
"-n", "auto"
|
||||
]
|
||||
|
||||
# Add timeout if pytest-timeout is installed
|
||||
try:
|
||||
subprocess.run(["poetry", "run", "python", "-c", "import pytest_timeout"],
|
||||
capture_output=True, check=True)
|
||||
cmd.extend(["--timeout=300"])
|
||||
except:
|
||||
print_colored("Warning: pytest-timeout not installed, skipping timeout option", Colors.YELLOW)
|
||||
|
||||
print_colored("Running pytest with command:", Colors.YELLOW)
|
||||
print(f" {' '.join(cmd)}")
|
||||
print()
|
||||
|
||||
# Run the tests
|
||||
result = subprocess.run(cmd, capture_output=False)
|
||||
|
||||
# Generate the report regardless of test outcome
|
||||
if os.path.exists(junit_xml):
|
||||
print()
|
||||
print_colored("Generating test report...", Colors.BLUE)
|
||||
generate_markdown_report(junit_xml, report_path, tag, commit)
|
||||
|
||||
# Print summary to console
|
||||
print()
|
||||
print_colored("Test Summary:", Colors.BOLD + Colors.PURPLE)
|
||||
|
||||
# Parse XML for quick summary
|
||||
tree = ET.parse(junit_xml)
|
||||
root = tree.getroot()
|
||||
|
||||
if root.tag == 'testsuites':
|
||||
suites = root.findall('testsuite')
|
||||
else:
|
||||
suites = [root]
|
||||
|
||||
total = sum(int(s.get('tests', 0)) for s in suites)
|
||||
failures = sum(int(s.get('failures', 0)) for s in suites)
|
||||
errors = sum(int(s.get('errors', 0)) for s in suites)
|
||||
skipped = sum(int(s.get('skipped', 0)) for s in suites)
|
||||
passed = total - failures - errors - skipped
|
||||
|
||||
print(f" Total: {total}")
|
||||
print_colored(f" Passed: {passed}", Colors.GREEN)
|
||||
if failures > 0:
|
||||
print_colored(f" Failed: {failures}", Colors.RED)
|
||||
if errors > 0:
|
||||
print_colored(f" Errors: {errors}", Colors.RED)
|
||||
if skipped > 0:
|
||||
print_colored(f" Skipped: {skipped}", Colors.YELLOW)
|
||||
|
||||
if total > 0:
|
||||
pass_rate = (passed / total) * 100
|
||||
color = Colors.GREEN if pass_rate >= 80 else Colors.YELLOW if pass_rate >= 60 else Colors.RED
|
||||
print_colored(f" Pass Rate: {pass_rate:.1f}%", color)
|
||||
else:
|
||||
print_colored("No test results found!", Colors.RED)
|
||||
|
||||
print()
|
||||
print_colored("Test run complete!", Colors.BOLD + Colors.GREEN)
|
||||
|
||||
return result.returncode
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Run LLM Translation Tests")
|
||||
parser.add_argument("--test-path", default="tests/llm_translation/",
|
||||
help="Path to test directory")
|
||||
parser.add_argument("--junit-xml", default="test-results/junit.xml",
|
||||
help="Path for JUnit XML output")
|
||||
parser.add_argument("--report", default="test-results/llm_translation_report.md",
|
||||
help="Path for markdown report")
|
||||
parser.add_argument("--tag", help="Git tag or version")
|
||||
parser.add_argument("--commit", help="Git commit SHA")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Get git info if not provided
|
||||
if not args.commit:
|
||||
try:
|
||||
result = subprocess.run(["git", "rev-parse", "HEAD"],
|
||||
capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
args.commit = result.stdout.strip()
|
||||
except:
|
||||
pass
|
||||
|
||||
if not args.tag:
|
||||
try:
|
||||
result = subprocess.run(["git", "describe", "--tags", "--abbrev=0"],
|
||||
capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
args.tag = result.stdout.strip()
|
||||
except:
|
||||
pass
|
||||
|
||||
exit_code = run_tests(
|
||||
test_path=args.test_path,
|
||||
junit_xml=args.junit_xml,
|
||||
report_path=args.report,
|
||||
tag=args.tag,
|
||||
commit=args.commit
|
||||
)
|
||||
|
||||
sys.exit(exit_code)
|
||||
@@ -1,20 +0,0 @@
|
||||
name: "Stale Issue Management"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # Runs daily at midnight UTC
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
stale-issue-message: "This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs."
|
||||
stale-pr-message: "This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs."
|
||||
days-before-stale: 90 # Revert to 60 days
|
||||
days-before-close: 7 # Revert to 7 days
|
||||
stale-issue-label: "stale"
|
||||
operations-per-run: 1000
|
||||
@@ -1,85 +0,0 @@
|
||||
name: Sync Helm Chart Tags
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_existing_tag:
|
||||
description: "Existing chart-* tag to dispatch if tag creation succeeded but release dispatch failed"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: sync-helm-chart-tags
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
noop:
|
||||
name: Ignore non-main push
|
||||
if: github.event_name == 'push' && github.ref != 'refs/heads/main'
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
timeout-minutes: 1
|
||||
steps:
|
||||
- name: Skip tag sync
|
||||
run: echo "Helm chart tag sync only runs on main pushes or manual dispatch."
|
||||
|
||||
sync:
|
||||
name: Sync chart tags
|
||||
if: github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main'
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
actions: write
|
||||
contents: write
|
||||
env:
|
||||
BASE_REF: refs/remotes/origin/main
|
||||
BACKFILL_FROM_VERSION: 1.9.0
|
||||
CHART_PATH: helm/librechat/Chart.yaml
|
||||
DEFAULT_BRANCH: main
|
||||
DISPATCH_WORKFLOW: helmcharts.yml
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
RELEASE_EXISTING_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_existing_tag || '' }}
|
||||
REPO_DIR: /tmp/librechat-sync
|
||||
TAG_PREFIX: chart-
|
||||
steps:
|
||||
- name: Fetch main and tags
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ ! "$GITHUB_REPOSITORY" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then
|
||||
echo "::error::Unexpected repository name: $GITHUB_REPOSITORY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$GITHUB_SERVER_URL" != "https://github.com" ]]; then
|
||||
echo "::error::Unexpected GitHub server URL: $GITHUB_SERVER_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf "$REPO_DIR"
|
||||
git init "$REPO_DIR"
|
||||
cd "$REPO_DIR"
|
||||
git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
|
||||
AUTH_HEADER="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 | tr -d '\n')"
|
||||
git -c "http.extraheader=AUTHORIZATION: basic ${AUTH_HEADER}" \
|
||||
fetch --prune --force --tags origin \
|
||||
"+refs/heads/${DEFAULT_BRANCH}:refs/remotes/origin/${DEFAULT_BRANCH}"
|
||||
git checkout --detach "$BASE_REF"
|
||||
|
||||
- name: Create missing chart tags
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PUSH_TAGS: "true"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd "$REPO_DIR"
|
||||
.github/scripts/sync-helm-chart-tags.sh
|
||||
@@ -3,49 +3,26 @@ name: Docker Images Build on Tag
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: api-build
|
||||
file: Dockerfile.multi
|
||||
image_name: librechat-api
|
||||
image_name: hanzochat-api
|
||||
- target: node
|
||||
file: Dockerfile
|
||||
image_name: librechat
|
||||
image_name: hanzochat
|
||||
|
||||
steps:
|
||||
# Check out the repository
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate release tag
|
||||
id: release-tag
|
||||
env:
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG_REGEX='^v[0-9]+[.][0-9]+[.][0-9]+(-rc[0-9]+)?$'
|
||||
STABLE_TAG_REGEX='^v[0-9]+[.][0-9]+[.][0-9]+$'
|
||||
if [[ ! "$REF_NAME" =~ $TAG_REGEX ]]; then
|
||||
echo "::error::Docker release tags must use v<semver> or v<semver>-rcN, for example v0.8.5 or v0.8.5-rc1"
|
||||
exit 1
|
||||
fi
|
||||
printf 'image_tag=%s\n' "$REF_NAME" >> "$GITHUB_OUTPUT"
|
||||
if [[ "$REF_NAME" =~ $STABLE_TAG_REGEX ]]; then
|
||||
echo "is_stable=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "is_stable=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Set up QEMU
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
@@ -56,7 +33,7 @@ jobs:
|
||||
|
||||
# Log in to GitHub Container Registry
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -74,32 +51,6 @@ jobs:
|
||||
run: |
|
||||
cp .env.example .env
|
||||
|
||||
- name: Compute build metadata
|
||||
run: |
|
||||
echo "BUILD_COMMIT=${{ github.sha }}" >> $GITHUB_ENV
|
||||
echo "BUILD_BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
|
||||
|
||||
- name: Resolve image tags
|
||||
id: image-tags
|
||||
env:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
IMAGE_NAME: ${{ matrix.image_name }}
|
||||
IMAGE_TAG: ${{ steps.release-tag.outputs.image_tag }}
|
||||
IS_STABLE: ${{ steps.release-tag.outputs.is_stable }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
{
|
||||
echo 'tags<<EOF'
|
||||
printf 'ghcr.io/%s/%s:%s\n' "$GITHUB_REPOSITORY_OWNER" "$IMAGE_NAME" "$IMAGE_TAG"
|
||||
printf '%s/%s:%s\n' "$DOCKERHUB_USERNAME" "$IMAGE_NAME" "$IMAGE_TAG"
|
||||
if [ "$IS_STABLE" = "true" ]; then
|
||||
printf 'ghcr.io/%s/%s:latest\n' "$GITHUB_REPOSITORY_OWNER" "$IMAGE_NAME"
|
||||
printf '%s/%s:latest\n' "$DOCKERHUB_USERNAME" "$IMAGE_NAME"
|
||||
fi
|
||||
echo 'EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Build and push Docker images for each target
|
||||
- name: Build and push Docker images
|
||||
uses: docker/build-push-action@v5
|
||||
@@ -107,10 +58,10 @@ jobs:
|
||||
context: .
|
||||
file: ${{ matrix.file }}
|
||||
push: true
|
||||
tags: ${{ steps.image-tags.outputs.tags }}
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:${{ github.ref_name }}
|
||||
ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:latest
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:${{ github.ref_name }}
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
target: ${{ matrix.target }}
|
||||
build-args: |
|
||||
BUILD_COMMIT=${{ env.BUILD_COMMIT }}
|
||||
BUILD_BRANCH=${{ env.BUILD_BRANCH }}
|
||||
BUILD_DATE=${{ env.BUILD_DATE }}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
name: LiteLLM Linting
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install openai==1.81.0
|
||||
poetry install --with dev
|
||||
pip install openai==1.81.0
|
||||
|
||||
|
||||
|
||||
- name: Run Black formatting
|
||||
run: |
|
||||
cd litellm
|
||||
poetry run black .
|
||||
cd ..
|
||||
|
||||
- name: Run Ruff linting
|
||||
run: |
|
||||
cd litellm
|
||||
poetry run ruff check .
|
||||
cd ..
|
||||
|
||||
- name: Run MyPy type checking
|
||||
run: |
|
||||
cd litellm
|
||||
poetry run mypy . --ignore-missing-imports
|
||||
cd ..
|
||||
|
||||
- name: Check for circular imports
|
||||
run: |
|
||||
cd litellm
|
||||
poetry run python ../tests/documentation_tests/test_circular_imports.py
|
||||
cd ..
|
||||
|
||||
- name: Check import safety
|
||||
run: |
|
||||
poetry run python -c "from litellm import *" || (echo '🚨 import failed, this means you introduced unprotected imports! 🚨'; exit 1)
|
||||
@@ -1,70 +0,0 @@
|
||||
name: Test Chat Service
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
pull_request:
|
||||
branches: [ main, develop ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
|
||||
services:
|
||||
mongodb:
|
||||
image: mongo:7
|
||||
ports:
|
||||
- 27017:27017
|
||||
|
||||
meili:
|
||||
image: getmeili/meilisearch:latest
|
||||
env:
|
||||
MEILI_MASTER_KEY: test_master_key
|
||||
ports:
|
||||
- 7700:7700
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v5
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build workspace packages
|
||||
run: |
|
||||
pnpm run build:data-provider
|
||||
pnpm run build:data-schemas
|
||||
pnpm run build:api
|
||||
pnpm run build:client-package
|
||||
|
||||
- name: Run API tests
|
||||
run: pnpm run test:api
|
||||
env:
|
||||
NODE_ENV: test
|
||||
MONGO_URI: mongodb://localhost:27017/librechat-test
|
||||
MEILI_HOST: http://localhost:7700
|
||||
MEILI_MASTER_KEY: test_master_key
|
||||
|
||||
- name: Run client tests
|
||||
run: pnpm run test:client
|
||||
@@ -7,32 +7,28 @@ on:
|
||||
- 'package-lock.json'
|
||||
- 'client/**'
|
||||
- 'api/**'
|
||||
- 'packages/client/**'
|
||||
- 'packages/api/**'
|
||||
|
||||
jobs:
|
||||
detect-unused-packages:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js 20.x
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install depcheck
|
||||
run: pnpm add -g depcheck
|
||||
run: npm install -g depcheck
|
||||
|
||||
- name: Validate JSON files
|
||||
run: |
|
||||
for FILE in package.json client/package.json api/package.json packages/client/package.json; do
|
||||
for FILE in package.json client/package.json api/package.json; do
|
||||
if [[ -f "$FILE" ]]; then
|
||||
jq empty "$FILE" || (echo "::error title=Invalid JSON::$FILE is invalid" && exit 1)
|
||||
fi
|
||||
@@ -66,151 +62,29 @@ jobs:
|
||||
extract_deps_from_code() {
|
||||
local folder=$1
|
||||
local output_file=$2
|
||||
|
||||
# Initialize empty output file
|
||||
> "$output_file"
|
||||
|
||||
if [[ -d "$folder" ]]; then
|
||||
# Extract require() statements (use explicit includes for portability)
|
||||
grep -rEho "require\\(['\"]([a-zA-Z0-9@/._-]+)['\"]\\)" "$folder" \
|
||||
--include='*.js' --include='*.ts' --include='*.tsx' --include='*.jsx' --include='*.mjs' --include='*.cjs' 2>/dev/null | \
|
||||
sed -E "s/require\\(['\"]([a-zA-Z0-9@/._-]+)['\"]\\)/\1/" >> "$output_file" || true
|
||||
grep -rEho "require\\(['\"]([a-zA-Z0-9@/._-]+)['\"]\\)" "$folder" --include=\*.{js,ts,mjs,cjs} | \
|
||||
sed -E "s/require\\(['\"]([a-zA-Z0-9@/._-]+)['\"]\\)/\1/" > "$output_file"
|
||||
|
||||
# Extract ES6 imports - import x from 'module'
|
||||
grep -rEho "import .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" \
|
||||
--include='*.js' --include='*.ts' --include='*.tsx' --include='*.jsx' --include='*.mjs' --include='*.cjs' 2>/dev/null | \
|
||||
sed -E "s/import .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" || true
|
||||
|
||||
# import 'module' (side-effect imports)
|
||||
grep -rEho "import ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" \
|
||||
--include='*.js' --include='*.ts' --include='*.tsx' --include='*.jsx' --include='*.mjs' --include='*.cjs' 2>/dev/null | \
|
||||
sed -E "s/import ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" || true
|
||||
|
||||
# export { x } from 'module' or export * from 'module'
|
||||
grep -rEho "export .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" \
|
||||
--include='*.js' --include='*.ts' --include='*.tsx' --include='*.jsx' --include='*.mjs' --include='*.cjs' 2>/dev/null | \
|
||||
sed -E "s/export .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" || true
|
||||
|
||||
# import type { x } from 'module' (TypeScript)
|
||||
grep -rEho "import type .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" \
|
||||
--include='*.ts' --include='*.tsx' 2>/dev/null | \
|
||||
sed -E "s/import type .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" || true
|
||||
grep -rEho "import .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" --include=\*.{js,ts,mjs,cjs} | \
|
||||
sed -E "s/import .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file"
|
||||
|
||||
# Remove subpath imports but keep the base package
|
||||
# For scoped packages: '@scope/pkg/subpath' -> '@scope/pkg'
|
||||
# For regular packages: 'pkg/subpath' -> 'pkg'
|
||||
# Scoped packages (must keep @scope/package, strip anything after)
|
||||
sed -i -E 's|^(@[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+)/.*|\1|' "$output_file" 2>/dev/null || true
|
||||
# Non-scoped packages (keep package name, strip subpath)
|
||||
sed -i -E 's|^([a-zA-Z0-9_-]+)/.*|\1|' "$output_file" 2>/dev/null || true
|
||||
|
||||
sort -u "$output_file" -o "$output_file"
|
||||
else
|
||||
touch "$output_file"
|
||||
fi
|
||||
}
|
||||
|
||||
extract_deps_from_code "." root_used_code.txt
|
||||
extract_deps_from_code "client" client_used_code.txt
|
||||
extract_deps_from_code "api" api_used_code.txt
|
||||
|
||||
# Extract dependencies used by workspace packages
|
||||
# These packages are used in the workspace but dependencies are provided by parent package.json
|
||||
extract_deps_from_code "packages/client" packages_client_used_code.txt
|
||||
extract_deps_from_code "packages/api" packages_api_used_code.txt
|
||||
|
||||
- name: Get @librechat/client dependencies
|
||||
id: get-librechat-client-deps
|
||||
run: |
|
||||
if [[ -f "packages/client/package.json" ]]; then
|
||||
# Get all dependencies from @librechat/client (dependencies, devDependencies, and peerDependencies)
|
||||
DEPS=$(jq -r '.dependencies // {} | keys[]' packages/client/package.json 2>/dev/null || echo "")
|
||||
DEV_DEPS=$(jq -r '.devDependencies // {} | keys[]' packages/client/package.json 2>/dev/null || echo "")
|
||||
PEER_DEPS=$(jq -r '.peerDependencies // {} | keys[]' packages/client/package.json 2>/dev/null || echo "")
|
||||
|
||||
# Combine all dependencies
|
||||
echo "$DEPS" > librechat_client_deps.txt
|
||||
echo "$DEV_DEPS" >> librechat_client_deps.txt
|
||||
echo "$PEER_DEPS" >> librechat_client_deps.txt
|
||||
|
||||
# Also include dependencies that are imported in packages/client
|
||||
cat packages_client_used_code.txt >> librechat_client_deps.txt
|
||||
|
||||
# Remove empty lines and sort
|
||||
grep -v '^$' librechat_client_deps.txt | sort -u > temp_deps.txt
|
||||
mv temp_deps.txt librechat_client_deps.txt
|
||||
else
|
||||
touch librechat_client_deps.txt
|
||||
fi
|
||||
|
||||
- name: Get @librechat/api dependencies
|
||||
id: get-librechat-api-deps
|
||||
run: |
|
||||
if [[ -f "packages/api/package.json" ]]; then
|
||||
# Get all dependencies from @librechat/api (dependencies, devDependencies, and peerDependencies)
|
||||
DEPS=$(jq -r '.dependencies // {} | keys[]' packages/api/package.json 2>/dev/null || echo "")
|
||||
DEV_DEPS=$(jq -r '.devDependencies // {} | keys[]' packages/api/package.json 2>/dev/null || echo "")
|
||||
PEER_DEPS=$(jq -r '.peerDependencies // {} | keys[]' packages/api/package.json 2>/dev/null || echo "")
|
||||
|
||||
# Combine all dependencies
|
||||
echo "$DEPS" > librechat_api_deps.txt
|
||||
echo "$DEV_DEPS" >> librechat_api_deps.txt
|
||||
echo "$PEER_DEPS" >> librechat_api_deps.txt
|
||||
|
||||
# Also include dependencies that are imported in packages/api
|
||||
cat packages_api_used_code.txt >> librechat_api_deps.txt
|
||||
|
||||
# Remove empty lines and sort
|
||||
grep -v '^$' librechat_api_deps.txt | sort -u > temp_deps.txt
|
||||
mv temp_deps.txt librechat_api_deps.txt
|
||||
else
|
||||
touch librechat_api_deps.txt
|
||||
fi
|
||||
|
||||
- name: Extract Workspace Dependencies
|
||||
id: extract-workspace-deps
|
||||
run: |
|
||||
# Function to get dependencies from a workspace package that are used by another package
|
||||
get_workspace_package_deps() {
|
||||
local package_json=$1
|
||||
local output_file=$2
|
||||
|
||||
# Get all workspace dependencies (starting with @librechat/)
|
||||
if [[ -f "$package_json" ]]; then
|
||||
local workspace_deps=$(jq -r '.dependencies // {} | to_entries[] | select(.key | startswith("@librechat/")) | .key' "$package_json" 2>/dev/null || echo "")
|
||||
|
||||
# For each workspace dependency, get its dependencies
|
||||
for dep in $workspace_deps; do
|
||||
# Convert @librechat/api to packages/api
|
||||
local workspace_path=$(echo "$dep" | sed 's/@librechat\//packages\//')
|
||||
local workspace_package_json="${workspace_path}/package.json"
|
||||
|
||||
if [[ -f "$workspace_package_json" ]]; then
|
||||
# Extract all dependencies from the workspace package
|
||||
jq -r '.dependencies // {} | keys[]' "$workspace_package_json" 2>/dev/null >> "$output_file"
|
||||
# Also extract peerDependencies
|
||||
jq -r '.peerDependencies // {} | keys[]' "$workspace_package_json" 2>/dev/null >> "$output_file"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -f "$output_file" ]]; then
|
||||
sort -u "$output_file" -o "$output_file"
|
||||
else
|
||||
touch "$output_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get workspace dependencies for each package
|
||||
get_workspace_package_deps "package.json" root_workspace_deps.txt
|
||||
get_workspace_package_deps "client/package.json" client_workspace_deps.txt
|
||||
get_workspace_package_deps "api/package.json" api_workspace_deps.txt
|
||||
|
||||
- name: Run depcheck for root package.json
|
||||
id: check-root
|
||||
run: |
|
||||
if [[ -f "package.json" ]]; then
|
||||
UNUSED=$(depcheck --json | jq -r '.dependencies | join("\n")' || echo "")
|
||||
# Exclude dependencies used in scripts, code, and workspace packages
|
||||
UNUSED=$(comm -23 <(echo "$UNUSED" | sort) <(cat root_used_deps.txt root_used_code.txt root_workspace_deps.txt | sort) || echo "")
|
||||
UNUSED=$(comm -23 <(echo "$UNUSED" | sort) <(cat root_used_deps.txt root_used_code.txt | sort) || echo "")
|
||||
echo "ROOT_UNUSED<<EOF" >> $GITHUB_ENV
|
||||
echo "$UNUSED" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
@@ -223,8 +97,7 @@ jobs:
|
||||
chmod -R 755 client
|
||||
cd client
|
||||
UNUSED=$(depcheck --json | jq -r '.dependencies | join("\n")' || echo "")
|
||||
# Exclude dependencies used in scripts, code, workspace packages, and @librechat/client imports
|
||||
UNUSED=$(comm -23 <(echo "$UNUSED" | sort) <(cat ../client_used_deps.txt ../client_used_code.txt ../client_workspace_deps.txt ../packages_client_used_code.txt ../librechat_client_deps.txt 2>/dev/null | sort -u) || echo "")
|
||||
UNUSED=$(comm -23 <(echo "$UNUSED" | sort) <(cat ../client_used_deps.txt ../client_used_code.txt | sort) || echo "")
|
||||
# Filter out false positives
|
||||
UNUSED=$(echo "$UNUSED" | grep -v "^micromark-extension-llm-math$" || echo "")
|
||||
echo "CLIENT_UNUSED<<EOF" >> $GITHUB_ENV
|
||||
@@ -240,8 +113,7 @@ jobs:
|
||||
chmod -R 755 api
|
||||
cd api
|
||||
UNUSED=$(depcheck --json | jq -r '.dependencies | join("\n")' || echo "")
|
||||
# Exclude dependencies used in scripts, code, workspace packages, and @librechat/api imports
|
||||
UNUSED=$(comm -23 <(echo "$UNUSED" | sort) <(cat ../api_used_deps.txt ../api_used_code.txt ../api_workspace_deps.txt ../packages_api_used_code.txt ../librechat_api_deps.txt 2>/dev/null | sort -u) || echo "")
|
||||
UNUSED=$(comm -23 <(echo "$UNUSED" | sort) <(cat ../api_used_deps.txt ../api_used_code.txt | sort) || echo "")
|
||||
echo "API_UNUSED<<EOF" >> $GITHUB_ENV
|
||||
echo "$UNUSED" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
@@ -280,4 +152,4 @@ jobs:
|
||||
|
||||
- name: Fail workflow if unused dependencies found
|
||||
if: env.ROOT_UNUSED != '' || env.CLIENT_UNUSED != '' || env.API_UNUSED != ''
|
||||
run: exit 1
|
||||
run: exit 1
|
||||
@@ -1,54 +0,0 @@
|
||||
import os
|
||||
import requests
|
||||
from datetime import datetime
|
||||
|
||||
# GitHub API endpoints
|
||||
GITHUB_API_URL = "https://api.github.com"
|
||||
REPO_OWNER = "BerriAI"
|
||||
REPO_NAME = "litellm"
|
||||
|
||||
# GitHub personal access token (required for uploading release assets)
|
||||
GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN")
|
||||
|
||||
# Headers for GitHub API requests
|
||||
headers = {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"Authorization": f"Bearer {GITHUB_ACCESS_TOKEN}",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
}
|
||||
|
||||
# Get the latest release
|
||||
releases_url = f"{GITHUB_API_URL}/repos/{REPO_OWNER}/{REPO_NAME}/releases/latest"
|
||||
response = requests.get(releases_url, headers=headers)
|
||||
latest_release = response.json()
|
||||
print("Latest release:", latest_release)
|
||||
|
||||
# Upload an asset to the latest release
|
||||
upload_url = latest_release["upload_url"].split("{?")[0]
|
||||
asset_name = "results_stats.csv"
|
||||
asset_path = os.path.join(os.getcwd(), asset_name)
|
||||
print("upload_url:", upload_url)
|
||||
|
||||
with open(asset_path, "rb") as asset_file:
|
||||
asset_data = asset_file.read()
|
||||
|
||||
upload_payload = {
|
||||
"name": asset_name,
|
||||
"label": "Load test results",
|
||||
"created_at": datetime.utcnow().isoformat() + "Z",
|
||||
}
|
||||
|
||||
upload_headers = headers.copy()
|
||||
upload_headers["Content-Type"] = "application/octet-stream"
|
||||
|
||||
upload_response = requests.post(
|
||||
upload_url,
|
||||
headers=upload_headers,
|
||||
data=asset_data,
|
||||
params=upload_payload,
|
||||
)
|
||||
|
||||
if upload_response.status_code == 201:
|
||||
print(f"Asset '{asset_name}' uploaded successfully to the latest release.")
|
||||
else:
|
||||
print(f"Failed to upload asset. Response: {upload_response.text}")
|
||||
@@ -1,7 +0,0 @@
|
||||
name: Workflow Sanity
|
||||
on:
|
||||
pull_request:
|
||||
paths: ['.github/workflows/**']
|
||||
jobs:
|
||||
sanity:
|
||||
uses: hanzoai/.github/.github/workflows/workflow-sanity.yml@main
|
||||
+112
-137
@@ -1,154 +1,129 @@
|
||||
### node etc ###
|
||||
|
||||
__azurite**
|
||||
__blobstorage__/**/*
|
||||
!.env.hanzo-cloud
|
||||
!**/.env.example
|
||||
!**/.env.test.example
|
||||
!/client/src/@types/i18next.d.ts
|
||||
!client/src/components/Nav/SettingsTabs/Data/
|
||||
!memory/agents/README.md
|
||||
!memory/sessions/README.md
|
||||
!vite-env.d.ts
|
||||
.aider*
|
||||
.claude-flow/
|
||||
.claude/settings.local.json
|
||||
.clineignore
|
||||
.cursor
|
||||
.DS_Store
|
||||
.env.hanzo
|
||||
.env*
|
||||
.floo
|
||||
.floobit
|
||||
.flooignore
|
||||
.git
|
||||
.grunt
|
||||
.hive-mind/
|
||||
.idea
|
||||
.idx
|
||||
.mcp.json
|
||||
.npmrc
|
||||
.swarm/
|
||||
.turbo
|
||||
.vscode/settings.json
|
||||
*.cert
|
||||
*.code-workspace
|
||||
*.d.ts
|
||||
*.db
|
||||
*.db-journal
|
||||
*.db-wal
|
||||
*.iml
|
||||
*.local.md
|
||||
# Logs
|
||||
data-node
|
||||
meili_data*
|
||||
data/
|
||||
logs
|
||||
*.log
|
||||
*.pem
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.sqlite
|
||||
*.sqlite-journal
|
||||
*.sqlite-wal
|
||||
**/.venv/
|
||||
**/dist/
|
||||
**/storageState.json
|
||||
**/venv/
|
||||
/.aider/
|
||||
/.claude/
|
||||
/.codeium
|
||||
/.copilot/
|
||||
/.cursor/
|
||||
/.openai/
|
||||
/.tabnine/
|
||||
/e2e/playwright-report/
|
||||
/e2e/specs/.test-results/
|
||||
/images
|
||||
/packages/ux-shared/
|
||||
/playwright/.cache/
|
||||
# AI
|
||||
# AI Assistants
|
||||
# Azure Blob Storage Emulator (Azurite)
|
||||
# CI/CD data
|
||||
# Claude Flow generated files
|
||||
# Compiled Dirs (http://nodejs.org/api/addons.html)
|
||||
# Coverage directory used by tools like istanbul
|
||||
# Dependency directorys
|
||||
# Deployed apps should consider commenting these lines out:
|
||||
.git
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
# docker override file
|
||||
# Environment
|
||||
# Floobits
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
# Hanzo-specific
|
||||
# Helm
|
||||
# Logs
|
||||
# meilisearch
|
||||
# owner
|
||||
# Removed Windows wrapper files per user request
|
||||
# Runtime data
|
||||
# SAML Idp cert
|
||||
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
|
||||
.grunt
|
||||
|
||||
# translation services
|
||||
# Turborepo
|
||||
# User uploads
|
||||
### node etc ###
|
||||
#config file
|
||||
api/data/
|
||||
api/node_modules/
|
||||
archive
|
||||
auth.json
|
||||
bower_components/
|
||||
config/translations/stores/*
|
||||
client/src/localization/languages/*_missing_keys.json
|
||||
|
||||
# Compiled Dirs (http://nodejs.org/api/addons.html)
|
||||
build/
|
||||
cache.json
|
||||
claude-flow
|
||||
claude-flow.config.json
|
||||
CLAUDE.md
|
||||
client/node_modules/
|
||||
dist/
|
||||
public/main.js
|
||||
public/main.js.map
|
||||
public/main.js.LICENSE.txt
|
||||
client/public/images/
|
||||
client/public/main.js
|
||||
client/public/main.js.LICENSE.txt
|
||||
client/public/main.js.map
|
||||
client/src/localization/languages/*_missing_keys.json
|
||||
client/public/main.js.LICENSE.txt
|
||||
|
||||
# Azure Blob Storage Emulator (Azurite)
|
||||
__azurite**
|
||||
__blobstorage__/**/*
|
||||
|
||||
# Dependency directorys
|
||||
# Deployed apps should consider commenting these lines out:
|
||||
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
|
||||
node_modules/
|
||||
meili_data/
|
||||
api/node_modules/
|
||||
client/node_modules/
|
||||
bower_components/
|
||||
*.d.ts
|
||||
!vite-env.d.ts
|
||||
|
||||
# AI
|
||||
.clineignore
|
||||
.cursor
|
||||
.aider*
|
||||
|
||||
# Floobits
|
||||
.floo
|
||||
.floobit
|
||||
.floo
|
||||
.flooignore
|
||||
|
||||
#config file
|
||||
chat.yaml
|
||||
chat.yml
|
||||
|
||||
# Environment
|
||||
.npmrc
|
||||
.env*
|
||||
my.secrets
|
||||
!**/.env.example
|
||||
!**/.env.test.example
|
||||
cache.json
|
||||
api/data/
|
||||
owner.yml
|
||||
archive
|
||||
.vscode/settings.json
|
||||
src/style - official.css
|
||||
/e2e/specs/.test-results/
|
||||
/e2e/playwright-report/
|
||||
/playwright/.cache/
|
||||
.DS_Store
|
||||
*.code-workspace
|
||||
.idx
|
||||
monospace.json
|
||||
.idea
|
||||
*.iml
|
||||
*.pem
|
||||
config.local.ts
|
||||
config/translations/stores/*
|
||||
coordination/
|
||||
coordination/memory_bank/*
|
||||
coordination/orchestration/*
|
||||
coordination/subtasks/*
|
||||
coverage
|
||||
data-node
|
||||
data.ms/*
|
||||
data/
|
||||
dist/
|
||||
**/storageState.json
|
||||
junit.xml
|
||||
**/.venv/
|
||||
**/venv/
|
||||
|
||||
# docker override file
|
||||
docker-compose.override.yaml
|
||||
docker-compose.override.yml
|
||||
dump.rdb
|
||||
helm/**/.values.yaml
|
||||
helm/**/charts/
|
||||
helm/librechat/Chart.lock
|
||||
hive-mind-prompt-*.txt
|
||||
junit.xml
|
||||
lib-cov
|
||||
librechat.yaml
|
||||
librechat.yml
|
||||
logs
|
||||
meili_data*
|
||||
meili_data/
|
||||
|
||||
# meilisearch
|
||||
meilisearch
|
||||
meilisearch.exe
|
||||
memory/
|
||||
memory/agents/*
|
||||
memory/claude-flow-data.json
|
||||
memory/sessions/*
|
||||
monospace.json
|
||||
my.secrets
|
||||
node_modules/
|
||||
owner.yml
|
||||
pids
|
||||
public/main.js
|
||||
public/main.js.LICENSE.txt
|
||||
public/main.js.map
|
||||
release/
|
||||
src/style - official.css
|
||||
test-image*
|
||||
tests/**/*.jpg
|
||||
tests/**/*.mp3
|
||||
tests/**/*.png
|
||||
tests/**/*.wav
|
||||
data.ms/*
|
||||
auth.json
|
||||
|
||||
/packages/ux-shared/
|
||||
/images
|
||||
|
||||
!client/src/components/Nav/SettingsTabs/Data/
|
||||
|
||||
# User uploads
|
||||
uploads/
|
||||
|
||||
# owner
|
||||
release/
|
||||
|
||||
# Helm
|
||||
helm/librechat/Chart.lock
|
||||
helm/**/charts/
|
||||
helm/**/.values.yaml
|
||||
|
||||
!/client/src/@types/i18next.d.ts
|
||||
|
||||
# SAML Idp cert
|
||||
*.cert
|
||||
*.orig
|
||||
.claude
|
||||
|
||||
+34
-2
@@ -1,2 +1,34 @@
|
||||
[ -n "$CI" ] && exit 0
|
||||
npx lint-staged --config ./.husky/lint-staged.config.js
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
# Run linting on staged files
|
||||
npx lint-staged
|
||||
|
||||
# Run tests for changed files
|
||||
CHANGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|jsx|ts|tsx)$' || true)
|
||||
|
||||
if [ -n "$CHANGED_FILES" ]; then
|
||||
echo "Running tests for changed files..."
|
||||
|
||||
# Check if client files changed
|
||||
if echo "$CHANGED_FILES" | grep -q "^client/"; then
|
||||
echo "Client files changed, running client tests..."
|
||||
cd client && npm run test:ci -- --passWithNoTests --bail
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# Check if API files changed
|
||||
if echo "$CHANGED_FILES" | grep -q "^api/"; then
|
||||
echo "API files changed, running API tests..."
|
||||
cd api && npm run test:ci -- --passWithNoTests --bail --maxWorkers=2
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# Check if package files changed
|
||||
if echo "$CHANGED_FILES" | grep -q "^packages/"; then
|
||||
echo "Package files changed, building packages..."
|
||||
npm run build:data-provider
|
||||
npm run build:data-schemas
|
||||
npm run build:api
|
||||
fi
|
||||
fi
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user