chore: sync backend and deployment changes

This commit is contained in:
2026-05-25 17:25:52 +09:00
parent d38d022872
commit fb5ec649cd
58 changed files with 17575 additions and 378 deletions

View File

@@ -59,6 +59,11 @@ const CODEX_LIVE_FINISHED_RETENTION_MS = Math.max(
const activeCodexExecutions = new Map();
const recentCodexExecutions = new Map();
function resolveCodexLiveModel(value) {
const normalized = String(value ?? '').trim();
return /^[A-Za-z0-9._:-]+$/u.test(normalized) ? normalized : CODEX_LIVE_MODEL;
}
function createCodexExecutionRecord({ requestId, child, tempDir }) {
return {
requestId,
@@ -134,6 +139,99 @@ function scheduleCodexExecutionCleanup(record) {
record.cleanupTimer.unref?.();
}
function normalizeCodexUsageMetricValue(value) {
if (typeof value === 'number' && Number.isFinite(value)) {
return Math.max(0, Math.round(value));
}
if (typeof value === 'string' && value.trim()) {
const normalized = Number(value);
if (Number.isFinite(normalized)) {
return Math.max(0, Math.round(normalized));
}
}
return null;
}
function extractCodexUsageSnapshot(parsed) {
if (!parsed || typeof parsed !== 'object') {
return null;
}
const usage =
parsed.usage && typeof parsed.usage === 'object'
? parsed.usage
: parsed.response &&
typeof parsed.response === 'object' &&
parsed.response !== null &&
parsed.response.usage &&
typeof parsed.response.usage === 'object'
? parsed.response.usage
: null;
if (!usage) {
return null;
}
const input = normalizeCodexUsageMetricValue(usage.input_tokens);
const output = normalizeCodexUsageMetricValue(usage.output_tokens);
const cached = normalizeCodexUsageMetricValue(usage.cached_input_tokens);
const reasoning = normalizeCodexUsageMetricValue(usage.reasoning_output_tokens ?? usage.reasoning_tokens);
const total =
normalizeCodexUsageMetricValue(usage.total_tokens) ??
normalizeCodexUsageMetricValue(usage.totalTokens) ??
[input ?? 0, output ?? 0].reduce((sum, value) => sum + value, 0);
if (input === null && output === null && cached === null && reasoning === null && total === null) {
return null;
}
return {
tokenTotals: {
total: total ?? 0,
input: input ?? 0,
output: output ?? 0,
cached: cached ?? 0,
reasoning: reasoning ?? 0,
},
totalTokens: total ?? 0,
};
}
function extractCodexUsageSnapshotFromText(output) {
const text = String(output ?? '');
if (!text.trim()) {
return null;
}
const lines = text
.split('\n')
.map((line) => line.trim())
.filter(Boolean);
for (let index = lines.length - 1; index >= 0; index -= 1) {
const line = lines[index] ?? '';
if (!line.startsWith('{')) {
continue;
}
try {
const usageSnapshot = extractCodexUsageSnapshot(JSON.parse(line));
if (usageSnapshot) {
return usageSnapshot;
}
} catch {
// ignore malformed JSON lines during fallback scan
}
}
return null;
}
function finalizeCodexExecution(record) {
if (record.completed) {
return;
@@ -642,6 +740,7 @@ async function runCodexLiveExecution(payload, response) {
const resourceDir = path.join(repoPath, 'public', '.codex_chat', sessionId, 'resource');
const uploadDir = path.join(resourceDir, 'uploads');
const codexBin = process.env.SERVER_COMMAND_RUNNER_CODEX_BIN?.trim() || process.env.PLAN_CODEX_BIN?.trim() || 'codex';
const codexModel = resolveCodexLiveModel(payload?.model);
const configuredIdleTimeoutMs = resolveCodexLiveIdleTimeoutMs(payload?.idleTimeoutSeconds);
const configuredMaxExecutionMs = resolveCodexLiveMaxExecutionMs(payload?.maxExecutionSeconds, configuredIdleTimeoutMs);
@@ -673,13 +772,14 @@ async function runCodexLiveExecution(payload, response) {
let stderrTail = '';
let jsonLineBuffer = '';
let completedText = '';
let streamedUsageSnapshot = null;
let idleTimer = null;
let executionTimer = null;
let terminationRequested = false;
const child = spawn(
codexBin,
['exec', '--model', CODEX_LIVE_MODEL, '--json', '--dangerously-bypass-approvals-and-sandbox', '-C', repoPath, '-'],
['exec', '--model', codexModel, '--json', '--dangerously-bypass-approvals-and-sandbox', '-C', repoPath, '-'],
{
cwd: repoPath,
stdio: ['pipe', 'pipe', 'pipe'],
@@ -703,6 +803,7 @@ async function runCodexLiveExecution(payload, response) {
broadcastCodexExecutionEvent(executionRecord, {
type: 'started',
pid: child.pid ?? null,
model: codexModel,
configuredIdleTimeoutSeconds: Math.round(configuredIdleTimeoutMs / 1000),
configuredMaxExecutionSeconds: Math.round(configuredMaxExecutionMs / 1000),
});
@@ -785,16 +886,7 @@ async function runCodexLiveExecution(payload, response) {
}
const { completedText: nextCompletedText, deltaText } = extractCodexStreamText(parsed);
if (nextCompletedText) {
refreshIdleTimer();
completedText = nextCompletedText;
broadcastCodexExecutionEvent(executionRecord, {
type: 'completed',
text: nextCompletedText,
});
return true;
}
const usageSnapshot = extractCodexUsageSnapshot(parsed);
if (deltaText) {
refreshIdleTimer();
@@ -802,10 +894,28 @@ async function runCodexLiveExecution(payload, response) {
type: 'delta',
text: deltaText,
});
return true;
}
return false;
if (usageSnapshot) {
streamedUsageSnapshot = usageSnapshot;
refreshIdleTimer();
broadcastCodexExecutionEvent(executionRecord, {
type: 'usage',
usageSnapshot,
});
}
if (nextCompletedText) {
refreshIdleTimer();
completedText = nextCompletedText;
broadcastCodexExecutionEvent(executionRecord, {
type: 'completed',
text: nextCompletedText,
usageSnapshot,
});
}
return Boolean(deltaText || usageSnapshot || nextCompletedText || activityLog);
};
child.stdout?.on('data', (chunk) => {
@@ -876,6 +986,18 @@ async function runCodexLiveExecution(payload, response) {
handleCodexJsonLine(trailingLine);
}
if (!streamedUsageSnapshot) {
const fallbackUsageSnapshot = extractCodexUsageSnapshotFromText([stdoutTail, trailingLine].filter(Boolean).join('\n'));
if (fallbackUsageSnapshot) {
streamedUsageSnapshot = fallbackUsageSnapshot;
broadcastCodexExecutionEvent(executionRecord, {
type: 'usage',
usageSnapshot: fallbackUsageSnapshot,
});
}
}
if (code !== 0) {
broadcastCodexExecutionEvent(executionRecord, {
type: 'error',