@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/.cursor/project.mdc +694 -0
- package/.dockerignore +11 -0
- package/Dockerfile +156 -0
- package/README.md +628 -0
- package/bundles/.gitkeep +2 -0
- package/dist/cli/render.d.ts +10 -0
- package/dist/cli/render.d.ts.map +1 -0
- package/dist/cli/render.js +206 -0
- package/dist/cli/render.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/renderer/PuppeteerRenderer.d.ts +72 -0
- package/dist/renderer/PuppeteerRenderer.d.ts.map +1 -0
- package/dist/renderer/PuppeteerRenderer.js +392 -0
- package/dist/renderer/PuppeteerRenderer.js.map +1 -0
- package/dist/renderer/index.d.ts +2 -0
- package/dist/renderer/index.d.ts.map +1 -0
- package/dist/renderer/index.js +2 -0
- package/dist/renderer/index.js.map +1 -0
- package/dist/server/index.d.ts +14 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +348 -0
- package/dist/server/index.js.map +1 -0
- package/docker-compose.local.yml +29 -0
- package/package.json +35 -0
- package/samples/life-replay-lr-mkcdfzc2-u8cqbs.json +1770 -0
- package/scripts/build-bundle.sh +52 -0
- package/scripts/deploy-aliyun.sh +81 -0
- package/src/cli/render.ts +243 -0
- package/src/index.ts +2 -0
- package/src/renderer/PuppeteerRenderer.ts +512 -0
- package/src/renderer/index.ts +1 -0
- package/src/server/index.ts +407 -0
- package/tsconfig.json +35 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replay Render Server
|
|
3
|
+
*
|
|
4
|
+
* HTTP API for rendering replays to video
|
|
5
|
+
*
|
|
6
|
+
* Endpoints:
|
|
7
|
+
* - POST /render - Start a render job
|
|
8
|
+
* - GET /jobs - List all jobs
|
|
9
|
+
* - GET /status/:jobId - Get job status
|
|
10
|
+
* - GET /download/:jobId - Download completed video
|
|
11
|
+
* - GET /bundles - List available game bundles
|
|
12
|
+
*/
|
|
13
|
+
import express from 'express';
|
|
14
|
+
import cors from 'cors';
|
|
15
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
16
|
+
import * as fs from 'fs';
|
|
17
|
+
import * as path from 'path';
|
|
18
|
+
import { fileURLToPath } from 'url';
|
|
19
|
+
import { dirname } from 'path';
|
|
20
|
+
import { execSync } from 'child_process';
|
|
21
|
+
import { PuppeteerRenderer } from '../renderer/PuppeteerRenderer.js';
|
|
22
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const app = express();
|
|
24
|
+
app.use(cors());
|
|
25
|
+
app.use(express.json({ limit: '10mb' }));
|
|
26
|
+
// Configuration
|
|
27
|
+
const PORT = process.env.PORT || 3001;
|
|
28
|
+
const GAME_URL = process.env.GAME_URL || 'http://localhost:5173';
|
|
29
|
+
const OUTPUT_DIR = process.env.OUTPUT_DIR || path.join(process.cwd(), 'output');
|
|
30
|
+
const BUNDLES_DIR = process.env.BUNDLES_DIR || path.join(__dirname, '../../bundles');
|
|
31
|
+
/**
|
|
32
|
+
* GET /bundles
|
|
33
|
+
* List available game bundles
|
|
34
|
+
* This route must be registered BEFORE the static middleware to handle /bundles requests
|
|
35
|
+
*/
|
|
36
|
+
app.get('/bundles', (req, res) => {
|
|
37
|
+
console.log('[GET /bundles] Request received');
|
|
38
|
+
try {
|
|
39
|
+
// Ensure bundles directory exists
|
|
40
|
+
if (!fs.existsSync(BUNDLES_DIR)) {
|
|
41
|
+
return res.json({ bundles: [], count: 0 });
|
|
42
|
+
}
|
|
43
|
+
// List directories in bundles folder
|
|
44
|
+
const entries = fs.readdirSync(BUNDLES_DIR, { withFileTypes: true });
|
|
45
|
+
const bundles = entries
|
|
46
|
+
.filter(entry => entry.isDirectory())
|
|
47
|
+
.map(entry => {
|
|
48
|
+
const bundlePath = path.join(BUNDLES_DIR, entry.name);
|
|
49
|
+
const indexPath = path.join(bundlePath, 'index.html');
|
|
50
|
+
const hasIndex = fs.existsSync(indexPath);
|
|
51
|
+
return {
|
|
52
|
+
bundleId: entry.name,
|
|
53
|
+
path: bundlePath,
|
|
54
|
+
url: `http://localhost:${PORT}/bundles/${entry.name}/`,
|
|
55
|
+
ready: hasIndex,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
console.log('[GET /bundles] Found bundles:', bundles.length);
|
|
59
|
+
res.json({
|
|
60
|
+
bundles,
|
|
61
|
+
count: bundles.length,
|
|
62
|
+
bundlesDir: BUNDLES_DIR,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
console.error('[GET /bundles] Error listing bundles:', error);
|
|
67
|
+
res.status(500).json({ error: 'Failed to list bundles' });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
// Serve static game bundles (AFTER API route to allow /bundles/<bundleId>/ paths)
|
|
71
|
+
app.use('/bundles', express.static(BUNDLES_DIR, {
|
|
72
|
+
index: 'index.html',
|
|
73
|
+
extensions: ['html']
|
|
74
|
+
}));
|
|
75
|
+
/**
|
|
76
|
+
* GET /debug/chrome
|
|
77
|
+
* Debug endpoint to verify Chromium installation
|
|
78
|
+
*/
|
|
79
|
+
app.get('/debug/chrome', async (req, res) => {
|
|
80
|
+
const results = {
|
|
81
|
+
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || '/usr/bin/chromium',
|
|
82
|
+
tests: {}
|
|
83
|
+
};
|
|
84
|
+
try {
|
|
85
|
+
// Test 1: Check if chromium exists
|
|
86
|
+
const version = execSync(`${results.executablePath} --version`, {
|
|
87
|
+
encoding: 'utf8',
|
|
88
|
+
timeout: 5000
|
|
89
|
+
});
|
|
90
|
+
results.tests.version = { ok: true, output: version.trim() };
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
results.tests.version = {
|
|
94
|
+
ok: false,
|
|
95
|
+
error: e.message,
|
|
96
|
+
stderr: e.stderr?.toString()
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
// Test 2: Check library dependencies
|
|
101
|
+
const ldd = execSync(`ldd ${results.executablePath}`, {
|
|
102
|
+
encoding: 'utf8',
|
|
103
|
+
timeout: 5000
|
|
104
|
+
});
|
|
105
|
+
results.tests.dependencies = { ok: true, summary: 'All libraries found' };
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
results.tests.dependencies = {
|
|
109
|
+
ok: false,
|
|
110
|
+
error: e.message,
|
|
111
|
+
stderr: e.stderr?.toString()
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
// Test 3: Check if Chinese fonts are available
|
|
116
|
+
const fonts = execSync(`fc-list :lang=zh-cn`, {
|
|
117
|
+
encoding: 'utf8',
|
|
118
|
+
timeout: 5000
|
|
119
|
+
});
|
|
120
|
+
const fontLines = fonts.split('\n').filter(l => l.trim());
|
|
121
|
+
results.tests.chineseFonts = {
|
|
122
|
+
ok: fontLines.length > 0,
|
|
123
|
+
count: fontLines.length,
|
|
124
|
+
sample: fontLines.slice(0, 3)
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
results.tests.chineseFonts = {
|
|
129
|
+
ok: false,
|
|
130
|
+
error: 'fontconfig not available or no Chinese fonts found'
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
res.json(results);
|
|
134
|
+
});
|
|
135
|
+
/**
|
|
136
|
+
* Resolve game URL from manifest bundleId or config
|
|
137
|
+
*/
|
|
138
|
+
function resolveGameUrl(manifest, configGameUrl) {
|
|
139
|
+
// 1. Explicit gameUrl in config takes precedence
|
|
140
|
+
if (configGameUrl) {
|
|
141
|
+
return configGameUrl;
|
|
142
|
+
}
|
|
143
|
+
// 2. Use bundleId from manifest to serve from local bundles
|
|
144
|
+
if (manifest.bundleId) {
|
|
145
|
+
const bundlePath = path.join(BUNDLES_DIR, manifest.bundleId);
|
|
146
|
+
if (!fs.existsSync(bundlePath)) {
|
|
147
|
+
throw new Error(`Bundle not found: ${manifest.bundleId}`);
|
|
148
|
+
}
|
|
149
|
+
return `http://localhost:${PORT}/bundles/${manifest.bundleId}/`;
|
|
150
|
+
}
|
|
151
|
+
// 3. Fall back to default GAME_URL
|
|
152
|
+
return GAME_URL;
|
|
153
|
+
}
|
|
154
|
+
const jobs = new Map();
|
|
155
|
+
// Ensure output directory exists
|
|
156
|
+
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
157
|
+
/**
|
|
158
|
+
* POST /render
|
|
159
|
+
* Start a new render job
|
|
160
|
+
*/
|
|
161
|
+
app.post('/render', async (req, res) => {
|
|
162
|
+
console.log('[POST /render] Request received');
|
|
163
|
+
try {
|
|
164
|
+
const { manifest, manifestUrl, config } = req.body;
|
|
165
|
+
// Get manifest from body or URL
|
|
166
|
+
let replayManifest;
|
|
167
|
+
if (manifest) {
|
|
168
|
+
replayManifest = manifest;
|
|
169
|
+
}
|
|
170
|
+
else if (manifestUrl) {
|
|
171
|
+
const response = await fetch(manifestUrl);
|
|
172
|
+
if (!response.ok) {
|
|
173
|
+
console.error('[POST /render] Failed to fetch manifest from URL:', manifestUrl);
|
|
174
|
+
return res.status(400).json({ error: 'Failed to fetch manifest from URL' });
|
|
175
|
+
}
|
|
176
|
+
replayManifest = await response.json();
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
console.error('[POST /render] Missing required parameter: manifest or manifestUrl');
|
|
180
|
+
return res.status(400).json({ error: 'Either manifest or manifestUrl is required' });
|
|
181
|
+
}
|
|
182
|
+
// Validate manifest
|
|
183
|
+
if (replayManifest.schema !== 'lifeRestart.replay.v1') {
|
|
184
|
+
console.error('[POST /render] Invalid manifest schema:', replayManifest.schema);
|
|
185
|
+
return res.status(400).json({ error: 'Invalid manifest schema' });
|
|
186
|
+
}
|
|
187
|
+
// Create job
|
|
188
|
+
const jobId = uuidv4();
|
|
189
|
+
const outputPath = path.join(OUTPUT_DIR, `${jobId}.mp4`);
|
|
190
|
+
const job = {
|
|
191
|
+
id: jobId,
|
|
192
|
+
status: 'pending',
|
|
193
|
+
progress: null,
|
|
194
|
+
outputPath: null,
|
|
195
|
+
error: null,
|
|
196
|
+
createdAt: new Date(),
|
|
197
|
+
completedAt: null,
|
|
198
|
+
};
|
|
199
|
+
jobs.set(jobId, job);
|
|
200
|
+
// Resolve game URL from bundleId or config
|
|
201
|
+
let gameUrl;
|
|
202
|
+
try {
|
|
203
|
+
gameUrl = resolveGameUrl(replayManifest, config?.gameUrl);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
207
|
+
console.error('[POST /render] Failed to resolve game URL:', errorMessage);
|
|
208
|
+
return res.status(400).json({ error: errorMessage });
|
|
209
|
+
}
|
|
210
|
+
// Start rendering in background
|
|
211
|
+
processJob(job, replayManifest, {
|
|
212
|
+
gameUrl,
|
|
213
|
+
outputPath,
|
|
214
|
+
width: config?.width,
|
|
215
|
+
height: config?.height,
|
|
216
|
+
fps: config?.fps,
|
|
217
|
+
secondsPerAge: config?.secondsPerAge,
|
|
218
|
+
});
|
|
219
|
+
console.log('[POST /render] Render job started successfully, jobId:', jobId);
|
|
220
|
+
console.log('[POST /render] Using game URL:', gameUrl);
|
|
221
|
+
res.json({
|
|
222
|
+
jobId,
|
|
223
|
+
status: job.status,
|
|
224
|
+
message: 'Render job started',
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
console.error('[POST /render] Error starting render job:', error);
|
|
229
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
/**
|
|
233
|
+
* GET /jobs
|
|
234
|
+
* List all jobs
|
|
235
|
+
*/
|
|
236
|
+
app.get('/jobs', (req, res) => {
|
|
237
|
+
console.log('[GET /jobs] Request received');
|
|
238
|
+
try {
|
|
239
|
+
const jobList = Array.from(jobs.values()).map(job => ({
|
|
240
|
+
jobId: job.id,
|
|
241
|
+
status: job.status,
|
|
242
|
+
progress: job.progress,
|
|
243
|
+
error: job.error,
|
|
244
|
+
createdAt: job.createdAt,
|
|
245
|
+
completedAt: job.completedAt,
|
|
246
|
+
}));
|
|
247
|
+
console.log('[GET /jobs] Returning job list, count:', jobList.length);
|
|
248
|
+
res.json({
|
|
249
|
+
jobs: jobList,
|
|
250
|
+
count: jobList.length,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
console.error('[GET /jobs] Error listing jobs:', error);
|
|
255
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
/**
|
|
259
|
+
* GET /status/:jobId
|
|
260
|
+
* Get job status
|
|
261
|
+
*/
|
|
262
|
+
app.get('/status/:jobId', (req, res) => {
|
|
263
|
+
const { jobId } = req.params;
|
|
264
|
+
const job = jobs.get(jobId);
|
|
265
|
+
if (!job) {
|
|
266
|
+
return res.status(404).json({ error: 'Job not found' });
|
|
267
|
+
}
|
|
268
|
+
res.json({
|
|
269
|
+
jobId: job.id,
|
|
270
|
+
status: job.status,
|
|
271
|
+
progress: job.progress,
|
|
272
|
+
error: job.error,
|
|
273
|
+
createdAt: job.createdAt,
|
|
274
|
+
completedAt: job.completedAt,
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
/**
|
|
278
|
+
* GET /download/:jobId
|
|
279
|
+
* Download completed video
|
|
280
|
+
*/
|
|
281
|
+
app.get('/download/:jobId', (req, res) => {
|
|
282
|
+
const { jobId } = req.params;
|
|
283
|
+
const job = jobs.get(jobId);
|
|
284
|
+
if (!job) {
|
|
285
|
+
return res.status(404).json({ error: 'Job not found' });
|
|
286
|
+
}
|
|
287
|
+
if (job.status !== 'completed' || !job.outputPath) {
|
|
288
|
+
return res.status(400).json({ error: 'Job not completed' });
|
|
289
|
+
}
|
|
290
|
+
if (!fs.existsSync(job.outputPath)) {
|
|
291
|
+
return res.status(404).json({ error: 'Video file not found' });
|
|
292
|
+
}
|
|
293
|
+
res.download(job.outputPath, `replay-${jobId}.mp4`);
|
|
294
|
+
});
|
|
295
|
+
/**
|
|
296
|
+
* GET /health
|
|
297
|
+
* Health check endpoint
|
|
298
|
+
*/
|
|
299
|
+
app.get('/health', (req, res) => {
|
|
300
|
+
// Count available bundles
|
|
301
|
+
let bundleCount = 0;
|
|
302
|
+
if (fs.existsSync(BUNDLES_DIR)) {
|
|
303
|
+
const entries = fs.readdirSync(BUNDLES_DIR, { withFileTypes: true });
|
|
304
|
+
bundleCount = entries.filter(e => e.isDirectory()).length;
|
|
305
|
+
}
|
|
306
|
+
res.json({
|
|
307
|
+
status: 'ok',
|
|
308
|
+
gameUrl: GAME_URL,
|
|
309
|
+
bundlesDir: BUNDLES_DIR,
|
|
310
|
+
bundleCount,
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
/**
|
|
314
|
+
* Process a render job
|
|
315
|
+
*/
|
|
316
|
+
async function processJob(job, manifest, config) {
|
|
317
|
+
job.status = 'processing';
|
|
318
|
+
const renderer = new PuppeteerRenderer();
|
|
319
|
+
try {
|
|
320
|
+
const result = await renderer.render(manifest, {
|
|
321
|
+
...config,
|
|
322
|
+
onProgress: (progress) => {
|
|
323
|
+
job.progress = progress;
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
if (result.success) {
|
|
327
|
+
job.status = 'completed';
|
|
328
|
+
job.outputPath = result.outputPath || null;
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
job.status = 'failed';
|
|
332
|
+
job.error = result.error || 'Unknown error';
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
job.status = 'failed';
|
|
337
|
+
job.error = error instanceof Error ? error.message : String(error);
|
|
338
|
+
}
|
|
339
|
+
job.completedAt = new Date();
|
|
340
|
+
}
|
|
341
|
+
// Start server
|
|
342
|
+
app.listen(PORT, () => {
|
|
343
|
+
console.log(`🎬 Replay Render Server running on http://localhost:${PORT}`);
|
|
344
|
+
console.log(`📺 Default Game URL: ${GAME_URL}`);
|
|
345
|
+
console.log(`📦 Bundles directory: ${BUNDLES_DIR}`);
|
|
346
|
+
console.log(`📁 Output directory: ${OUTPUT_DIR}`);
|
|
347
|
+
});
|
|
348
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
|
|
3
|
+
# Local development Docker Compose for replay-server
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# cd packages/replay-server
|
|
7
|
+
# docker-compose -f docker-compose.local.yml up --build
|
|
8
|
+
#
|
|
9
|
+
# This will:
|
|
10
|
+
# 1. Build the replay-server with embedded game-life-restart bundle
|
|
11
|
+
# 2. Start the server on port 3001
|
|
12
|
+
# 3. Mount ./output for video files
|
|
13
|
+
|
|
14
|
+
services:
|
|
15
|
+
replay-server:
|
|
16
|
+
build:
|
|
17
|
+
context: ../..
|
|
18
|
+
dockerfile: packages/replay-server/Dockerfile
|
|
19
|
+
ports:
|
|
20
|
+
- "3001:3001"
|
|
21
|
+
environment:
|
|
22
|
+
- PORT=3001
|
|
23
|
+
- BUNDLES_DIR=/app/packages/replay-server/bundles
|
|
24
|
+
- OUTPUT_DIR=/app/packages/replay-server/output
|
|
25
|
+
volumes:
|
|
26
|
+
# Mount output directory for easy access to rendered videos
|
|
27
|
+
- ./output:/app/packages/replay-server/output
|
|
28
|
+
# Increase shared memory for Chromium
|
|
29
|
+
shm_size: '2gb'
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agent-foundry/replay-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Video rendering service for AgentFoundry mini-game replays",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "tsx watch src/server/index.ts",
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"start": "node dist/server/index.js",
|
|
11
|
+
"render": "tsx src/cli/render.ts",
|
|
12
|
+
"clean": "rimraf dist output"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@agent-foundry/replay": "^1.0.1",
|
|
16
|
+
"express": "^4.18.2",
|
|
17
|
+
"puppeteer": "^22.0.0",
|
|
18
|
+
"fluent-ffmpeg": "^2.1.2",
|
|
19
|
+
"cors": "^2.8.5",
|
|
20
|
+
"uuid": "^9.0.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/express": "^4.17.21",
|
|
24
|
+
"@types/fluent-ffmpeg": "^2.1.24",
|
|
25
|
+
"@types/cors": "^2.8.17",
|
|
26
|
+
"@types/uuid": "^9.0.7",
|
|
27
|
+
"@types/node": "^20.11.0",
|
|
28
|
+
"typescript": "^5.3.3",
|
|
29
|
+
"tsx": "^4.7.0",
|
|
30
|
+
"rimraf": "^5.0.5"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
}
|
|
35
|
+
}
|