mirror of
https://github.com/THU-MAIC/OpenMAIC.git
synced 2026-10-02 01:15:18 +08:00
* feat(media): add OpenRouter image and video providers
OpenMAIC ships six separate video providers (Veo, Kling, Seedance,
MiniMax, Grok, HappyHorse) and seven image providers, each needing its
own key. OpenRouter fronts those same model families behind one key and
one account, so this adds it as a provider on both sides.
Both use OpenRouter's dedicated media endpoints, not chat-completions:
- Image: POST /images -> { data: [{ b64_json }] }
- Video: POST /videos -> 202 { id, status }, poll GET /videos/{id},
then GET /videos/{id}/content for the mp4 bytes
The model list is fetched live from GET /images/models and
GET /videos/models through /api/openrouter-models rather than pinned in
the registry: OpenRouter hosts 48 image and 28 video models today and
adds more, so a hardcoded shortlist would decide for the operator which
models exist. The registry keeps a three-entry seed as an offline
fallback, and the existing custom-model UI still accepts any model id.
Both catalogs answer unauthenticated, so the picker fills before a key
is pasted; a key is forwarded when present for proxied base URLs.
Adapter contracts are covered by stubbed-fetch tests (request shape,
empty-response handling, and the video job state machine including
terminal failure). No test performs a billable call.
Closes #1355
* fix(media): validate the key and tolerate a pasted endpoint URL
Three fixes found while configuring the new provider:
1. Both connectivity probes hit the model catalogs, which answer 200
unauthenticated — so "Test Connection" reported success for any
string, including an invalid key. Probe GET /key instead: equally
cheap, and it actually rejects a bad key.
2. The settings field is labelled "Base URL" but the panel echoes it
back as "Request URL", so pasting the full endpoint
(https://openrouter.ai/api/v1/images) is the natural mistake. That
built /api/v1/images/images and 404'd. Trim a trailing slash and a
trailing /images or /videos so both forms work; a proxy path that
merely contains the word is left alone.
3. The image and video settings panels read `data.message` on a failed
test, but failures answer with `error` (apiError) and only successes
carry `message`. Every failing connectivity test — for any provider,
not just OpenRouter — rendered "connection failed: undefined" instead
of the reason. Pre-existing; surfaced by 1 and 2 above.
Closes #1355
* fix(media): make every OpenRouter model selectable, and always select a provider
Two gaps found while configuring the new provider.
The settings Models list is a read-only catalog for every provider; the
actual model picker is the media popover. That picker built its groups
from the static registry array, so OpenRouter offered only the
three-entry seed while settings listed the full live catalog — the
models were visible but not choosable. Feed the same live catalog into
the popover, fetched only once the provider is usable so an
unconfigured install makes no request.
Separately, `imageProviderId`/`videoProviderId` are empty until a
provider is chosen (first-run auto-config leaves them blank when the
server reports no media provider). Opening the settings panel on an
empty id selected nothing: the header rendered the missing name key as
"settings.undefined", and Test Connection posted a blank
x-image-provider/x-video-provider, so it failed with "No image/video
provider configured" whatever key was typed. Fall back to the first
catalog entry so the panel always has a selection. Pre-existing and not
specific to OpenRouter.
Closes #1355
* fix(tts): request a browser-playable format from custom providers
`generateOpenAITTS` serves every custom OpenAI-compatible TTS provider but
never sent `response_format`, so it inherited whatever each provider
defaults to. OpenAI defaults to mp3; OpenRouter's /audio/speech defaults
to raw `pcm`. The unknown content type then fell through to the `'mp3'`
default below, the client built `data:audio/mp3;base64,…` from headerless
PCM samples, and playback failed with "no supported source was found" —
while the server logged a clean 200, because the audio really was
generated. Name the format instead of inheriting it.
Also stop mislabelling an unrecognised body: `pcm`/`l16` now raises a
message naming the cause, and `aac`/`opus` are recognised.
Two supporting fixes:
- /api/openrouter-models normalises its base URL the way the adapters do
and falls back to the public catalog when a custom base URL fails, so a
typo in a free-text settings field cannot empty the model picker. Also
types the headers object so tsc accepts the conditional.
- provider-neutrality-guard pins exact per-vendor occurrence counts in
lib/server/provider-config.ts. Adding the image and video env entries
raises "openrouter" from 2 to 6 (each entry contributes both its key and
its value); CI failed without the bump.
Closes #1355
* fix(security): never send the operator key to a client-chosen host
Review found `/api/openrouter-models` was an SSRF and key-exfiltration
path, and the finding is correct. The route took `x-base-url` from the
caller at highest precedence while preferring the *server* env key, so any
caller could make the server send the operator's OpenRouter credential as
an `Authorization: Bearer` header to an arbitrary URL. The route's own
comment claimed it followed `/api/verify-image-provider`; that pattern
runs `validateUrlForSSRF` on client base URLs, and this route did not.
The boundary is now explicit: the server key travels only to the
operator's own base URL. A client-supplied URL is SSRF-validated and
carries only that caller's own `x-api-key` — the server key is dropped —
and the unauthenticated public-catalog fallback never forwards a
credential chosen for a different host. Redirects are no longer followed
(`redirect: 'manual'`), since a redirect would carry the Authorization
header off-host and reopen the same hole, and upstream reads are bounded
by a timeout.
The per-URL cache is now keyed by destination *and* a hash of the
credential, and bounded to 64 entries with oldest-first eviction, so
client-supplied URLs cannot grow it without limit and one caller's
key-authorised catalog is never served to another.
Also from the review:
- The image adapter discarded the reported `media_type`. The
orchestration layer wraps a bare `base64` as `data:image/png`
unconditionally, so jpeg/webp results were mislabelled; the adapter now
returns a data URL carrying the real type.
- Adapter generation and poll requests set `redirect: 'manual'`, matching
the `/key` probe that already did.
- `runPolledTask` accepts an `AbortSignal` so the sleep between polls is
cancellable; the video adapter passes the caller's signal. Without it a
cancelled generation still slept out a full 10s interval.
Tests cover the highest-risk paths the review named: which credential
reaches which URL, that an SSRF-rejected destination is never contacted,
that the fallback is unauthenticated, cache isolation between callers,
and MIME preservation.
Findings 2 (base-URL normalisation) and 4 (neutrality-guard debt) were
already fixed in d553a08, pushed after the review was submitted; CI is
green on that commit.
Closes #1355
* ci: retry flaky voice clone timeout
* fix(vercel): keep OpenRouter catalogs within Hobby function limit
* fix(vercel): avoid tracing self-hosted sharp binaries
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: wyuc <wang-yc24@mails.tsinghua.edu.cn>
606 lines
26 KiB
Bash
606 lines
26 KiB
Bash
# =============================================================================
|
|
# OpenMAIC Environment Variables
|
|
# Copy this file to .env.local and fill in the values you need.
|
|
# All variables are optional — only configure the providers you want to use.
|
|
# You can also use server-providers.yml for configuration (see docs).
|
|
# =============================================================================
|
|
|
|
# --- LLM Providers -----------------------------------------------------------
|
|
# Format: {PROVIDER}_API_KEY, {PROVIDER}_BASE_URL (optional), {PROVIDER}_MODELS (optional, comma-separated)
|
|
|
|
OPENAI_API_KEY=
|
|
OPENAI_BASE_URL=
|
|
OPENAI_MODELS=
|
|
# For relays whose non-streaming Chat Completions response is incompatible.
|
|
# Forces custom OpenAI base URLs to use Chat Completions and buffers SSE responses.
|
|
# Has no effect on the official OpenAI base URL. Disabled by default.
|
|
# OPENAI_COMPAT_USE_STREAMING_CHAT=true
|
|
|
|
# Azure uses deployment names as model IDs.
|
|
AZURE_OPENAI_API_KEY=
|
|
AZURE_OPENAI_BASE_URL=https://YOUR-RESOURCE.openai.azure.com/openai
|
|
AZURE_OPENAI_MODELS=
|
|
|
|
ATLASCLOUD_API_KEY=
|
|
ATLASCLOUD_BASE_URL=https://api.atlascloud.ai/v1
|
|
# Example: qwen/qwen3.5-flash,deepseek-ai/deepseek-v4-pro
|
|
ATLASCLOUD_MODELS=
|
|
|
|
ANTHROPIC_API_KEY=
|
|
ANTHROPIC_BASE_URL=
|
|
ANTHROPIC_MODELS=
|
|
|
|
GOOGLE_API_KEY=
|
|
GOOGLE_BASE_URL=
|
|
GOOGLE_MODELS=
|
|
|
|
DEEPSEEK_API_KEY=
|
|
DEEPSEEK_BASE_URL=
|
|
# Example: deepseek-v4-pro,deepseek-v4-flash,deepseek-v4-flash-vision-exp
|
|
DEEPSEEK_MODELS=
|
|
|
|
QWEN_API_KEY=
|
|
QWEN_BASE_URL=
|
|
QWEN_MODELS=
|
|
|
|
KIMI_API_KEY=
|
|
KIMI_BASE_URL=
|
|
KIMI_MODELS=
|
|
|
|
MINIMAX_API_KEY=
|
|
# MiniMax Anthropic-compatible endpoint for the built-in Anthropic SDK integration
|
|
MINIMAX_BASE_URL=https://api.minimaxi.com/anthropic/v1
|
|
# Example: MiniMax-M2.7-highspeed,MiniMax-M2.7,MiniMax-M2.5-highspeed,MiniMax-M2.5
|
|
MINIMAX_MODELS=
|
|
|
|
GLM_API_KEY=
|
|
GLM_BASE_URL=
|
|
GLM_MODELS=
|
|
|
|
SILICONFLOW_API_KEY=
|
|
SILICONFLOW_BASE_URL=
|
|
SILICONFLOW_MODELS=
|
|
|
|
DOUBAO_API_KEY=
|
|
DOUBAO_BASE_URL=
|
|
DOUBAO_MODELS=
|
|
|
|
OPENROUTER_API_KEY=
|
|
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
|
|
# Example: deepseek/deepseek-v4-pro,deepseek/deepseek-v4-flash
|
|
OPENROUTER_MODELS=
|
|
|
|
GROK_API_KEY=
|
|
GROK_BASE_URL=
|
|
# Example: grok-4.6,grok-4.5
|
|
GROK_MODELS=
|
|
|
|
TENCENT_API_KEY=
|
|
# Tencent TokenHub OpenAI-compatible endpoint. Hy3 is a model ID, not an env prefix.
|
|
# TENCENT_HUNYUAN_* is also accepted as an alias.
|
|
TENCENT_BASE_URL=https://tokenhub.tencentmaas.com/v1
|
|
# Example: hy3-preview,hunyuan-2.0-thinking-20251109,hunyuan-2.0-instruct-20251111
|
|
TENCENT_MODELS=
|
|
|
|
XIAOMI_API_KEY=
|
|
# MIMO_* is also accepted as an alias. Use tp-... keys only with Token Plan URLs.
|
|
XIAOMI_BASE_URL=https://api.xiaomimimo.com/v1
|
|
# Token Plan regional examples:
|
|
# XIAOMI_BASE_URL=https://token-plan-cn.xiaomimimo.com/v1
|
|
# XIAOMI_BASE_URL=https://token-plan-sgp.xiaomimimo.com/v1
|
|
# XIAOMI_BASE_URL=https://token-plan-ams.xiaomimimo.com/v1
|
|
# Example: mimo-v2.5-pro,mimo-v2-pro,mimo-v2.5,mimo-v2-omni,mimo-v2-flash
|
|
XIAOMI_MODELS=
|
|
|
|
TOKENDANCE_API_KEY=
|
|
# OpenAI-compatible gateway. The same key also works for the image, video, TTS
|
|
# and web-search routes on this host (see the README quick example).
|
|
TOKENDANCE_BASE_URL=https://tokendance.space/gateway/v1
|
|
# Example: deepseek-v4.1-flash,deepseek-v4-pro,glm-5.3,kimi-k3,qwen3.8-max
|
|
TOKENDANCE_MODELS=
|
|
|
|
# --- Ollama (Local Models) ---------------------------------------------------
|
|
# No API key needed. Configure BASE_URL here (server-side) so it bypasses SSRF
|
|
# protection automatically. Client-supplied localhost URLs are blocked in production.
|
|
# OLLAMA_BASE_URL=http://localhost:11434/v1
|
|
# OLLAMA_MODELS=llama3.3,llama3.2,qwen2.5,mistral,gemma3
|
|
|
|
# Lemonade local server (OpenAI-compatible, no API key required)
|
|
# LEMONADE_BASE_URL=http://localhost:13305/v1
|
|
# LEMONADE_MODELS=Qwen3-0.6B-GGUF,Llama-3.2-1B-Instruct-Hybrid,Qwen2.5-VL-7B-Instruct
|
|
|
|
# Amazon Bedrock LLMs (no OpenAI-style API key required)
|
|
# Set BEDROCK_REGION to enable Bedrock server-side provider config.
|
|
# AWS credentials are resolved from the standard AWS environment / credential chain.
|
|
# BEDROCK_REGION=us-east-1
|
|
# BEDROCK_MODELS=us.anthropic.claude-sonnet-5,us.anthropic.claude-opus-4-8
|
|
# Optional bearer-token authentication or custom Bedrock-compatible endpoint.
|
|
# AWS_BEARER_TOKEN_BEDROCK=
|
|
# BEDROCK_API_KEY=
|
|
# BEDROCK_BASE_URL=
|
|
# DEFAULT_MODEL=bedrock:us.anthropic.claude-sonnet-5
|
|
|
|
# --- TTS (Text-to-Speech) ----------------------------------------------------
|
|
|
|
TTS_OPENAI_API_KEY=
|
|
TTS_OPENAI_BASE_URL=
|
|
|
|
TTS_AZURE_API_KEY=
|
|
TTS_AZURE_BASE_URL=
|
|
|
|
TTS_GLM_API_KEY=
|
|
TTS_GLM_BASE_URL=
|
|
|
|
TTS_QWEN_API_KEY=
|
|
TTS_QWEN_BASE_URL=
|
|
# Qwen voice cloning reuses TTS_QWEN_API_KEY. Override the target model if needed.
|
|
# TTS_QWEN_VOICE_CLONE_MODEL=qwen3-tts-vc-2026-01-22
|
|
|
|
TTS_DOUBAO_API_KEY=
|
|
TTS_DOUBAO_BASE_URL=
|
|
|
|
TTS_MINIMAX_API_KEY=
|
|
# MiniMax TTS endpoint (speech-2.8 / 2.6 / 02 / 01 series)
|
|
TTS_MINIMAX_BASE_URL=https://api.minimaxi.com
|
|
TTS_ELEVENLABS_API_KEY=
|
|
TTS_ELEVENLABS_BASE_URL=
|
|
|
|
# VoxCPM2 TTS (local, OpenAI-compatible; API key is optional)
|
|
# TTS_VOXCPM_API_KEY=
|
|
# TTS_VOXCPM_BASE_URL=http://localhost:8000/v1
|
|
|
|
# Lemonade TTS (local, no API key required)
|
|
# TTS_LEMONADE_BASE_URL=http://localhost:13305/v1
|
|
|
|
# Operators can force-disable any built-in TTS provider. Examples:
|
|
# TTS_OPENAI_ENABLED=false
|
|
# TTS_BROWSER_NATIVE_ENABLED=false
|
|
|
|
# --- ASR (Automatic Speech Recognition) --------------------------------------
|
|
|
|
ASR_OPENAI_API_KEY=
|
|
ASR_OPENAI_BASE_URL=
|
|
|
|
ASR_QWEN_API_KEY=
|
|
ASR_QWEN_BASE_URL=
|
|
|
|
ASR_AZURE_API_KEY=
|
|
ASR_AZURE_BASE_URL=https://{region}.api.cognitive.microsoft.com
|
|
|
|
# FunASR (local, WAV input only, no API key required)
|
|
# ASR_FUNASR_BASE_URL=http://localhost:8000/v1
|
|
|
|
# Lemonade ASR (local, WAV input only, no API key required)
|
|
# ASR_LEMONADE_BASE_URL=http://localhost:13305/v1
|
|
|
|
# Operators can force-disable any built-in ASR provider. Examples:
|
|
# ASR_OPENAI_ENABLED=false
|
|
# ASR_BROWSER_NATIVE_ENABLED=false
|
|
|
|
# Optional local audio/video material extraction uses the first enabled server
|
|
# ASR provider above. It also requires the system `ffmpeg` and `ffprobe`
|
|
# executables on PATH; no bundled binary or npm dependency is installed.
|
|
# Without both executables, OpenMAIC skips the local extractor and uses a
|
|
# configured AliDocMind cloud extractor when available. With neither path
|
|
# enabled, media materials fail cleanly with setup guidance.
|
|
|
|
# --- PDF Processing -----------------------------------------------------------
|
|
|
|
PDF_UNPDF_API_KEY=
|
|
PDF_UNPDF_BASE_URL=
|
|
|
|
PDF_MINERU_API_KEY=
|
|
PDF_MINERU_BASE_URL=
|
|
# Optional. Defaults to "pipeline"; use "hybrid-auto-engine" only when your MinerU
|
|
# service has the required GPU/device configuration.
|
|
PDF_MINERU_BACKEND=
|
|
|
|
PDF_MINERU_CLOUD_API_KEY=
|
|
PDF_MINERU_CLOUD_BASE_URL=https://mineru.net/api/v4
|
|
# Self-hosted MinerU never falls back to MinerU Cloud implicitly: a request that
|
|
# selects self-hosted MinerU without a configured base URL fails loudly. Set this
|
|
# to "true" to explicitly opt in to MinerU Cloud as a fallback (documents then
|
|
# leave your infrastructure). Default: off.
|
|
ALLOW_MINERU_CLOUD_FALLBACK=
|
|
|
|
# AliDocMind uses an Alibaba Cloud AccessKey pair instead of a single API key.
|
|
ALIDOCMIND_ACCESS_KEY_ID=
|
|
ALIDOCMIND_ACCESS_KEY_SECRET=
|
|
ALIDOCMIND_BASE_URL=
|
|
|
|
# --- Image Generation ---------------------------------------------------------
|
|
|
|
IMAGE_OPENAI_API_KEY=
|
|
IMAGE_OPENAI_BASE_URL=https://api.openai.com/v1
|
|
|
|
IMAGE_SEEDREAM_API_KEY=
|
|
IMAGE_SEEDREAM_BASE_URL=
|
|
|
|
IMAGE_QWEN_IMAGE_API_KEY=
|
|
IMAGE_QWEN_IMAGE_BASE_URL=
|
|
|
|
IMAGE_NANO_BANANA_API_KEY=
|
|
IMAGE_NANO_BANANA_BASE_URL=
|
|
|
|
IMAGE_MINIMAX_API_KEY=
|
|
# Example models: image-01, image-01-live
|
|
IMAGE_MINIMAX_BASE_URL=https://api.minimaxi.com
|
|
|
|
IMAGE_GROK_API_KEY=
|
|
IMAGE_GROK_BASE_URL=
|
|
|
|
# OpenRouter image generation. Optional; read at runtime. One key reaches every
|
|
# image model OpenRouter hosts (FLUX, Seedream, GPT Image, Gemini, Qwen Image,
|
|
# Recraft, Krea, ...). The model list in Settings is fetched live from
|
|
# GET /images/models, so no model id is pinned here. Base URL defaults to
|
|
# https://openrouter.ai/api/v1 when left blank.
|
|
IMAGE_OPENROUTER_API_KEY=
|
|
IMAGE_OPENROUTER_BASE_URL=
|
|
|
|
# Lemonade image generation (local, no API key required)
|
|
# IMAGE_LEMONADE_BASE_URL=http://localhost:13305/v1
|
|
|
|
# Operators can force-disable any built-in image provider, including the
|
|
# client-only ComfyUI provider (it has no credential env). Examples:
|
|
# IMAGE_OPENAI_ENABLED=false
|
|
# IMAGE_COMFYUI_ENABLED=false
|
|
|
|
# --- Video Generation ---------------------------------------------------------
|
|
|
|
VIDEO_SEEDANCE_API_KEY=
|
|
VIDEO_SEEDANCE_BASE_URL=
|
|
|
|
VIDEO_KLING_API_KEY=
|
|
VIDEO_KLING_BASE_URL=
|
|
|
|
VIDEO_VEO_API_KEY=
|
|
VIDEO_VEO_BASE_URL=
|
|
|
|
VIDEO_SORA_API_KEY=
|
|
VIDEO_SORA_BASE_URL=
|
|
|
|
VIDEO_MINIMAX_API_KEY=
|
|
# Example models: MiniMax-Hailuo-2.3, MiniMax-Hailuo-2.3-Fast, MiniMax-Hailuo-02
|
|
VIDEO_MINIMAX_BASE_URL=https://api.minimaxi.com
|
|
|
|
VIDEO_GROK_API_KEY=
|
|
VIDEO_GROK_BASE_URL=
|
|
|
|
VIDEO_HAPPYHORSE_API_KEY=
|
|
VIDEO_HAPPYHORSE_BASE_URL=https://dashscope.aliyuncs.com
|
|
|
|
# OpenRouter video generation. Optional; read at runtime. One key reaches every
|
|
# video model OpenRouter hosts (Veo, Kling, Runway, Seedance, Hailuo, Wan,
|
|
# Sora, Grok Imagine, ...). The model list in Settings is fetched live from
|
|
# GET /videos/models, so no model id is pinned here. Base URL defaults to
|
|
# https://openrouter.ai/api/v1 when left blank.
|
|
VIDEO_OPENROUTER_API_KEY=
|
|
VIDEO_OPENROUTER_BASE_URL=
|
|
|
|
# Operators can force-disable any built-in video provider. Examples:
|
|
# VIDEO_GROK_ENABLED=false
|
|
# VIDEO_KLING_ENABLED=false
|
|
|
|
# --- Web Search ---------------------------------------------------------------
|
|
# Note: Grok (xAI) web search is available via chat completions + search tools,
|
|
# not as a standalone search API. Use Grok LLM provider with search_parameters
|
|
# in chat requests. See: https://docs.x.ai/docs/guides/tools/search-tools
|
|
|
|
TAVILY_API_KEY=
|
|
TAVILY_BASE_URL=
|
|
EXA_API_KEY=
|
|
EXA_BASE_URL=https://api.exa.ai
|
|
BOCHA_API_KEY=
|
|
BOCHA_BASE_URL=https://api.bocha.cn
|
|
BRAVE_API_KEY=
|
|
BRAVE_BASE_URL=
|
|
BAIDU_API_KEY=
|
|
BAIDU_BASE_URL=https://qianfan.baidubce.com
|
|
# Self-hosted SearXNG instance (no API key required)
|
|
SEARXNG_BASE_URL=
|
|
# Dedicated MiniMax web-search vars avoid conflicting with the LLM MINIMAX_* endpoint.
|
|
WEB_SEARCH_MINIMAX_API_KEY=
|
|
WEB_SEARCH_MINIMAX_BASE_URL=https://api.minimaxi.com
|
|
# Dedicated Doubao web-search vars avoid conflicting with the Doubao LLM provider.
|
|
WEB_SEARCH_DOUBAO_API_KEY=
|
|
WEB_SEARCH_DOUBAO_BASE_URL=https://open.feedcoopapi.com
|
|
# Claude (Anthropic) native web search. Dedicated vars avoid conflicting with
|
|
# ANTHROPIC_* LLM provider vars. Optional WEB_SEARCH_CLAUDE_MODELS pins the
|
|
# search model server-side (first entry wins), e.g. claude-sonnet-5.
|
|
WEB_SEARCH_CLAUDE_API_KEY=
|
|
WEB_SEARCH_CLAUDE_BASE_URL=https://api.anthropic.com/v1
|
|
WEB_SEARCH_CLAUDE_MODELS=
|
|
|
|
# Operators can force-disable any built-in web search provider. Examples:
|
|
# TAVILY_ENABLED=false
|
|
# EXA_ENABLED=false
|
|
# WEB_SEARCH_DOUBAO_ENABLED=false
|
|
# SEARXNG_ENABLED=false
|
|
|
|
# Server-only, default-OFF selector for the Native Child execution harness.
|
|
# OPENMAIC_ENABLE_PI_NATIVE_CHILD_RUNTIME=true
|
|
# Server-only, default-OFF Native Spotlight capability; does not select the runtime.
|
|
# OPENMAIC_ENABLE_PI_NATIVE_CHILD_SPOTLIGHT=true
|
|
|
|
# --- Experimental Features ---------------------------------------------------
|
|
|
|
# Boolean feature flags accept "true" or "1". NEXT_PUBLIC_* values are compiled
|
|
# into the browser bundle at build time, so changing them requires a rebuild.
|
|
|
|
# Enable the Pro workbench entry (the workbench also requires the agent
|
|
# runtime to be configured server-side; see the Agent Runtime section).
|
|
# Implies the MAIC Editor gate below — Pro mode always ships with the editor.
|
|
# NEXT_PUBLIC_PRO_WORKBENCH_ENABLED=true
|
|
|
|
# Master gate for the MAIC Editor Pro-mode entry point. Implied by
|
|
# NEXT_PUBLIC_PRO_WORKBENCH_ENABLED; set it alone to enable the classroom
|
|
# editor on a deployment without the workbench.
|
|
# NEXT_PUBLIC_MAIC_EDITOR_ENABLED=true
|
|
|
|
# Select @openmaic/editor inside Pro mode. This does not enable Pro mode by itself.
|
|
# NEXT_PUBLIC_MAIC_EDITOR_RENDERER_ENABLED=true
|
|
|
|
# Use @openmaic/renderer for the classroom playback canvas.
|
|
# NEXT_PUBLIC_MAIC_PLAYBACK_RENDERER_ENABLED=true
|
|
|
|
# Use the experimental Pi-based classroom chat runtime. Disabled by default.
|
|
# NEXT_PUBLIC_PI_CHAT_ENABLED=true
|
|
|
|
# Enable the unified PPT/Interactive courseware-reference entry in Pi playback.
|
|
# Pi chat and editor element references remain independent from this default-off build-time gate.
|
|
# Changing a NEXT_PUBLIC_* value requires rebuilding the application.
|
|
# NEXT_PUBLIC_COURSEWARE_REFERENCE_ENABLED=true
|
|
|
|
# Enable the server-side vocational task-engine generation path.
|
|
# OPENMAIC_ENABLE_VOCATIONAL=true
|
|
|
|
# Show the experimental vocational task-engine control in the client.
|
|
# NEXT_PUBLIC_SHOW_VOCATIONAL_TEST_UI=true
|
|
|
|
# Show the video export and PPTX import entry points.
|
|
# NEXT_PUBLIC_ENABLE_VIDEO_EXPORT=true
|
|
# NEXT_PUBLIC_ENABLE_PPTX_IMPORT=true
|
|
|
|
# Informational destination shown on exported Quiz/PBL cover cards. Unset or
|
|
# blank defaults to open.maic.chat; set to "off" to omit it.
|
|
# NEXT_PUBLIC_VIDEO_EXPORT_CTA_DESTINATION=open.maic.chat
|
|
|
|
# --- Agent Runtime (experimental) ---------------------------------------------
|
|
|
|
# Server-only gate for durable background agent sessions: the /api/agent
|
|
# session and owner-event control-plane routes plus the in-process session
|
|
# runner. Default OFF — while disabled, every /api/agent/sessions* and
|
|
# /api/agent/owner-events route answers 404. Truthy values are "true" or "1";
|
|
# anything else (including unset) is treated as disabled.
|
|
# OPENMAIC_AGENT_RUNTIME_ENABLED=true
|
|
|
|
# The runtime is server-backed and requires the PostgreSQL connection from the
|
|
# "Server-backed Persistence" section below: without a non-empty DATABASE_URL
|
|
# the runner never starts and the session store rejects requests, even with the
|
|
# flag above enabled.
|
|
# DATABASE_URL=postgres://openmaic:password@postgres:5432/openmaic
|
|
|
|
# REQUIRED while the runtime is enabled: MODEL_ROUTES must explicitly route the
|
|
# "maic-agent-driver" stage to a provider-prefixed model id. There is
|
|
# intentionally no fallback — without this route every agent session fails at
|
|
# run start. The route object must set api (or its alias dialect) to
|
|
# "openai-completions" or "openai-responses"; any other value is rejected, and
|
|
# a bare model id without a provider prefix is rejected too. Optional fields:
|
|
# contextWindow pins the effective context window below the provider catalog
|
|
# value (used by compaction thresholds), and thinking must never set effort
|
|
# (the tool-using driver cannot combine reasoning_effort with function tools on
|
|
# this transport).
|
|
# MODEL_ROUTES='{"maic-agent-driver":{"model":"openai:gpt-5.5","api":"openai-completions"}}'
|
|
|
|
# Runner tuning. Defaults are shown; only relevant once the runtime is enabled.
|
|
# OPENMAIC_AGENT_RUNTIME_SCAN_INTERVAL_MS=1000
|
|
# OPENMAIC_AGENT_RUNTIME_HEARTBEAT_MS=2000
|
|
# OPENMAIC_AGENT_RUNTIME_LEASE_TTL_MS=10000
|
|
# OPENMAIC_AGENT_RUNTIME_MAX_CONCURRENT=2
|
|
# OPENMAIC_AGENT_RUNTIME_MAX_ATTEMPTS=5
|
|
|
|
# Global per-tool-call execution bound for every agent run (ms). A tool call
|
|
# that neither resolves nor rejects within the budget is aborted and settles as
|
|
# an error tool-result the agent can retry or proceed from; the session does
|
|
# not die. Default 600000 (10 minutes). Tools with known longer budgets (media
|
|
# synthesis, material extraction) carry their own explicit bounds in code.
|
|
# OPENMAIC_AGENT_TOOL_TIMEOUT_MS=600000
|
|
|
|
# Conversation compaction is reserved and OFF by default. The reusable
|
|
# compaction runtime is not implemented yet — it lands in a later slice of
|
|
# work — and until then the runner runs without context transformation, so
|
|
# these knobs are inert placeholders.
|
|
# OPENMAIC_AGENT_COMPACTION_ENABLED=true
|
|
# OPENMAIC_AGENT_COMPACTION_RESERVE_TOKENS=0
|
|
# OPENMAIC_AGENT_COMPACTION_KEEP_RECENT_TOKENS=0
|
|
|
|
# Session prompts and follow-up messages are capped server-side at a fixed
|
|
# 100,000 characters; this limit is a constant and is not configurable.
|
|
|
|
# --- Proxy (optional) --------------------------------------------------------
|
|
|
|
# HTTP_PROXY=
|
|
# HTTPS_PROXY=
|
|
# Comma-separated hosts that bypass the proxy. Supports domain suffixes and *.
|
|
# NO_PROXY=localhost,127.0.0.1,.internal.example.com
|
|
|
|
# --- Misc ---------------------------------------------------------------------
|
|
|
|
# Server-side default model for API routes like /api/generate-classroom.
|
|
# Required for server-side stages (those that don't receive a client x-model):
|
|
# resolveModel throws if a stage resolves to no model (no MODEL_ROUTES entry, no
|
|
# x-model, no DEFAULT_MODEL) — there is intentionally no hardcoded vendor fallback.
|
|
# Example: anthropic:claude-3-5-haiku-20241022 or google:gemini-3-flash-preview
|
|
# OpenAI example: openai:gpt-5.5
|
|
# MiniMax example: minimax:MiniMax-M2.7-highspeed
|
|
# Bedrock example: bedrock:us.anthropic.claude-sonnet-5
|
|
DEFAULT_MODEL=
|
|
|
|
# Optional per-stage model routing (#745). A JSON object mapping a generation
|
|
# stage to a model string (`provider:model`). For most stages, resolution order
|
|
# is stage route > x-model (client) > DEFAULT_MODEL. A configured route is the
|
|
# operator's deliberate choice and wins even when the browser sends its saved
|
|
# model as x-model. `conversation-title` is the exception: when unconfigured it
|
|
# reuses the exact `maic-agent-driver` connection, never x-model or DEFAULT_MODEL,
|
|
# and keeps thinking disabled unless its own route explicitly enables it.
|
|
# At boot the server validates MODEL_ROUTES / DEFAULT_MODEL / <PREFIX>_MODELS
|
|
# and prints [config] warnings for unknown stages, unregistered providers,
|
|
# providers with no API key, and bare model ids (which still default to openai
|
|
# but are deprecated — write provider:model). Warnings only: the server starts
|
|
# regardless, so a bad value is caught here instead of at request time.
|
|
# Routable stages: scene-outlines-stream, scene-content, scene-actions,
|
|
# agent-profiles, quiz-grade, pbl-chat, pbl-v2-runtime, chat-adapter,
|
|
# generate-classroom, web-search-query-rewrite, maic-agent,
|
|
# maic-agent-driver, conversation-title.
|
|
# scene-content can also be routed per scene type with composite keys:
|
|
# scene-content:slide, scene-content:quiz, scene-content:interactive,
|
|
# scene-content:pbl. A type falls back to the base scene-content route when it
|
|
# has no key of its own (so scene-content:<type> > scene-content > x-model >
|
|
# DEFAULT_MODEL).
|
|
# pbl-v2-runtime follows the same composite fallback pattern with
|
|
# pbl-v2-runtime:instructor, pbl-v2-runtime:open-task, pbl-v2-runtime:evaluate
|
|
# and pbl-v2-runtime:simulator, falling back to the base pbl-v2-runtime route.
|
|
# maic-agent-driver is REQUIRED when the agent runtime is enabled; see the
|
|
# "Agent runtime (experimental)" section for its api/dialect constraints.
|
|
# A route value can be a model string, OR an object {"model","thinking"} where
|
|
# `thinking` is the full ThinkingConfig: mode (default|disabled|enabled|auto),
|
|
# effort (none|minimal|low|medium|high|xhigh|max), level (minimal|low|medium|
|
|
# high, Gemini), enabled (bool), budgetTokens (number), excludeReasoningOutput
|
|
# (bool). It is normalized per the model's capability. When a stage is routed:
|
|
# a set `thinking` wins over the client's thinking; with no `thinking` the routed
|
|
# model uses its own default and the client's thinking is dropped. Unrouted
|
|
# stages keep the client thinking.
|
|
# Example: cheap default, stronger model only for the heavy/conversational stages:
|
|
# MODEL_ROUTES='{"scene-content":"openai:gpt-5.4","scene-actions":"openai:gpt-5.4","pbl-chat":"anthropic:claude-sonnet-4","chat-adapter":"anthropic:claude-sonnet-4"}'
|
|
# Example: per scene type + pinned thinking (qwen budget, deepseek off):
|
|
# MODEL_ROUTES='{"scene-content:interactive":{"model":"qwen:qwen3.7-plus","thinking":{"enabled":true,"budgetTokens":8000}},"scene-content:quiz":{"model":"deepseek:deepseek-v4-pro","thinking":{"enabled":false}}}'
|
|
# MODEL_ROUTES=
|
|
|
|
# LOG_LEVEL=info
|
|
# LOG_FORMAT=pretty
|
|
# LLM_THINKING_DISABLED=false
|
|
|
|
# Opt-in parallel scene-content generation (#572). 0/unset = serial (default).
|
|
# A value > 1 fetches scene content concurrently (capped at 10); actions + TTS
|
|
# stay serial. Leave off if your API key has a low per-key concurrency quota.
|
|
# PARALLEL_SCENE_CONCURRENCY=3
|
|
|
|
# --- Local/Self-hosted Deployment ---------------------------------------------
|
|
# Set to "true" to allow private/local network URLs. That covers private
|
|
# (RFC1918), loopback, link-local and CGNAT (100.64.0.0/10, used by Tailscale
|
|
# and similar overlay networks) targets. Required for self-hosted models like
|
|
# Ollama. Do NOT enable on public deployments.
|
|
# The known cloud instance-metadata and credential endpoints (169.254.169.254,
|
|
# 169.254.170.2, 169.254.170.23, 100.100.100.200, 168.63.129.16, 192.0.0.192,
|
|
# fd00:ec2::254, fd00:ec2::23, metadata.google.internal) are blocked with or
|
|
# without this flag, as are IANA reserved, multicast and broadcast ranges. That
|
|
# is a fixed address list, not a general link-local block, and under the flag a
|
|
# hostname whose DNS lookup fails, times out or returns no answer is still
|
|
# allowed through.
|
|
# ALLOW_LOCAL_NETWORKS=true
|
|
|
|
# Build-time, space-separated CSP frame-ancestor sources in addition to 'self'.
|
|
# Configure only origins you trust to embed OpenMAIC, then rebuild the app. The
|
|
# Docker and Compose builds also accept this value as a build argument.
|
|
# ALLOWED_FRAME_ANCESTORS=https://partner.example.com
|
|
|
|
# Optional MP4 render service (issue #866). When set, the in-app "Export Video"
|
|
# menu offers one-click MP4 rendering; when unset, it degrades to downloading a
|
|
# project ZIP for local CLI rendering. Point this at the isolated render-service
|
|
# container (see render-service/ and the "video-export" docker-compose profile).
|
|
# This is operator-supplied trusted config: the app forwards uploads to it
|
|
# without the SSRF guard, so a private/compose-network target works WITHOUT
|
|
# setting ALLOW_LOCAL_NETWORKS.
|
|
# RENDER_SERVICE_URL=http://render-service:9000
|
|
|
|
# Render-service-only opt-in for bounded local chunk execution. These variables
|
|
# are read at runtime by the isolated render-service container; the public HTTP
|
|
# API is unchanged. Defaults keep the existing in-process renderer.
|
|
# RENDER_CHUNK_EXECUTION=false
|
|
# RENDER_CHUNK_COUNT=1
|
|
# RENDER_CHUNK_WORKERS=1
|
|
# RENDER_MAX_PARALLEL_CHUNKS=1
|
|
# RENDER_CHUNK_SIZE_FRAMES=0
|
|
# RENDER_TARGET_CHUNK_FRAMES=0
|
|
|
|
# Honor x-forwarded-for / x-real-ip when deriving client identity for both
|
|
# render-service admission and access-code verification throttling.
|
|
# Enable only behind a trusted reverse proxy that overwrites these headers.
|
|
# TRUST_PROXY_HEADERS=true
|
|
|
|
# --- Server-backed Persistence ------------------------------------------------
|
|
|
|
# Build-time client switch and shared development token. The public token must
|
|
# match PERSISTENCE_DEV_TOKEN. This development-only scheme provides no user
|
|
# isolation and must not be used as public-production authentication.
|
|
# NEXT_PUBLIC_PERSISTENCE=1
|
|
# NEXT_PUBLIC_PERSISTENCE_TOKEN=
|
|
|
|
# Runtime PostgreSQL connection and matching development token.
|
|
# DATABASE_URL=postgres://openmaic:password@postgres:5432/openmaic
|
|
# PERSISTENCE_DEV_TOKEN=
|
|
|
|
# The development authenticator above is refused when NODE_ENV=production
|
|
# unless this explicit opt-in is set. It provides no user isolation (the
|
|
# learner key is client-supplied), so enabling it in production is only safe
|
|
# on a trusted-network, single-user deployment.
|
|
# PERSISTENCE_ALLOW_INSECURE_DEV_AUTH=true
|
|
|
|
# The anonymous owner cookie carries the `Secure` flag in production builds.
|
|
# Safari refuses to store `Secure` cookies served over plain http://localhost
|
|
# (unlike Chromium/Firefox, it does not special-case localhost), so every
|
|
# request mints a fresh anonymous owner and owner-scoped document writes fail
|
|
# with 403. Deployments that serve plain HTTP (no TLS) opt out with the exact
|
|
# value 0 — any other spelling (false, no) leaves `Secure` on:
|
|
# COOKIE_SECURE=0
|
|
# Only do this on a trusted network or locally: without `Secure` the cookie
|
|
# travels in the clear and can be replayed by anyone on-path (see
|
|
# PERSISTENCE_ALLOW_INSECURE_DEV_AUTH above for the same class of tradeoff).
|
|
|
|
# Store asset bytes in S3 instead of PostgreSQL. Region, endpoint, and credentials
|
|
# are resolved through the standard AWS SDK environment / credential chain.
|
|
# ASSET_S3_BUCKET=
|
|
|
|
# Opt into indirect asset byte egress: answer asset byte GETs with a short-lived
|
|
# signed S3 URL (a 302, or a JSON descriptor for the packaged client) instead of
|
|
# the bytes. Unset or "direct" keeps direct egress (the safe default). Requires
|
|
# the object store's CORS to admit this app's origin and expose Content-Type, and
|
|
# the signing identity to hold s3:ListBucket on the bucket so a missing key
|
|
# answers 404 NoSuchKey rather than 403.
|
|
# ASSET_BYTE_EGRESS=redirect
|
|
|
|
# The asset collector is enabled by default when DATABASE_URL is configured.
|
|
# ASSET_COLLECTION_ENABLED=true
|
|
# ASSET_COLLECTION_INTERVAL_MS=900000
|
|
# ASSET_COLLECTION_GRACE_MS=3600000
|
|
|
|
# Root directory for the file-backed classroom store: the classroom JSON
|
|
# documents plus their generated media/audio. Defaults to <cwd>/data/classrooms.
|
|
# This moves ONLY the classrooms — the classroom generation job store is not
|
|
# configurable and stays at <cwd>/data/classroom-jobs.
|
|
# OPENMAIC_CLASSROOMS_DIR=/var/lib/openmaic/classrooms
|
|
|
|
# How long an allocated asset stays pending -- stored, but not yet named by any
|
|
# document -- before the collector expires it. A client stores bytes first and
|
|
# writes the id into the document afterwards, so this window has to outlive a
|
|
# whole generation pass plus a write-back that is waiting for its slide to be
|
|
# built; the default is one day for that reason. A value that is not a positive
|
|
# integer stops the server from starting.
|
|
# ASSET_PENDING_TTL_MS=86400000
|
|
|
|
# --- Access Control -----------------------------------------------------------
|
|
# Set a password to restrict site access. When set, users must enter this code
|
|
# before using the app. Leave empty or remove to disable access control.
|
|
# Use a long random value (at least 16 characters from a random generator):
|
|
# this code is the only secret guarding the deployment. The code is remembered
|
|
# in a signed token stored in an HTTP-only cookie for 7 days; the lifetime is
|
|
# enforced server-side, so visitors re-verify after it expires.
|
|
# ACCESS_CODE=your-secret-code
|
|
#
|
|
# Verification is rate limited only when TRUST_PROXY_HEADERS=true (see above):
|
|
# behind a trusted reverse proxy that overwrites x-forwarded-for / x-real-ip,
|
|
# each client is limited to 10 attempts per 60 seconds, and a trusted client's
|
|
# successful verification clears its own counter. Without a trusted proxy the
|
|
# app cannot attribute a request to a client, so there is no throttle; rely on
|
|
# the length and randomness of the code instead.
|