Skip to content

Commit 9c97f3d

Browse files
committed
feat(vertex): route frontend traffic through the server proxy
1 parent 2ae6001 commit 9c97f3d

21 files changed

Lines changed: 544 additions & 10 deletions

.dockerignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Never send local cloud credentials to the Docker build context.
2+
secrets/

.env.aistudio.example

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# AI Studio server-managed mode: the API key stays in the API container.
2+
GEMINI_BACKEND=aistudio
3+
GEMINI_API_KEY=
4+
GEMINI_API_BASE=https://generativelanguage.googleapis.com
5+
GOOGLE_APPLICATION_CREDENTIALS_DIR=./docker/empty-secrets
6+
GOOGLE_APPLICATION_CREDENTIALS=
7+
8+
WEB_PORT=127.0.0.1:8080
9+
RUNTIME_SERVER_MANAGED_API=true
10+
RUNTIME_USE_CUSTOM_API_CONFIG=true
11+
RUNTIME_USE_API_PROXY=true
12+
RUNTIME_API_PROXY_URL=/api/gemini
13+
RUNTIME_PYODIDE_BASE_URL=
14+
RUNTIME_BACKEND_FLAVOR=aistudio
15+
RUNTIME_ENFORCE_API_CONFIG=true

.env.byok.example

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# AI Studio BYOK mode: each browser supplies its own Gemini API key.
2+
GEMINI_BACKEND=aistudio
3+
GEMINI_API_KEY=
4+
GEMINI_API_BASE=https://generativelanguage.googleapis.com
5+
GOOGLE_APPLICATION_CREDENTIALS_DIR=./docker/empty-secrets
6+
GOOGLE_APPLICATION_CREDENTIALS=
7+
8+
WEB_PORT=127.0.0.1:8080
9+
RUNTIME_SERVER_MANAGED_API=false
10+
RUNTIME_USE_CUSTOM_API_CONFIG=true
11+
RUNTIME_USE_API_PROXY=true
12+
RUNTIME_API_PROXY_URL=/api/gemini
13+
RUNTIME_PYODIDE_BASE_URL=
14+
RUNTIME_BACKEND_FLAVOR=aistudio
15+
RUNTIME_ENFORCE_API_CONFIG=true

.env.example

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@ PORT=3001
1212
ENABLE_MCP_STDIO=false
1313
ENABLE_MCP_PRIVATE_HTTP=false
1414

15+
# Vertex AI backend. Keep aistudio for the existing API-key flow.
16+
# For Docker, put the Service Account JSON at ./secrets/sa.json, set
17+
# GOOGLE_APPLICATION_CREDENTIALS_DIR=./secrets, and use the container path below.
18+
# AI Studio/BYOK profiles should keep the default empty credentials directory.
19+
GEMINI_BACKEND=aistudio
20+
GCP_PROJECT_ID=
21+
GCP_LOCATION=us-central1
22+
GOOGLE_APPLICATION_CREDENTIALS_DIR=./docker/empty-secrets
23+
GOOGLE_APPLICATION_CREDENTIALS=
24+
25+
# Optional Gemini Files API -> GCS adapter for Vertex mode.
26+
GCS_BUCKET=
27+
GCS_OBJECT_PREFIX=amc-files/
28+
GCS_MAX_FILE_BYTES=2147483648
29+
1530
# Web container
1631
WEB_PORT=8080
1732

@@ -21,3 +36,6 @@ RUNTIME_USE_CUSTOM_API_CONFIG=true
2136
RUNTIME_USE_API_PROXY=true
2237
RUNTIME_API_PROXY_URL=/api/gemini
2338
RUNTIME_PYODIDE_BASE_URL=
39+
RUNTIME_BACKEND_FLAVOR=aistudio
40+
# Set true only for deployment profiles that must override stale browser API settings.
41+
RUNTIME_ENFORCE_API_CONFIG=false

.env.vertex.example

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Vertex AI: the API container authenticates with a Google Cloud Service Account.
2+
GEMINI_BACKEND=vertex
3+
GEMINI_API_KEY=
4+
GEMINI_API_BASE=https://generativelanguage.googleapis.com
5+
6+
GCP_PROJECT_ID=your-gcp-project-id
7+
GCP_LOCATION=global
8+
GOOGLE_APPLICATION_CREDENTIALS_DIR=./secrets
9+
GOOGLE_APPLICATION_CREDENTIALS=/run/secrets/sa.json
10+
11+
# Optional: enables Gemini Files API compatibility through GCS.
12+
GCS_BUCKET=
13+
GCS_OBJECT_PREFIX=amc-files/
14+
GCS_MAX_FILE_BYTES=2147483648
15+
16+
WEB_PORT=127.0.0.1:8080
17+
RUNTIME_SERVER_MANAGED_API=true
18+
RUNTIME_USE_CUSTOM_API_CONFIG=true
19+
RUNTIME_USE_API_PROXY=true
20+
RUNTIME_API_PROXY_URL=/api/gemini
21+
RUNTIME_PYODIDE_BASE_URL=
22+
RUNTIME_BACKEND_FLAVOR=vertex
23+
RUNTIME_ENFORCE_API_CONFIG=true

docker-compose.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,17 @@ services:
88
GEMINI_API_KEY: ${GEMINI_API_KEY:-}
99
GEMINI_API_BASE: ${GEMINI_API_BASE:-https://generativelanguage.googleapis.com}
1010
ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-}
11+
GEMINI_BACKEND: ${GEMINI_BACKEND:-aistudio}
12+
GCP_PROJECT_ID: ${GCP_PROJECT_ID:-}
13+
GCP_LOCATION: ${GCP_LOCATION:-us-central1}
14+
GOOGLE_APPLICATION_CREDENTIALS: ${GOOGLE_APPLICATION_CREDENTIALS:-}
15+
GCS_BUCKET: ${GCS_BUCKET:-}
16+
GCS_OBJECT_PREFIX: ${GCS_OBJECT_PREFIX:-amc-files/}
17+
GCS_MAX_FILE_BYTES: ${GCS_MAX_FILE_BYTES:-2147483648}
1118
ENABLE_MCP_STDIO: ${ENABLE_MCP_STDIO:-false}
1219
ENABLE_MCP_PRIVATE_HTTP: ${ENABLE_MCP_PRIVATE_HTTP:-false}
20+
volumes:
21+
- ${GOOGLE_APPLICATION_CREDENTIALS_DIR:-./docker/empty-secrets}:/run/secrets:ro
1322
expose:
1423
- '3001'
1524
restart: unless-stopped
@@ -26,6 +35,8 @@ services:
2635
RUNTIME_USE_API_PROXY: ${RUNTIME_USE_API_PROXY:-true}
2736
RUNTIME_API_PROXY_URL: ${RUNTIME_API_PROXY_URL:-/api/gemini}
2837
RUNTIME_PYODIDE_BASE_URL: ${RUNTIME_PYODIDE_BASE_URL:-}
38+
RUNTIME_BACKEND_FLAVOR: ${RUNTIME_BACKEND_FLAVOR:-aistudio}
39+
RUNTIME_ENFORCE_API_CONFIG: ${RUNTIME_ENFORCE_API_CONFIG:-false}
2940
ports:
3041
- '${WEB_PORT:-8080}:80'
3142
restart: unless-stopped

docker/empty-secrets/.gitkeep

Whitespace-only changes.

docker/web-server.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Minimal static + reverse-proxy server for the Docker web container (Dockerfile.web).
22
// Serves the web container:
33
// - serves static files from /usr/share/nginx/html with SPA fallback to index.html
4-
// - proxies /api/* to http://api:3001 (path preserved, body + response streamed)
4+
// - proxies /api/* and /health to http://api:3001 (path preserved, body + response streamed)
55
// - generates /runtime-config.js at startup from RUNTIME_* env vars
66
// - long-cache headers for /assets/*, no-store for /runtime-config.js
77
// Uses only Node built-ins (http, fs, path) so no npm install is needed in the image.
@@ -21,6 +21,7 @@ const jsonStringOrNull = (value) => {
2121
if (!trimmed) return 'null';
2222
return JSON.stringify(trimmed);
2323
};
24+
const normalizeBackendFlavor = (value) => (trim(value).toLowerCase() === 'vertex' ? 'vertex' : 'aistudio');
2425

2526
function writeRuntimeConfig() {
2627
const config = {
@@ -29,6 +30,8 @@ function writeRuntimeConfig() {
2930
useApiProxy: toBool(process.env.RUNTIME_USE_API_PROXY ?? 'true'),
3031
apiProxyUrl: JSON.parse(jsonStringOrNull(process.env.RUNTIME_API_PROXY_URL ?? '/api/gemini')),
3132
pyodideBaseUrl: JSON.parse(jsonStringOrNull(process.env.RUNTIME_PYODIDE_BASE_URL)),
33+
backendFlavor: normalizeBackendFlavor(process.env.RUNTIME_BACKEND_FLAVOR),
34+
enforceApiConfig: toBool(process.env.RUNTIME_ENFORCE_API_CONFIG),
3235
};
3336
const content = `window.__AMC_RUNTIME_CONFIG__ = ${JSON.stringify({ ...(globalThis.__AMC_RUNTIME_CONFIG__ || {}), ...config }, null, 2)};`;
3437
fs.writeFileSync(path.join(ROOT, 'runtime-config.js'), content);
@@ -146,7 +149,7 @@ function serveStatic(req, res) {
146149

147150
const server = http.createServer((req, res) => {
148151
const pathname = new URL(req.url, 'http://localhost').pathname;
149-
if (pathname === '/api' || pathname.startsWith('/api/')) {
152+
if (pathname === '/health' || pathname === '/api' || pathname.startsWith('/api/')) {
150153
return proxyApi(req, res);
151154
}
152155
return serveStatic(req, res);

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"build:analyze": "vite build --mode analyze",
1313
"build:api": "tsc -p server/tsconfig.json",
1414
"build:docker": "npm run build && npm run build:api",
15+
"backend:switch": "bash scripts/switch-backend.sh",
1516
"preview": "vite preview",
1617
"clean": "rm -rf dist server/dist coverage playwright-report test-results tmp-live-artifact-demo .playwright-visible-demo-profile .codex-dev-*",
1718
"lint": "eslint . --ext .ts,.tsx,.js,.mjs --max-warnings=0",

public/runtime-config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@ window.__AMC_RUNTIME_CONFIG__ = {
44
useApiProxy: false,
55
apiProxyUrl: '/api/gemini',
66
pyodideBaseUrl: null,
7+
backendFlavor: 'aistudio',
8+
enforceApiConfig: false,
79
...(window.__AMC_RUNTIME_CONFIG__ || {}),
810
};

0 commit comments

Comments
 (0)