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
集成同源 BYOK 会话隔离、精简模型设置、官方订阅入口和 HTTPS 发布运维;保留本地训练/调参与控制能力。同步 npm 版本及 CHANGELOG,记录公网真实 API 验收仍待用户凭据。
87 lines
3.3 KiB
TypeScript
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;
|
|
}
|
|
}
|