Files
Mujoco_WASM/web_platform/e2e/fixtures/decisionService.ts
T
chenlin f3a8a38acd
web-platform-ci / Standalone decision service (no cloud credentials) (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
lekiwi-compatibility / cpu-compatibility (push) Has been cancelled
web-platform-ci / Standalone decision service (no cloud credentials) (pull_request) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (pull_request) Has been cancelled
web-platform-ci / Playwright E2E (pull_request) Has been cancelled
lekiwi-compatibility / cpu-compatibility (pull_request) Has been cancelled
feat: release v1.0.1 CADWorld 网站与 LeKiwi 智能抓放
集成同源 BYOK 会话隔离、精简模型设置、官方订阅入口和 HTTPS 发布运维;保留本地训练/调参与控制能力。同步 npm 版本及 CHANGELOG,记录公网真实 API 验收仍待用户凭据。
2026-09-24 09:57:41 +08:00

87 lines
3.3 KiB
TypeScript

import { spawn } from 'node:child_process';
import { randomBytes } from 'node:crypto';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import { stopProcess } from './controlBridge';
/** Explicit live-test helper: keys stay in Python memory, only the ephemeral bridge
* token crosses to the browser. Tighten (never bypass) production admission limits. */
export async function startLiveDecisionService() {
const token = randomBytes(32).toString('hex');
const directory = await mkdtemp(join(tmpdir(), 'lekiwi-live-decision-'));
const script = `
import asyncio, json, os, signal, sys
from pathlib import Path
from aiohttp import web
from decision_server.server import create_app, STATE
from decision_server.credentials import deepseek_llm, openrouter_jev
from decision_server.protocol import DecisionError
async def main():
app = create_app(Path(sys.argv[1]), os.environ.pop('DECISION_SMOKE_TOKEN'), origins={'http://127.0.0.1:4176'}, port=0)
service = app[STATE]
service.connections.values['llm'] = deepseek_llm(Path('.env'))
service.connections.values['jev'] = openrouter_jev(Path('.env'))
original = service.admit
counts = {'llm': 0, 'jev': 0}
def admit(role, stamp):
if counts[role] >= {'llm': 1, 'jev': 12}[role]:
raise DecisionError('live_smoke_budget', 429)
original(role, stamp)
counts[role] += 1
service.admit = admit
runner = web.AppRunner(app, access_log=None, handler_cancellation=True)
await runner.setup()
site = web.TCPSite(runner, '127.0.0.1', 0)
await site.start()
port = site._server.sockets[0].getsockname()[1]
service.hosts = {f'127.0.0.1:{port}'}
stop = asyncio.Event()
for sig in (signal.SIGTERM, signal.SIGINT):
asyncio.get_running_loop().add_signal_handler(sig, stop.set)
print('DECISION_READY ' + json.dumps({'endpoint': f'http://127.0.0.1:{port}'}), flush=True)
try:
await stop.wait()
finally:
await runner.cleanup()
asyncio.run(main())
`;
const child = spawn(resolve('.venv/bin/python'), ['-u', '-c', script, directory], {
env: { ...process.env, DECISION_SMOKE_TOKEN: token, PYTHONNOUSERSITE: '1', PYTHONPATH: '' },
stdio: ['ignore', 'pipe', 'ignore'],
});
const stop = async () => {
if (child.pid) await stopProcess(child);
await rm(directory, { recursive: true, force: true });
};
try {
const endpoint = await new Promise<string>((resolve, reject) => {
let output = '';
const timer = setTimeout(
() => reject(new Error('真实模型服务启动超时(不输出认证日志)')),
15000,
);
child.once('error', () => {
clearTimeout(timer);
reject(new Error('真实模型服务启动失败'));
});
child.once('exit', (code) => {
clearTimeout(timer);
reject(new Error(`真实模型服务退出:${code}`));
});
child.stdout.on('data', (chunk: Buffer) => {
output = (output + chunk.toString()).slice(-8192);
const match = output.match(/DECISION_READY (\{[^\n]+\})/);
if (match) {
clearTimeout(timer);
resolve((JSON.parse(match[1]) as { endpoint: string }).endpoint);
}
});
});
return { endpoint, token, stop };
} catch (error) {
await stop();
throw error;
}
}