Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Git
.git
.gitignore

# Dependencies (will be installed in container)
node_modules

# IDE/Editor
.vscode
.idea
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# Logs
*.log
npm-debug.log*

# Test coverage
coverage

# Environment files (should be provided via docker-compose/env_file)
.env
.env.*
!.env.example

# Data directories (mounted as volumes)
data/
*.json
!package.json
!package-lock.json

# Build artifacts
dist
build

# Documentation (not needed at runtime)
docs/

# Scripts (not needed at runtime)
scripts/

# Docker
Dockerfile
docker-compose.yml
docker-entrypoint.sh
.dockerignore
114 changes: 83 additions & 31 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,31 +1,83 @@
PORT=9766
HOST=0.0.0.0
DEFAULT_PROVIDER=kimi
DEFAULT_MODEL=kimi-k2.5
AUTH_PATH=./auth.json
# Preferred for current GLM web app (chat.z.ai)
GLM_TOKEN=
# Legacy China ChatGLM backend (chatglm.cn); set GLM_BACKEND=chatglm to use this
GLM_REFRESH_TOKEN=
GLM_BACKEND=zai
# Optional: full Cookie header copied from a successful chat.z.ai browser completion request (keeps cdn/acw/ssxmod anti-bot cookies)
ZAI_COOKIE=
# Optional: captcha_verify_param copied from Z.ai browser completion request body. It is usually a base64 string, not JSON.
ZAI_CAPTCHA_VERIFY_PARAM=
# Optional browser fingerprint overrides for Z.ai request query/headers
ZAI_USER_AGENT=
ZAI_ACCEPT_LANGUAGE=en-US
ZAI_LANGUAGE=ru-RU
ZAI_LANGUAGES=ru-RU,ru,en-US,en
ZAI_TIMEZONE=Europe/Samara
ZAI_TIMEZONE_OFFSET=-240
KIMI_TOKEN=
# Set to 1 for local tests without real GLM/Kimi credentials
MOCK_PROVIDER=0
# Optional local keys accepted by proxy; empty disables auth check
API_KEYS=
# Admin / account-pool behavior
# 1 = POST/DELETE /admin/accounts persists auth.json by default; use persist:false per request to keep runtime-only
PERSIST_ADMIN_ACCOUNTS=1
# Milliseconds to skip an account after provider error before retrying it
ACCOUNT_COOLDOWN_MS=60000
PORT=9766
HOST=0.0.0.0
DEFAULT_PROVIDER=kimi
DEFAULT_MODEL=kimi-k2.5
AUTH_PATH=./auth.json
# Preferred for current GLM web app (chat.z.ai)
GLM_TOKEN=
# Legacy China ChatGLM backend (chatglm.cn); set GLM_BACKEND=chatglm to use this
GLM_REFRESH_TOKEN=
GLM_BACKEND=zai
# Optional: full Cookie header copied from a successful chat.z.ai browser completion request (keeps cdn/acw/ssxmod anti-bot cookies)
ZAI_COOKIE=
# Optional: captcha_verify_param copied from Z.ai browser completion request body. It is usually a base64 string, not JSON.
ZAI_CAPTCHA_VERIFY_PARAM=
# Optional browser fingerprint overrides for Z.ai request query/headers
ZAI_USER_AGENT=
ZAI_ACCEPT_LANGUAGE=en-US
ZAI_LANGUAGE=ru-RU
ZAI_LANGUAGES=ru-RU,ru,en-US,en
ZAI_TIMEZONE=Europe/Samara
ZAI_TIMEZONE_OFFSET=-240
# Z.ai browser fallback (Puppeteer/CloakBrowser). See docs/browser-fallback.md
# for what this is, and for the CPU/network-leak issue that was fixed here.
# 1: enable per-account (or set browser_fallback:true in auth.json instead)
# ZAI_BROWSER_FALLBACK=1
# cloak or puppeteer (default puppeteer)
# ZAI_BROWSER_ENGINE=puppeteer
# false to watch it work locally; default true (headless)
# ZAI_BROWSER_HEADLESS=true
# How long to wait for a UI completion response before giving up:
# ZAI_BROWSER_COMPLETION_TIMEOUT=180000
# How often to log a "still waiting" heartbeat while waiting on the above:
# ZAI_BROWSER_HEARTBEAT_MS=15000
# Close the whole browser after this long with zero requests (0 disables
# auto-close entirely). Relaunches lazily on the next request either way.
# ZAI_BROWSER_IDLE_CLOSE_MS=600000
# ZAI_BROWSER_IDLE_CHECK_MS=60000
# Extra Chromium launch flags (space-separated), appended to the built-in
# Docker-safe defaults (--disable-gpu, --disable-dev-shm-usage, etc.):
# ZAI_BROWSER_PUPPETEER_ARGS=
KIMI_TOKEN=
# Debugging REASON_CHAT_MESSAGE_NOT_FOUND on Kimi turn 2+: www.kimi.com's
# Connect-RPC Chat endpoint likely needs session/device identity headers we're
# not confirmed to be sending correctly. kimi.js best-effort-derives some from
# the JWT; KIMI_EXTRA_HEADERS (JSON) always overrides those guesses. Capture
# the real ones with `npm run kimi:dump-headers` (see script header for steps).
KIMI_LANGUAGE=en-US
KIMI_TIMEZONE=America/Los_Angeles
KIMI_EXTRA_HEADERS=
# Set to 1 to log every outgoing Kimi request headers + chat_id/parent_id
DEBUG_KIMI=0
# Set to 1 for local tests without real GLM/Kimi credentials
MOCK_PROVIDER=0
# Optional local keys accepted by proxy; empty disables auth check
API_KEYS=
# Admin / account-pool behavior
# 1 = POST/DELETE /admin/accounts persists auth.json by default; use persist:false per request to keep runtime-only
PERSIST_ADMIN_ACCOUNTS=1
# Milliseconds to skip an account after provider error before retrying it
ACCOUNT_COOLDOWN_MS=60000
# How long an idle conversation keeps its provider-side chat id before the
# next turn starts a fresh chat, and how many turns one chat id chain may
# grow to before the same reset happens. See docs/sessions.md.
SESSION_TTL_MS=7200000
SESSION_MAX_DEPTH=100

# Model catalog / auto-discover-and-retire. See docs/model-lifecycle.md.
# Where the discovered/retired model catalog persists across restarts
# (leave unset for in-memory only, reset on every restart):
# MODEL_CATALOG_PATH=/app/data/models.json
# Consecutive model-not-found-shaped failures before a model is auto-
# retired from GET /v1/models (it can still be requested explicitly;
# retirement only affects what gets advertised):
MODEL_RETIRE_AFTER_FAILURES=3
# Extra regex patterns (JSON array of strings) to recognize a provider
# error as a model-does-not-exist error, beyond the built-in ones in
# src/modelCatalog.js:
MODEL_ERROR_PATTERNS_EXTRA=
# Kimi only: override which upstream scenario (effectively: which model
# generation) a given model id maps to. The built-in guesses for
# kimi-k2.6/k2.7-code/k3 are UNCONFIRMED - verify with
# node scripts/kimi_dump_curl_headers.js and correct here if needed:
KIMI_SCENARIOS=
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ auth.jso
auth.*.json
*.log
.DS_Store
/data/zai-browser-profile
84 changes: 84 additions & 0 deletions .mimocode/commands/docker-test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
description: Build, start, and test the FreeGLMKimiAPI Docker container with a curl request to /v1/chat/completions
agent: main
---

# Docker Test Command

Build the Docker image, start the container, and run a test request against the OpenAI-compatible endpoint.

## Usage

```bash
/mimocode:command docker-test [model] [port] [--no-cache] [--mock]
```

## Parameters

- `model` (optional): Model to test. Default: `glm-5-search`
- `port` (optional): Host port mapped to container port 3364. Default: `3364`
- `--no-cache`: Run `docker-compose build --no-cache`
- `--mock`: Set `MOCK_PROVIDER=1` in container environment (tests without real credentials)

## Procedure

1. **Build the image**
```bash
cd /home/yury/Documents/FreeGLMKimiAPI
docker-compose build [--no-cache]
```

2. **Start the container** (detached)
```bash
docker-compose up -d
```

3. **Wait for server readiness** (5-10 seconds)
```bash
sleep 8
```

4. **Run test request**
```bash
curl -X POST http://localhost:${PORT:-3364}/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "${MODEL:-glm-5-search}",
"messages": [{"role": "user", "content": "hello"}],
"stream": false
}'
```

5. **Show container logs** (last 30 lines)
```bash
docker-compose logs --tail=30
```

6. **Stop container** (optional, for clean state)
```bash
docker-compose down
```

## Example Invocations

```bash
# Default test (glm-5-search on port 3364)
/mimocode:command docker-test

# Test kimi-k2.5 model
/mimocode:command docker-test kimi-k2.5

# Test with mock provider (no real API keys needed)
/mimocode:command docker-test glm-5-search --mock

# Full rebuild without cache
/mimocode:command docker-test --no-cache
```

## Notes

- The container exposes port 3364 internally; docker-compose.yml maps it to host port 3364 by default
- VNC is available at `vnc://localhost:3365` (password: `vampir`) when `ENABLE_VNC=1`
- Real credentials are loaded from `/home/yury/Documents/FreeGLMKimiAPI/data/auth.json` (mounted in container)
- `ZAI_BROWSER_ENGINE=cloak` is set in Dockerfile ENV for GLM browser fallback
- Node 22 and system chromium are pre-installed in the image
100 changes: 100 additions & 0 deletions .mimocode/commands/local-test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
description: Start FreeGLMKimiAPI server locally and test with a curl request to /v1/chat/completions
agent: main
---

# Local Test Command

Start the FreeGLMKimiAPI server locally (with optional mock provider) and run a test request against the OpenAI-compatible endpoint.

## Usage

```bash
/mimocode:command local-test [model] [port] [--mock] [--bg]
```

## Parameters

- `model` (optional): Model to test. Default: `glm-5-search`
- `port` (optional): Server port. Default: `9766` (from config.js)
- `--mock`: Set `MOCK_PROVIDER=1` to test without real API credentials
- `--bg`: Run server in background and leave it running after test

## Procedure

1. **Stop any existing server on the port**
```bash
pkill -f "node src/server.js" 2>/dev/null || true
sleep 1
```

2. **Start the server**
```bash
cd /home/yury/Documents/FreeGLMKimiAPI

# Environment setup
export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
export PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
export CHROME_PATH=/usr/bin/chromium
export ZAI_BROWSER_ENGINE=cloak

if [ "${MOCK:-false}" = "true" ]; then
export MOCK_PROVIDER=1
fi

PORT=${PORT:-9766} node src/server.js &
SERVER_PID=$!
sleep 3
```

3. **Verify server is running**
```bash
ps aux | grep "server.js" | grep -v grep
```

4. **Run test request**
```bash
curl -X POST http://localhost:${PORT:-9766}/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "${MODEL:-glm-5-search}",
"messages": [{"role": "user", "content": "hello"}],
"stream": false
}'
```

5. **Stop server** (unless `--bg` specified)
```bash
if [ "${BG:-false}" != "true" ]; then
kill $SERVER_PID 2>/dev/null || true
fi
```

## Example Invocations

```bash
# Default test (glm-5-search on port 9766)
/mimocode:command local-test

# Test kimi-k2.5 model
/mimocode:command local-test kimi-k2.5

# Test with mock provider (no real API keys needed)
/mimocode:command local-test glm-5-search --mock

# Start server in background and leave running
/mimocode:command local-test --bg

# Test specific port
/mimocode:command local-test glm-5-search 9767
```

## Notes

- Server runs on port 9766 by default (configured in `src/config.js`)
- Real credentials loaded from `/home/yury/Documents/FreeGLMKimiAPI/data/auth.json`
- `ZAI_BROWSER_ENGINE=cloak` required for GLM browser fallback to work
- System chromium at `/usr/bin/chromium` must be installed (Node 22+)
- First GLM browser request takes ~100s (chromium download + warmup); subsequent requests faster
- Kimi provider works end-to-end with real credentials (~2s response)
19 changes: 19 additions & 0 deletions .mimocode/plans/1786118231225-silent-harbor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Plan: Fix Identical Responses from GLM and Kimi Models

## Root Cause
The model names being sent include a `glmkimi-free/` prefix (e.g., `glmkimi-free/glm-5-search`, `glmkimi-free/kimi-k2.6-search`). The `resolveModel()` function in `src/config.js:67-73` only checks for models starting with `glm` or `kimi` (case-insensitive). With the prefix, neither condition matches, so both requests fall back to `DEFAULT_MODEL` (which is `kimi-k2.5` - a Kimi model). Both requests route to the Kimi provider, producing identical responses.

## Solution
Modify `resolveModel()` in `src/config.js` to strip known gateway prefixes before matching, OR add the prefix patterns to the matching logic.

## Files to Modify
- `src/config.js` - Update `resolveModel()` function

## Implementation Approach
Add prefix stripping logic at the start of `resolveModel()` to handle common gateway prefixes like `glmkimi-free/`. This ensures the actual model ID (`glm-5-search`, `kimi-k2.6-search`) is used for provider resolution.

## Verification
1. Send request with `model: "glmkimi-free/glm-5-search"` → should route to GLM provider (ZaiProvider since GLM_BACKEND=zai)
2. Send request with `model: "glmkimi-free/kimi-k2.6-search"` → should route to Kimi provider
3. Verify responses are different (from different backends)
4. Test without prefix still works
Loading