chore: version up to 2.80.38 and package with refined recovery

This commit is contained in:
g1nation
2026-05-13 00:15:45 +09:00
parent 6c4bc3494f
commit eb36cec050
15 changed files with 202 additions and 62 deletions
+38 -6
View File
@@ -125,8 +125,36 @@ export function shouldFinalOnlyRetry(cleaned: CleanedAssistantOutput): boolean {
}
/**
* Should we silently continue from where the answer was cut off? Only when it actually hit the
* output-token ceiling and we already have a non-trivial visible answer to continue from.
* Does the answer plainly end mid-sentence / mid-structure? Conservative — only flags *unambiguous*
* incompleteness (a complete Korean sentence may legitimately end without a period, so we never flag
* a plain syllable like `다`/`요`; we only flag connective particles, mid-English-words, mid-clause
* commas/colons, unclosed code fences/brackets, and dangling markdown bullets/headings).
*/
export function looksCutOff(text: string): boolean {
const t = (text || '').replace(/\s+$/, '');
if (t.length < 12) return false;
// unclosed code fence
if ((t.match(/```/g) || []).length % 2 === 1) return true;
// ends with an opening bracket / quote (unclosed pair)
if (/[([{“‘"'`]$/.test(t)) return true;
// dangling markdown bullet / heading / blockquote with no content after the marker
if (/(?:^|\n)\s*(?:[-*+]|#{1,6}|>|\d+\.)\s*$/.test(t)) return true;
// ends mid-English-word or mid-number
if (/[A-Za-z0-9]$/.test(t)) return true;
// ends mid-clause (comma / colon / semicolon / list separator)
if (/[,:;·、,]$/.test(t)) return true;
// ends with a Korean particle / connective ending that NEVER closes a sentence
if (/(?:으로|로서|로써|로|의|에서|에게|한테|에|을|를|과|와|이랑|랑|는|은|이|가|도|만|까지|부터|마다|조차|마저|밖에|뿐|처럼|같이|보다|이나|거나|든지|든가|고|며|면서|면|어서|아서|여서|니까|는데|은데|ㄴ데|지만|던|도록)$/.test(t)) return true;
return false;
}
/**
* Should we silently continue from where the answer was cut off? The point is to recover regardless
* of *why* it stopped, since local engines / SDKs often report the stop reason wrongly or not at all:
* - the engine said it hit the output cap (`output-limit`), OR
* - it generated close to the cap (a complete answer wouldn't dangle that early), OR
* - the visible answer plainly ends mid-sentence and the engine didn't give a clean "done" reason.
* Never continues from a too-short fragment, and never from a clean ending (terminal punctuation).
*/
export function shouldAutoContinue(
stopKind: GenerationStopKind,
@@ -134,10 +162,14 @@ export function shouldAutoContinue(
outputTokens: number,
maxOutputTokens: number
): boolean {
if (stopKind !== 'output-limit') return false;
if (!visibleAnswer || visibleAnswer.trim().length < 40) return false;
if (!Number.isFinite(maxOutputTokens) || maxOutputTokens <= 0) return true;
return outputTokens >= Math.floor(maxOutputTokens * 0.8);
const v = (visibleAnswer || '').trim();
if (v.length < 24) return false;
// These won't be fixed by generating more text — don't auto-continue.
if (stopKind === 'user-stopped' || stopKind === 'context-overflow' || stopKind === 'error' || stopKind === 'tool-calls') return false;
if (stopKind === 'output-limit') return true;
if (Number.isFinite(maxOutputTokens) && maxOutputTokens > 0 && outputTokens >= Math.floor(maxOutputTokens * 0.85)) return true;
// 'complete' (eosFound) or 'unknown' but the text is plainly unfinished → continue.
return looksCutOff(v);
}
/** Appended to the system prompt for a final-only retry — the previous reply was reasoning-only. */