refactor: modularize portal server and harden session recovery

This commit is contained in:
john
2026-07-24 18:39:24 +08:00
parent 1d291fd528
commit 785731d98d
109 changed files with 29185 additions and 5836 deletions
+22
View File
@@ -33,6 +33,25 @@ function messageContentItems(event) {
return candidates.find((content) => Array.isArray(content)) ?? [];
}
function providerReplyError(event) {
if (event?.type !== 'Message') return null;
const text = messageContentItems(event)
.filter((item) => item?.type === 'text')
.map((item) => String(item?.text ?? ''))
.join('')
.trim();
if (!/^Ran into this error:\s*/i.test(text)) return null;
const normalized = text
.replace(/^Ran into this error:\s*/i, '')
.replace(/\n\nPlease retry if you think this is a transient or recoverable error\.?\s*$/i, '')
.trim();
const error = new Error(normalized || 'session reply failed');
error.code = /tool_calls|tool_call_id|insufficient tool messages/i.test(normalized)
? 'SESSION_TOOL_HISTORY_POISONED'
: 'SESSION_REPLY_ERROR';
return error;
}
function parseGenerateImageResult(toolResult) {
const content = toolResult?.value?.content;
if (!Array.isArray(content)) return null;
@@ -117,6 +136,7 @@ export async function consumeSessionEventsUntilFinish(
const reader = Readable.fromWeb(body);
const decoder = new TextDecoder();
const toolEvidence = createToolEvidenceCollector();
let pendingProviderError = null;
let buffer = '';
const deadline = Date.now() + Math.max(1, Number(timeoutMs) || 1);
@@ -137,6 +157,7 @@ export async function consumeSessionEventsUntilFinish(
const event = parseSessionStreamEvent(trimmed);
if (!event) continue;
if (!eventMatchesRequest(event, requestId)) continue;
pendingProviderError = providerReplyError(event) ?? pendingProviderError;
toolEvidence.observe(event);
onEvent?.(event);
if (event.type === 'Error') {
@@ -146,6 +167,7 @@ export async function consumeSessionEventsUntilFinish(
throw err;
}
if (event.type === 'Finish') {
if (pendingProviderError) throw pendingProviderError;
return {
finishEvent: event,
tokenState: event.token_state ?? null,