






















@@ -41,6 +41,7 @@ const MODEL_CAPACITY_ERROR_USER_MESSAGE =
4141const OVERLOADED_ERROR_USER_MESSAGE =
4242"The AI service is temporarily overloaded. Please try again in a moment.";
4343const FINAL_TAG_RE = /<\s*\/?\s*final\s*>/gi;
44+const TOOL_CALLS_OMITTED_PLACEHOLDER_LINE_RE = /^[ \t]*\[tool calls omitted\][ \t]*$/i;
4445const ERROR_PREFIX_RE =
4546/^(?:error|(?:[a-z][\w-]*\s+)?api\s*error|openai\s*error|anthropic\s*error|gateway\s*error|codex\s*error|request failed|failed|exception)(?:\s+\d{3})?[:\s-]+/i;
4647const CONTEXT_OVERFLOW_ERROR_HEAD_RE =
@@ -332,6 +333,22 @@ function stripFinalTagsFromText(text: unknown): string {
332333return normalized.replace(FINAL_TAG_RE, "");
333334}
334335336+function stripToolCallsOmittedPlaceholderLines(text: string): string {
337+let result = "";
338+let start = 0;
339+while (start < text.length) {
340+const newlineIndex = text.indexOf("\n", start);
341+const end = newlineIndex === -1 ? text.length : newlineIndex + 1;
342+const chunk = text.slice(start, end);
343+const line = chunk.endsWith("\n") ? chunk.slice(0, -1).replace(/\r$/, "") : chunk;
344+if (!TOOL_CALLS_OMITTED_PLACEHOLDER_LINE_RE.test(line)) {
345+result += chunk;
346+}
347+start = end;
348+}
349+return result;
350+}
351+335352function collapseConsecutiveDuplicateBlocks(text: string): string {
336353const trimmed = text.trim();
337354if (!trimmed) {
@@ -383,15 +400,18 @@ export function sanitizeUserFacingText(text: unknown, opts?: { errorContext?: bo
383400}
384401const errorContext = opts?.errorContext ?? false;
385402const stripped = stripInboundMetadata(stripInternalRuntimeContext(stripFinalTagsFromText(raw)));
386-const trimmed = stripped.trim();
403+// Replay repair may synthesize this placeholder to keep provider transcripts valid.
404+// It is internal scaffolding, so drop standalone placeholder lines before delivery
405+// while preserving ordinary inline mentions a user may be discussing.
406+const withoutPlaceholder = stripToolCallsOmittedPlaceholderLines(stripped);
407+const trimmed = withoutPlaceholder.trim();
387408if (!trimmed) {
388409return "";
389410}
390411391412if (!errorContext && shouldRewriteRawPayloadWithoutErrorContext(trimmed)) {
392413return formatRawAssistantErrorForUi(trimmed);
393414}
394-395415if (errorContext) {
396416const execDeniedMessage = formatExecDeniedUserMessage(trimmed);
397417if (execDeniedMessage) {
@@ -422,19 +442,15 @@ export function sanitizeUserFacingText(text: unknown, opts?: { errorContext?: bo
422442if (isBillingErrorMessage(trimmed)) {
423443return BILLING_ERROR_USER_MESSAGE;
424444}
425-426445if (isInvalidStreamingEventOrderError(trimmed)) {
427446return "LLM request failed: provider returned an invalid streaming response. Please try again.";
428447}
429-430448if (isRawApiErrorPayload(trimmed) || isLikelyHttpErrorText(trimmed)) {
431449return formatRawAssistantErrorForUi(trimmed);
432450}
433-434451if (isStreamingJsonParseError(trimmed)) {
435452return "LLM streaming response contained a malformed fragment. Please try again.";
436453}
437-438454if (ERROR_PREFIX_RE.test(trimmed)) {
439455const prefixedCopy = formatRateLimitOrOverloadedErrorCopy(trimmed);
440456if (prefixedCopy) {
@@ -451,6 +467,6 @@ export function sanitizeUserFacingText(text: unknown, opts?: { errorContext?: bo
451467}
452468}
453469454-const withoutLeadingEmptyLines = stripped.replace(/^(?:[ \t]*\r?\n)+/, "");
470+const withoutLeadingEmptyLines = withoutPlaceholder.replace(/^(?:[ \t]*\r?\n)+/, "");
455471return collapseConsecutiveDuplicateBlocks(withoutLeadingEmptyLines);
456472}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。