fix(combo): opt proactive-fallback compression into TV1 bail-out (no silent target drop) (#4228)

Integrated into release/v3.8.29 (opt proactive-fallback compression into TV1 bail-out; baseline combo.ts 2597->2601).
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-18 23:48:56 -03:00
committed by GitHub
parent 89824fb0d3
commit 48773face6
4 changed files with 89 additions and 2 deletions

View File

@@ -55,6 +55,7 @@
"_rebaseline_2026_06_17_ctx_editing_relays": "Context Editing relay coverage + 400-fallback (F4.2/F4.3) own growth: base.ts 1244->1292 (+48 at the existing dispatch chokepoint: extend the injection gate to anthropic-compatible-cc-* relays with an expanded comment documenting the claude-web / generic-anthropic-compatible exclusions; the contextEditingDisabled loop flag; and the 400-fallback block that, when an upstream rejects context_management with a 400, strips the param, re-serializes + re-signs (CCH), and retries the same URL once). Both edits sit on the per-URL request-build/fetch path and cannot be extracted without splitting the executor's fetch loop. No new file warranted — the logic is a few lines tightly coupled to the in-loop transformedBody/bodyString/response locals.",
"_rebaseline_2026_06_17_ctx_editing_telemetry": "Context Editing telemetry (F4.1) own growth: chatCore.ts 5875->5898 (+23 = a best-effort, Claude-only, non-streaming block at the existing non-streaming response handler that extracts the provider's server-side context-editing receipt from responseBody and records it under engine \"context-editing\"). The pure extractor (extractContextEditingTelemetry) lives in the existing small open-sse/config/contextEditing.ts and the DB writer (recordContextEditingTelemetry) in src/lib/db/compressionAnalytics.ts — both under cap. Cohesive at the response-handling chokepoint next to the existing attachCompressionUsageReceipt call; not extractable without hiding the receipt-recording boundary.",
"_rebaseline_2026_06_17_duckduckgo_free": "Free web search (free-claude-code port, Fase 6) own growth: handlers/search.ts 1442->1546 (+104 = tryDuckDuckGoFreeProvider, a dedicated provider path mirroring the existing tryZaiMCPProvider special-case: the lite endpoint returns HTML, not the JSON the generic tryProvider()/normalizeResponse() flow expects, so it cannot reuse that path). The reusable parser + guarded fetch (parseDuckDuckGoLite, freeWebSearch) live in the new small open-sse/services/freeWebSearch.ts (well under cap). Cohesive at the per-provider dispatch chokepoint; not extractable without hiding the provider boundary.",
"_rebaseline_2026_06_18_4228_combo_fallback_bailout": "PR #4228 own growth: combo.ts 2597->2601 (+4 at the existing proactive-fallback applyCompression call in handleComboChat = opt into the TV1 bail-out so a throwing fallback engine is SKIPPED instead of propagating out of executeTarget and being swallowed as a 'Speculative task error' that silently drops the combo target). minGainPercent:0 keeps the advance behavior identical to the default path — this only adds skip-on-throw. The bailout option is threaded through the (non-frozen) strategySelector.ts applyCompression signature down to applyStackedCompression. Cohesive guard at the existing fallback-compression chokepoint; not extractable without hiding the call site.",
"_rebaseline_2026_06_17_stream_recovery": "Stream recovery wiring (free-claude-code port, Fase 4) own growth: chatCore.ts 5898->5980 (+82 at the existing streaming-return chokepoint: read the resolved streamRecovery.enabled setting once; when ON, a reopenStream() thunk re-executes the SAME upstream — it closes over the ~15 per-attempt executor locals (executor/provider/modelToCall/bodyToSend/upstreamStream/execCreds/extendedContext/headers builders/onCredentialsRefreshed/skipUpstreamRetry/contextEditingEnabled) — and the body is wrapped with createRecoverableStream for transparent early-retry; OFF keeps the byte-identical wrapReadableStreamWithFinalize path). All reusable logic (HoldbackBuffer, createRecoverableStream, isRetryableStreamError, hasTerminalMarker) lives in the new open-sse/services/streamRecovery.ts (well under cap). Not extractable without hiding the per-attempt executor-dispatch boundary; opt-in (default OFF). Structural shrink of chatCore.ts tracked in #3501.",
"cap": 800,
"frozen": {
@@ -84,7 +85,7 @@
"open-sse/services/batchProcessor.ts": 828,
"open-sse/services/browserBackedChat.ts": 850,
"open-sse/services/claudeCodeCompatible.ts": 1202,
"open-sse/services/combo.ts": 2597,
"open-sse/services/combo.ts": 2601,
"open-sse/services/rateLimitManager.ts": 1035,
"open-sse/services/tokenRefresh.ts": 1997,
"open-sse/services/usage.ts": 3408,

View File

@@ -1281,7 +1281,11 @@ export async function handleComboChat({
const compressionResult = applyCompression(
attemptBody,
config.fallbackCompressionMode as CompressionMode,
{ model: modelStr }
// Opt into the TV1 bail-out so a throwing fallback engine is SKIPPED rather than
// propagating out of executeTarget and being swallowed as a "Speculative task
// error" (which silently drops this combo target). minGainPercent:0 keeps the
// advance behavior identical to the default path — this only adds skip-on-throw.
{ model: modelStr, bailout: { enabled: true, minGainPercent: 0 } }
);
if (compressionResult.compressed) {
log.info(

View File

@@ -97,6 +97,12 @@ export function applyCompression(
supportsVision?: boolean | null;
config?: CompressionConfig;
principalId?: string;
/**
* Opt into the TV1 stacked bail-out (skip-on-throw + min-gain). Default off keeps the
* legacy behavior. The combo proactive-fallback path enables it so a throwing engine is
* skipped instead of silently dropping the target. Flows through to applyStackedCompression.
*/
bailout?: BailoutConfig;
}
): CompressionResult {
if (mode === "off") {

View File

@@ -0,0 +1,76 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { applyCompression } from "../../../open-sse/services/compression/strategySelector.ts";
import { registerCompressionEngine } from "../../../open-sse/services/compression/index.ts";
import type { CompressionEngine } from "../../../open-sse/services/compression/engines/types.ts";
import type { CompressionConfig } from "../../../open-sse/services/compression/types.ts";
// The combo proactive-fallback path calls applyCompression(stacked); a throwing engine there
// propagates and is swallowed as a "Speculative task error", silently dropping the target. The
// fix lets that caller opt into the TV1 bail-out (enabled, minGainPercent 0 = skip-on-throw
// without changing the min-gain advance behavior). This documents the opt-in contract.
const THROWING_ENGINE: CompressionEngine = {
id: "applycompress-bailout-throw",
name: "Throwing Test Engine",
description: "test fixture",
icon: "",
targets: ["messages"],
stackable: true,
stackPriority: 999,
metadata: {
id: "applycompress-bailout-throw",
name: "Throwing Test Engine",
description: "test",
inputScope: "messages",
targetLatencyMs: 1,
supportsPreview: false,
stable: false,
},
apply() {
throw new Error("boom from fallback engine");
},
compress() {
throw new Error("boom from fallback engine");
},
getConfigSchema() {
return [];
},
validateConfig() {
return { valid: true, errors: [] };
},
};
function stackedConfig(): CompressionConfig {
return {
stackedPipeline: [{ engine: "applycompress-bailout-throw" }],
} as unknown as CompressionConfig;
}
describe("applyCompression — combo fallback bail-out opt-in", () => {
it("opting into bail-out skips a throwing engine instead of propagating (no silent drop)", () => {
registerCompressionEngine(THROWING_ENGINE);
const body = { messages: [{ role: "user", content: "hello world" }] };
let result: ReturnType<typeof applyCompression> | undefined;
assert.doesNotThrow(() => {
result = applyCompression(body, "stacked", {
config: stackedConfig(),
bailout: { enabled: true, minGainPercent: 0 },
});
});
assert.ok(result, "returns a result instead of throwing");
assert.equal(result!.compressed, false, "a crashing engine compresses nothing");
});
it("without bail-out, a throwing engine still propagates (TV1 default unchanged)", () => {
registerCompressionEngine(THROWING_ENGINE);
const body = { messages: [{ role: "user", content: "hello world" }] };
assert.throws(
() => applyCompression(body, "stacked", { config: stackedConfig() }),
/boom from fallback engine/,
"the default (opt-out) path is unchanged — bail-out is strictly opt-in"
);
});
});