@agent-foundry/replay-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,628 @@
1
+ # AgentFoundry Replay Renderer
2
+
3
+ Video rendering service for AgentFoundry mini-game replays using Puppeteer and FFmpeg.
4
+
5
+ ## Architecture
6
+
7
+ This renderer uses a **Puppeteer-based approach** where:
8
+
9
+ 1. The actual game is loaded in a headless browser
10
+ 2. A replay manifest is injected into the game
11
+ 3. The game enters "replay mode" and plays back the events
12
+ 4. Screenshots are captured at each frame
13
+ 5. FFmpeg combines screenshots into video
14
+
15
+ This approach ensures the video looks **exactly like the gameplay** with zero code duplication.
16
+
17
+ ### Static Bundle Hosting
18
+
19
+ The replay-server can host game bundles internally, eliminating the need for a separate game server:
20
+
21
+ ```
22
+ ┌──────────────────────────────────────────────────────────────┐
23
+ │ replay-server │
24
+ │ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │
25
+ │ │ Bundle Host │ │ Puppeteer │ │ HTTP API │ │
26
+ │ │ /bundles/* │◄──│ Renderer │◄──│ POST /render │ │
27
+ │ └─────────────┘ └──────────────┘ └─────────────────┘ │
28
+ │ │ ▲ │
29
+ │ ▼ │ │
30
+ │ bundles/game-life-restart/ manifest.json │
31
+ └──────────────────────────────────────────────────────────────┘
32
+ ```
33
+
34
+ With `bundleId` in the manifest, the server:
35
+ 1. Serves the game from `/bundles/<bundleId>/`
36
+ 2. Puppeteer loads the game from the internal static server
37
+ 3. No external game server required
38
+
39
+ ## Prerequisites
40
+
41
+ - Node.js 18+
42
+ - pnpm 9.1.0+ (this is a pnpm monorepo)
43
+ - FFmpeg installed and in PATH
44
+ - **Option A**: Game running at `http://localhost:5173` (development mode)
45
+ - **Option B**: Game bundle in `bundles/` directory (production mode)
46
+
47
+ ### Installing pnpm
48
+
49
+ ```bash
50
+ npm install -g pnpm@9.1.0
51
+ # or
52
+ corepack enable
53
+ corepack prepare pnpm@9.1.0 --activate
54
+ ```
55
+
56
+ ### Installing FFmpeg
57
+
58
+ **macOS:**
59
+ ```bash
60
+ brew install ffmpeg
61
+ ```
62
+
63
+ **Windows:**
64
+ ```bash
65
+ choco install ffmpeg
66
+ # or download from https://ffmpeg.org/download.html
67
+ ```
68
+
69
+ **Linux:**
70
+ ```bash
71
+ sudo apt install ffmpeg
72
+ ```
73
+
74
+ ## Usage
75
+
76
+ ### Setup
77
+
78
+ ```bash
79
+ # From project root - install all dependencies
80
+ pnpm install
81
+
82
+ # Or install only this package and its dependencies
83
+ cd packages/replay-server
84
+ pnpm install
85
+ ```
86
+
87
+ ### CLI Tool
88
+
89
+ ```bash
90
+ # From project root (recommended)
91
+ pnpm --filter replay-server render -- --manifest ./packages/replay-server/samples/replay.json --output ./packages/replay-server/output/output.mp4
92
+
93
+ # From package directory
94
+ cd packages/replay-server
95
+ pnpm run render -- --manifest ./samples/replay.json --output ./output/output.mp4
96
+
97
+ # Alternative: Use tsx directly
98
+ cd packages/replay-server
99
+ pnpm exec tsx src/cli/render.ts --manifest ./samples/replay.json --output ./output/output.mp4
100
+
101
+ # Render with custom settings
102
+ pnpm --filter replay-server render -- \
103
+ --manifest ./replay.json \
104
+ --output ./output.mp4 \
105
+ --game-url http://localhost:5173 \
106
+ --width 1080 \
107
+ --height 1920 \
108
+ --fps 30 \
109
+ --seconds-per-age 2
110
+ ```
111
+
112
+ ### HTTP Server
113
+
114
+ ```bash
115
+ # From project root (recommended)
116
+ pnpm dev:replay-server
117
+
118
+ # Or from package directory
119
+ cd packages/replay-server
120
+ pnpm run dev
121
+
122
+ # Production build and start
123
+ # From project root
124
+ pnpm build:replay-server
125
+ cd packages/replay-server
126
+ pnpm start
127
+
128
+ # Or from package directory
129
+ cd packages/replay-server
130
+ pnpm run build
131
+ pnpm start
132
+ ```
133
+
134
+ #### API Endpoints
135
+
136
+ **POST /render** - Start a render job
137
+
138
+ With bundleId (recommended - no external game server needed):
139
+ ```bash
140
+ curl -X POST http://localhost:3001/render \
141
+ -H "Content-Type: application/json" \
142
+ -d '{
143
+ "manifest": {
144
+ "schema": "lifeRestart.replay.v1",
145
+ "bundleId": "game-life-restart",
146
+ "gameId": "test-game",
147
+ "timeline": [...],
148
+ "highlights": []
149
+ },
150
+ "config": {
151
+ "width": 1080,
152
+ "height": 1920
153
+ }
154
+ }'
155
+ ```
156
+
157
+ With external gameUrl (legacy mode):
158
+ ```bash
159
+ curl -X POST http://localhost:3001/render \
160
+ -H "Content-Type: application/json" \
161
+ -d '{
162
+ "manifest": { ... },
163
+ "config": {
164
+ "gameUrl": "http://localhost:5173",
165
+ "width": 1080,
166
+ "height": 1920
167
+ }
168
+ }'
169
+ ```
170
+
171
+ Response:
172
+ ```json
173
+ {
174
+ "jobId": "5eaee18f-48a7-4193-b594-86756ac76ce2",
175
+ "status": "pending",
176
+ "message": "Render job started"
177
+ }
178
+ ```
179
+
180
+ **GET /bundles** - List available game bundles
181
+ ```bash
182
+ curl http://localhost:3001/bundles
183
+ ```
184
+
185
+ Response:
186
+ ```json
187
+ {
188
+ "bundles": [
189
+ {
190
+ "bundleId": "game-life-restart",
191
+ "path": "/app/packages/replay-server/bundles/game-life-restart",
192
+ "url": "http://localhost:3001/bundles/game-life-restart/",
193
+ "ready": true
194
+ }
195
+ ],
196
+ "count": 1
197
+ }
198
+ ```
199
+
200
+ **GET /jobs** - List all jobs
201
+ ```bash
202
+ curl http://localhost:3001/jobs
203
+ ```
204
+
205
+ Response:
206
+ ```json
207
+ {
208
+ "jobs": [
209
+ {
210
+ "jobId": "5eaee18f-48a7-4193-b594-86756ac76ce2",
211
+ "status": "completed",
212
+ "progress": { ... },
213
+ "error": null,
214
+ "createdAt": "2024-01-01T00:00:00.000Z",
215
+ "completedAt": "2024-01-01T00:05:00.000Z"
216
+ }
217
+ ],
218
+ "count": 1
219
+ }
220
+ ```
221
+
222
+ **GET /status/:jobId** - Check job status
223
+ ```bash
224
+ curl http://localhost:3001/status/5eaee18f-48a7-4193-b594-86756ac76ce2
225
+ ```
226
+
227
+ **GET /download/:jobId** - Download completed video
228
+ ```bash
229
+ curl -o video.mp4 http://localhost:3001/download/5eaee18f-48a7-4193-b594-86756ac76ce2
230
+ ```
231
+
232
+ ## Game Integration
233
+
234
+ For the renderer to work, the game must support replay mode:
235
+
236
+ ### 1. Listen for replay manifest
237
+
238
+ ```typescript
239
+ // In your game's main component
240
+ useEffect(() => {
241
+ // Check for manifest from URL or window
242
+ const urlParams = new URLSearchParams(window.location.search);
243
+ if (urlParams.get('mode') === 'replay') {
244
+ // Wait for manifest injection
245
+ const handleManifest = (e: CustomEvent) => {
246
+ const manifest = e.detail;
247
+ enterReplayMode(manifest);
248
+ };
249
+
250
+ window.addEventListener('replay-manifest-loaded', handleManifest);
251
+
252
+ // Also check if already injected
253
+ if ((window as any).__REPLAY_MANIFEST__) {
254
+ enterReplayMode((window as any).__REPLAY_MANIFEST__);
255
+ }
256
+ }
257
+ }, []);
258
+ ```
259
+
260
+ ### 2. Handle replay-next-age event
261
+
262
+ ```typescript
263
+ window.addEventListener('replay-next-age', () => {
264
+ // Advance to next age in replay
265
+ replayNextAge();
266
+ });
267
+ ```
268
+
269
+ ### 3. Signal when ready
270
+
271
+ ```tsx
272
+ // Add data attribute when ready for capture
273
+ <div data-replay-ready="true">
274
+ {/* game content */}
275
+ </div>
276
+ ```
277
+
278
+ ## Configuration
279
+
280
+ Environment variables:
281
+
282
+ | Variable | Default | Description |
283
+ |----------|---------|-------------|
284
+ | `PORT` | 3001 | Server port |
285
+ | `GAME_URL` | http://localhost:5173 | Fallback URL when no bundleId specified |
286
+ | `BUNDLES_DIR` | ./bundles | Directory containing game bundles |
287
+ | `OUTPUT_DIR` | ./output | Directory for rendered videos |
288
+
289
+ ### Game URL Resolution Order
290
+
291
+ When rendering a video, the game URL is resolved in this order:
292
+ 1. `config.gameUrl` (explicit URL in render request)
293
+ 2. `manifest.bundleId` (serves from `/bundles/<bundleId>/`)
294
+ 3. `GAME_URL` environment variable (fallback)
295
+
296
+ ## Output
297
+
298
+ Videos are rendered in:
299
+ - Format: MP4 (H.264)
300
+ - Resolution: 720x1080 (portrait, for mobile/Feed)
301
+ - FPS: 16 (configurable)
302
+ - Duration: ~1.2s per age (configurable)
303
+
304
+ ## Building Game Bundles
305
+
306
+ Before deploying, you need to build game bundles that will be hosted by the replay-server.
307
+
308
+ ### Option A: Using the build script
309
+
310
+ ```bash
311
+ # Build a specific game
312
+ cd packages/replay-server
313
+ ./scripts/build-bundle.sh game-life-restart
314
+
315
+ # This will:
316
+ # 1. Build the game from repo/game-life-restart
317
+ # 2. Copy dist/ to bundles/game-life-restart/
318
+ ```
319
+
320
+ ### Option B: Manual build
321
+
322
+ ```bash
323
+ # Build the game
324
+ cd repo/game-life-restart
325
+ pnpm install
326
+ pnpm build
327
+
328
+ # Copy to bundles
329
+ mkdir -p packages/replay-server/bundles/game-life-restart
330
+ cp -r dist/* packages/replay-server/bundles/game-life-restart/
331
+ ```
332
+
333
+ ### Option C: Docker (automatic)
334
+
335
+ The Dockerfile includes a multi-stage build that automatically builds and embeds the game bundle. No manual steps required.
336
+
337
+ ## Deployment Options
338
+
339
+ ### Local Development
340
+
341
+ ```bash
342
+ # From project root (recommended)
343
+ pnpm dev:replay-server
344
+
345
+ # Or from package directory
346
+ cd packages/replay-server
347
+ pnpm run dev
348
+ ```
349
+
350
+ ### Docker with Embedded Bundle (Recommended)
351
+
352
+ The Dockerfile uses multi-stage builds to:
353
+ 1. Build the game-life-restart bundle
354
+ 2. Build the replay-server
355
+ 3. Create a runtime image with both embedded
356
+
357
+ ```bash
358
+ # Build from project root
359
+ docker build -f packages/replay-server/Dockerfile -t replay-server:latest .
360
+
361
+ # Or use docker-compose for local testing
362
+ cd packages/replay-server
363
+ docker-compose -f docker-compose.local.yml up --build
364
+ ```
365
+
366
+ ### Serverless (Aliyun FC)
367
+
368
+ Build and deploy to Aliyun Function Compute:
369
+
370
+ #### 1. Login to Aliyun Container Registry (ACR)
371
+
372
+ ```bash
373
+ # Login using Aliyun CLI (recommended)
374
+ aliyun cr GetAuthorizationToken --region cn-beijing --InstanceId xxx
375
+ docker login --username=<your-username> --password=<token> registry.cn-beijing.aliyuncs.com
376
+
377
+ # Or use temporary password from Aliyun console
378
+ docker login registry.cn-beijing.aliyuncs.com
379
+ ```
380
+
381
+ #### 2. Build Docker Image
382
+
383
+ ```bash
384
+ # Build from project root (must be from root, as it needs access to multiple packages in monorepo)
385
+ cd /path/to/agent-foundry
386
+ docker build -f packages/replay-server/Dockerfile -t replay-server:latest .
387
+
388
+ # Note:
389
+ # - Dockerfile uses multi-stage builds
390
+ # - Stage 1: Builds game-life-restart bundle
391
+ # - Stage 2: Builds replay-server
392
+ # - Stage 3: Creates runtime with embedded bundle
393
+ # - Includes Chinese fonts (Noto CJK, WQY Zenhei) for proper text rendering
394
+ ```
395
+
396
+ #### 3. Local Docker Testing
397
+
398
+ Before pushing to ACR, test the Docker image locally:
399
+
400
+ ##### 3.1 Using Docker Compose (Recommended)
401
+
402
+ ```bash
403
+ cd packages/replay-server
404
+ docker-compose -f docker-compose.local.yml up --build
405
+
406
+ # In another terminal:
407
+ curl http://localhost:3001/health
408
+ curl http://localhost:3001/bundles
409
+ ```
410
+
411
+ ##### 3.2 Using Docker Run
412
+
413
+ ```bash
414
+ # Run container with embedded bundle
415
+ docker run -d \
416
+ --name replay-server-test \
417
+ -p 3001:3001 \
418
+ --shm-size=2g \
419
+ -e PORT=3001 \
420
+ -v $(pwd)/packages/replay-server/output:/app/packages/replay-server/output \
421
+ replay-server:latest
422
+
423
+ # View container logs
424
+ docker logs -f replay-server-test
425
+ ```
426
+
427
+ ##### 3.3 Test Health Check
428
+
429
+ ```bash
430
+ # Check if the service started normally
431
+ curl http://localhost:3001/health
432
+
433
+ # Expected response:
434
+ # {
435
+ # "status": "ok",
436
+ # "gameUrl": "http://localhost:5173",
437
+ # "bundlesDir": "/app/packages/replay-server/bundles",
438
+ # "bundleCount": 1
439
+ # }
440
+
441
+ # List available bundles
442
+ curl http://localhost:3001/bundles
443
+ ```
444
+
445
+ ##### 3.4 Test Render API with bundleId
446
+
447
+ ```bash
448
+ # Submit a render task using embedded bundle (no external game server needed!)
449
+ curl -X POST http://localhost:3001/render \
450
+ -H "Content-Type: application/json" \
451
+ -d '{
452
+ "manifest": {
453
+ "schema": "lifeRestart.replay.v1",
454
+ "bundleId": "game-life-restart",
455
+ "gameId": "test-game",
456
+ "timeline": [],
457
+ "highlights": []
458
+ },
459
+ "config": {
460
+ "width": 1080,
461
+ "height": 1920,
462
+ "fps": 30
463
+ }
464
+ }'
465
+
466
+ # Or use local manifest file
467
+ curl -X POST http://localhost:3001/render \
468
+ -H "Content-Type: application/json" \
469
+ -d "{\"manifest\": $(cat packages/replay-server/samples/life-replay-lr-mkcdfzc2-u8cqbs.json)}"
470
+
471
+ # View task list
472
+ curl http://localhost:3001/jobs
473
+
474
+ # Check specific task status (replace <job-id> with actual task ID)
475
+ curl http://localhost:3001/status/<job-id>
476
+
477
+ # Download completed video (replace <job-id> with actual task ID)
478
+ curl -o test-video.mp4 http://localhost:3001/download/<job-id>
479
+ ```
480
+
481
+ ##### 3.5 View Output Files
482
+
483
+ ```bash
484
+ # View generated video files locally
485
+ ls -lh packages/replay-server/output/
486
+
487
+ # Or enter container to view
488
+ docker exec -it replay-server-test ls -lh /app/packages/replay-server/output/
489
+ ```
490
+
491
+ ##### 3.6 Stop and Cleanup
492
+
493
+ ```bash
494
+ # Stop container
495
+ docker stop replay-server-test
496
+
497
+ # Remove container
498
+ docker rm replay-server-test
499
+
500
+ # Or stop and remove in one command
501
+ docker rm -f replay-server-test
502
+
503
+ # Remove image (optional)
504
+ docker rmi replay-server:latest
505
+ ```
506
+
507
+ ##### 3.7 Debugging Tips
508
+
509
+ ```bash
510
+ # Enter container for debugging
511
+ docker exec -it replay-server-test /bin/bash
512
+
513
+ # Check processes inside container
514
+ docker exec replay-server-test ps aux
515
+
516
+ # Check environment variables
517
+ docker exec replay-server-test env
518
+
519
+ # View container resource usage
520
+ docker stats replay-server-test
521
+ ```
522
+
523
+ #### 4. Deploy to ACR
524
+
525
+ Use the deploy script for easy deployment:
526
+
527
+ ```bash
528
+ # Set your ACR namespace
529
+ export ACR_NAMESPACE="your-namespace"
530
+
531
+ # Deploy (builds and pushes to ACR)
532
+ cd packages/replay-server
533
+ ./scripts/deploy-aliyun.sh
534
+
535
+ # Or with a custom version tag
536
+ ./scripts/deploy-aliyun.sh v1.0.0
537
+ ```
538
+
539
+ Or manually:
540
+
541
+ ```bash
542
+ # Replace <your-namespace> with your namespace
543
+ export ACR_REGISTRY="registry.cn-beijing.aliyuncs.com"
544
+ export ACR_NAMESPACE="<your-namespace>"
545
+ export IMAGE_NAME="replay-server"
546
+ export IMAGE_TAG="latest"
547
+
548
+ # Tag image
549
+ docker tag replay-server:latest ${ACR_REGISTRY}/${ACR_NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG}
550
+
551
+ # Push image
552
+ docker push ${ACR_REGISTRY}/${ACR_NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG}
553
+ ```
554
+
555
+ #### 5. Configure in Aliyun Function Compute
556
+
557
+ ** Rebuild to be compatible with Aliyun FC **
558
+ ```bash
559
+ # make sure you are at root dir
560
+ # build disabling attestation/provennace
561
+ docker buildx build \
562
+ --platform linux/amd64 \
563
+ --provenance=false \
564
+ --sbom=false \
565
+ -t ${ACR_REGISTRY}/${ACR_NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG} \
566
+ -f packages/replay-server/Dockerfile \
567
+ --push \
568
+ .
569
+
570
+ # imagetools inspectation
571
+ docker buildx imagetools inspect ${ACR_REGISTRY}/${ACR_NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG}
572
+ ```
573
+
574
+ **Environment Variables:**
575
+ - `PORT`: Server port (default: 9000, FC will set this automatically)
576
+ - `BUNDLES_DIR`: `/app/packages/replay-server/bundles` (already set in Dockerfile)
577
+ - `OUTPUT_DIR`: `/tmp/output` (use /tmp for FC writable directory)
578
+
579
+ **Function Configuration Requirements:**
580
+ - **Memory**: **Minimum 2048 MB (2GB)**, recommended 4GB
581
+ - Chromium requires significant memory to launch and render
582
+ - Less than 2GB may cause browser crashes or timeouts
583
+ - **Timeout**: **Minimum 300 seconds (5 minutes)**, recommended 600 seconds (10 minutes)
584
+ - Cold start + browser launch can take 30-60 seconds
585
+ - Video rendering time depends on replay length
586
+ - **Instance Concurrency**: **1** (required)
587
+ - Prevents resource contention between concurrent renders
588
+ - Each instance needs dedicated CPU/memory for Chromium
589
+ - **CPU**: Auto-scaled with memory (FC default is fine)
590
+
591
+ **Important Notes for FC Deployment:**
592
+ - The image includes all Chromium dependencies pre-installed
593
+ - Browser launch parameters are optimized for containerized environments
594
+ - Cold starts may take longer on first invocation (expect 30-60s for browser initialization)
595
+
596
+ #### 6. Verify Deployment
597
+
598
+ ```bash
599
+ # Test if function started normally
600
+ curl https://<your-function-url>/health
601
+
602
+ # Check available bundles
603
+ curl https://<your-function-url>/bundles
604
+
605
+ # **NEW: Debug Chromium installation**
606
+ curl https://<your-function-url>/debug/chrome
607
+ # This endpoint verifies Chromium is properly installed and all dependencies are available
608
+ # Expected response:
609
+ # {
610
+ # "executablePath": "/usr/bin/chromium",
611
+ # "tests": {
612
+ # "version": { "ok": true, "output": "Chromium 120.0.6099.224" },
613
+ # "dependencies": { "ok": true, "summary": "All libraries found" },
614
+ # "chineseFonts": { "ok": true, "count": 15, "sample": ["Noto Sans CJK...", "..."] }
615
+ # }
616
+ # }
617
+
618
+ # Submit a render job
619
+ curl -X POST https://<your-function-url>/render \
620
+ -H "Content-Type: application/json" \
621
+ -d '{
622
+ "manifest": {
623
+ "schema": "lifeRestart.replay.v1",
624
+ "bundleId": "game-life-restart",
625
+ ...
626
+ }
627
+ }'
628
+ ```
@@ -0,0 +1,2 @@
1
+ # This directory contains pre-built game bundles for replay rendering.
2
+ # Bundle contents are git-ignored; only the directory structure is tracked.
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI tool for offline video rendering
4
+ *
5
+ * Usage:
6
+ * npx tsx src/cli/render.ts --manifest ./replay.json --output ./output.mp4
7
+ * npx tsx src/cli/render.ts --manifest-url https://... --game-url http://localhost:5173
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/cli/render.ts"],"names":[],"mappings":";AACA;;;;;;GAMG"}