Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion config/quality/file-size-baseline.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"_rebaseline_2026_08_09_9296_adobe_media_capabilities": "PR #9296 (artickc, fix/adobe-firefly-model-capabilities) own growth: src/app/api/v1/models/catalog.ts 1590->1597 (+7). The image and video catalog serializers now expose the already-normalized Adobe Firefly discovery capability data (media_capabilities, plus the existing video modality/size fields) at their only response-emission chokepoints. The discovery parser and capability normalization remain in open-sse/services/adobeFireflyModels.ts; extracting these seven serialization fields would obscure the catalog contract. Covered by tests/unit/adobe-firefly.test.ts and tests/unit/image-upscale.test.ts.",
"_rebaseline_2026_08_08_v3850_base_drift_batch_9757": "Base drift on release/v3.8.50, not own growth: the 08-06..08-08 merge batches grew 12 already-frozen (or newly-landed) files without carrying their rebaselines — the dedicated rebaseline PR #9616 was closed as 'superseded' but its file-size entries never actually reached the base, and later merges (#8894 combos page, #9539 EditConnectionModal, #8895 models route, #9294/#9293 catalog, #9541 db/core, #8970 tokenHealthCheck, #8925 mcp schemas+server, #8890 accountFallback, #9467 chat.ts, #8931 openai-to-kiro, ProxyRegistryManager) kept growing them. All 12 values re-measured on THIS branch's tree (= pure tip + this PR's 1-line chat.ts fix, which adds zero lines). This PR's own source changes (chat.ts identifier restore, stream.ts format carve-out) do not grow any frozen file past these values.",
"_rebaseline_2026_08_08_migration_135_collision": "fix(db): resolve migration version 135 numbering collision — #9449's 135_connection_runtime_state.sql and #8908's 135_migrate_model_capability_max_token.sql both claimed version 135 (#9449 branched before #8908 merged and never got renumbered before landing on release/v3.8.50), which threw 'Migration version collision detected' the moment ANY code touched the database — a fresh install/deploy from this tip cannot even boot. Renumbered the later-landing file to 140 (next free slot) and added the matching isSchemaAlreadyApplied('140') retroactive guard, matching the established pattern already used for the prior 135/136 -> 137/138 renumber in the same file. Own growth: src/lib/db/migrationRunner.ts 1084->1094 (+10, the new case block) — irreducible, matches the existing per-case guard pattern exactly. Covered by tests/unit/migration-135-numbering-collision.test.ts (2/2), confirmed failing (reproducing the exact live crash) against the pre-fix colliding filenames, passing after.",
"_rebaseline_2026_08_08_9183_reasoning_cache_index_sync": "Extracted fix(responses-api): sync reasoning-cache write index with the fixed read side (from the originally-authored #9183) — chatCore.ts's write side cached every response under a hardcoded messageIndex:0, and translator/index.ts's plain-turn (non-tool-call) cache-key lookup ALSO still hardcoded messageIndex 0 at its call site (a second, previously-undiscovered instance of the same hardcoding bug, found while re-verifying this fix against the current upstream tip — the two never agreed once a conversation went past its first assistant turn, so DeepSeek/Xiaomi-mimo plain-turn reasoning replay silently missed the cache). Own growth: open-sse/handlers/chatCore.ts 5034->5042 (+8, computing messageIndex from the incoming request's message count at both the streaming and non-streaming cache-write call sites) — irreducible call-site wiring. Covered by tests/unit/reasoning-cache.test.ts (new end-to-end write/read regression test, rebaselined below) and tests/unit/translator-helper-branches.test.ts fixture updates. Other #9183 sub-fixes (output_index collision prevention, reasoning-content-alias generalization) were originally assumed already superseded by upstream's own independent fix — a live incident 2026-08-08 disproved that for the message-vs-tool-call collision case specifically (fixed separately in #9822); not re-extracted here since this PR's own scope is the narrower messageIndex sync only.",
"_rebaseline_2026_08_02_9259_rolling_rpm": "PR #9259 (issue #8733) own growth: open-sse/services/rateLimitManager.ts baseline 1060->1167 (+107; final source 1153). The existing withRateLimit chokepoint now composes process-local rolling RPM leases with Bottleneck admission, releases pre-dispatch leases on queue timeout/abort/connection disable, preserves caller abort reasons, and wires 429/header state into the extracted rollingRpmGate.ts. The remaining growth is irreducible lifecycle wiring at the dispatch boundary plus the real watchdog test hooks needed to verify queued-wedge recovery; moving it further would obscure lease ownership and Bottleneck cleanup. Covered by the focused rate-limit manager/sliding-window suite (33/33); distributed multi-instance coordination remains explicitly out of scope.",
"_rebaseline_2026_07_24_8470_hyperagent_sticky_thread": "PR #8470 (artickc, fix/hyperagent-tool-loop-thread-sticky) own growth: open-sse/executors/hyperagent.ts 936->1025 (wc -l; check-file-size.mjs counts via split(\"\\n\").length so the gate sees 937->1026, +89, crosses the 1000 cap). Fixes a real bug where a reverse-conversion proxy (text-Intent/JSON to Claude Code native tool_calls) rewrites assistant messages between agentic tool-loop turns, breaking HyperAgent’s conversation-prefix fingerprint and cold-starting the thread mid tool-loop. Adds Anthropic tool_use/tool_result flattening to extractMessageText() plus a new rootUserFingerprint()/root-key lookup tier in resolveHyperAgentThreadBinding()/storeHyperAgentThreadAfterTurn() so the thread stays sticky across the tool loop. Cohesive additions inside the existing single-file executor; not extractable without splitting the executor mid-request-flow. Covered by tests/unit/executor-hyperagent.test.ts (19/19, +5 new cases for tool_result/tool_use flattening + root-key stickiness). Pre-merge review flagged a cross-conversation root-key collision risk (tracked in the PR’s own mandatory pre-merge checklist, not yet addressed) — unrelated to this file-size ratchet, tracked separately by /fix-prs.",
"_rebaseline_2026_07_25_8494_capability_filter_fail_closed": "PR #8494 (fix/capability-filters-fail-closed, #8488) own growth: open-sse/services/combo.ts 3640->3693 (+53) adds a fail-closed guard after filterTargetsByRequestCompatibility() — when every eligible target is excluded by request-capability filtering (vision/tools/etc) instead of quota/health, the combo now returns an explicit `capability_mismatch` 400 (describeCapabilityFilterExhaustion, imported from combo/comboStructure.ts) rather than silently falling through to a generic no-targets error, plus a `compatFilterFailOpen` escape hatch (combo config OR settings) mirrored at both the main/auto and round-robin call sites for symmetry. combo/comboStructure.ts (previously under cap, un-frozen) grows 794->918 (+124) — new home for describeCapabilityFilterExhaustion + providerSupportsEmulatedToolCalling (#5240 emulated tool-calling exemption so fail-closed does not regress prompt-emulation-only combos like all-chatgpt-web). Irreducible orchestration wiring at the existing filter chokepoint (same precedent as #7301's universal-cooldown-retry generalization). Companion test tests/unit/combo-routing-engine.test.ts 3409->3449 (+40, fail-closed/fail-open coverage across both call sites) also rebaselined. Covered by tests/unit/8488-capability-filter-fail-closed.test.ts (new) + 95/95 passing across both files. Structural shrink of combo.ts tracked in #3501.",
"_rebaseline_2026_07_25_8499_ts7_result_union_predicates": "PR #8499 (backryun, chore/ts7-types-executor-scattered) own growth: muse-spark-web.ts 1396->1405 (+9, irreducible). Under this workspace's `strictNullChecks: false`, the boolean-literal discriminant on `GraphqlResult` (`{ ok: true } | { ok: false; error: string }`) narrows the positive `.ok===true` branch but leaves `!result.ok` at the full union under TS7, making `.error` unreachable to the checker at the two call sites (warmup, mode-switch). Fixed by adding a single `isGraphqlFailure()` type-predicate helper (doc comment + 3-line body) reused at both call sites instead of duplicating the predicate inline — not extractable to a shared module without splitting a single-file executor's local narrowing helper out of its own file. Covered by the existing muse-spark-web executor test suite (no behavior change, pure narrowing fix).",
Expand Down Expand Up @@ -162,6 +166,7 @@
"cap": 1000,
"testCap": 1000,
"testFrozen": {
"tests/unit/reasoning-cache.test.ts": 1035,
"_rebaseline_2026_06_27_5193_antigravity_test": "#5193 own test growth: oauth-providers-config.test.ts 870->873 (+3: antigravity projectId assertion + 50ms tick for the now fire-and-forget onboarding, matching the no-PKCE/no-openid flow).",
"_rebaseline_2026_07_02_5928_base_red": "web-cookie-providers-new.test.ts 845->850: #5928 (test(security) Kimi Web URL host parse, CodeQL #689) grew the file +5 lines and merged into release/v3.8.44 WITHOUT rebaselining, leaving a fast-gates base-red that blocked every subsequent PR->release. Test growth is legitimate (a security regression test); maintainer absorbs the drift here. Frozen at 850.",
"_rebaseline_2026_07_09_6126_clinepass_dualauth": "#6126 (ClinePass dual-auth) own test growth: oauth-providers-config.test.ts 842->845 (+3: clinepass key/config/required-fields entries reusing the Cline WorkOS flow config, needed after registering clinepass in the oauth.ts PROVIDERS enum).",
Expand Down Expand Up @@ -350,7 +355,7 @@
"open-sse/executors/deepseek-web.ts": 1148,
"open-sse/executors/grok-web.ts": 1044,
"open-sse/executors/muse-spark-web.ts": 1405,
"open-sse/handlers/chatCore.ts": 5034,
"open-sse/handlers/chatCore.ts": 5042,
"open-sse/handlers/imageGeneration.ts": 3101,
"open-sse/handlers/responseSanitizer.ts": 1128,
"open-sse/handlers/search.ts": 1536,
Expand Down
24 changes: 12 additions & 12 deletions open-sse/handlers/chatCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,6 @@ import { stageTrace } from "./chatCore/stageTrace.ts";
import { attachCompressionUsageReceiptAfterAnalytics as attachCompressionUsageReceiptAfterAnalyticsFor } from "./chatCore/compressionUsageReceipt.ts";
import { prepareUpstreamBody } from "./chatCore/upstreamBody.ts";
import { getQuotaScopeLabelForProvider } from "../services/antigravityQuotaFamily.ts";

import {
getCallLogPipelineCaptureStreamChunks,
getCallLogPipelineMaxSizeBytes,
Expand Down Expand Up @@ -367,9 +366,7 @@ import {
isTpmExhausted,
isRpmExhausted,
} from "../services/geminiRateLimitTracker.ts";

import { isSmallEnoughForSemanticCache } from "../utils/estimateSize.ts";

/**
* Core chat handler - shared between SSE and Worker
* Returns { success, response, status, error } for caller to handle fallback
Expand All @@ -389,10 +386,8 @@ import { isSmallEnoughForSemanticCache } from "../utils/estimateSize.ts";
* @param {boolean} options.isCombo - Whether this request is from a combo
* @param {string} options.connectionId - Connection ID for settings lookup
*/

// extractSystemRoleMessages extracted to chatCore/claudeSystemRole.ts (#3501); re-exported above so
// existing importers (e.g. tests/unit/system-role-extraction.test.ts) keep resolving it from here.

export async function handleChatCore({
body,
modelInfo,
Expand Down Expand Up @@ -428,7 +423,6 @@ export async function handleChatCore({
/* fail open */
}
}

// Per-request model-routing metadata (first extracted slice of the request-setup phase).
const { apiFormat, customModelTargetFormat, requestedModel } = resolveChatCoreRequestSetup(
modelInfo,
Expand All @@ -442,7 +436,6 @@ export async function handleChatCore({
// (not Math.random) purely to satisfy CodeQL js/insecure-randomness — this id
// is a log-correlation token, not a security secret.
const traceId = globalThis.crypto.randomUUID().slice(0, 6);

// Emit request.started event for real-time dashboard
setImmediate(() => {
emit("request.started", {
Expand Down Expand Up @@ -4333,9 +4326,14 @@ export async function handleChatCore({
try {
const firstChoice = translatedResponse?.choices?.[0];
const msg = firstChoice?.message;
// The response being cached now will be replayed as history on the *next*
// turn, where the read side (translator/index.ts) keys the lookup by the
// message's real position in that future `messages` array — i.e. right
// after everything the client sent this turn.
const bodyMessages = (body as { messages?: unknown[] } | null | undefined)?.messages;
cacheReasoningFromAssistantMessage(msg, provider, model, {
requestId: skillRequestId,
messageIndex: 0,
messageIndex: Array.isArray(bodyMessages) ? bodyMessages.length : 0,
});
} catch {
// Cache capture is non-critical — never block the response
Expand Down Expand Up @@ -4760,12 +4758,15 @@ export async function handleChatCore({
// with tool_calls so it can be replayed on subsequent turns (DeepSeek V4, Kimi K2, etc.)
if (normalizedStreamStatus === 200 && streamResponseBody) {
try {
const body = streamResponseBody as Record<string, unknown>;
const choices = body.choices as { message?: Record<string, unknown> }[] | undefined;
const streamBody = streamResponseBody as Record<string, unknown>;
const choices = streamBody.choices as { message?: Record<string, unknown> }[] | undefined;
const msg = choices?.[0]?.message;
// See the non-streaming capture above: messageIndex must match the
// position this message will occupy in the *next* turn's history.
const bodyMessages = (body as { messages?: unknown[] } | null | undefined)?.messages;
cacheReasoningFromAssistantMessage(msg, provider, model, {
requestId: skillRequestId,
messageIndex: 0,
messageIndex: Array.isArray(bodyMessages) ? bodyMessages.length : 0,
});
} catch {
// Cache capture is non-critical — never block the stream
Expand Down Expand Up @@ -5025,7 +5026,6 @@ export async function handleChatCore({
}),
};
}

export function isTokenExpiringSoon(expiresAt, bufferMs = 5 * 60 * 1000) {
if (!expiresAt) return false;
const expiresAtMs = new Date(expiresAt).getTime();
Expand Down
2 changes: 1 addition & 1 deletion open-sse/translator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ export function translateRequest(

const cacheKey = hasToolCalls
? msg.tool_calls[0]?.id
: getAssistantMessageCacheKey(result, 0);
: getAssistantMessageCacheKey(result, messageIndex);
if (cacheKey) {
const cached = lookupReasoning(cacheKey);
if (cached) {
Expand Down
63 changes: 59 additions & 4 deletions tests/unit/reasoning-cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -865,11 +865,12 @@ describe("Reasoning Replay Cache — Translator Replay", () => {
}),
},
});
// NOTE: the non-tool-call cache key is built as `getAssistantMessageCacheKey(result, 0)`
// — the message index is hardcoded to 0 in the translator, so the key is always
// `request:<id>:message:0` regardless of the assistant message's actual position.
// The non-tool-call cache key is built as `getAssistantMessageCacheKey(result, messageIndex)`
// where messageIndex is the assistant message's real position in the `messages`
// array (index 1 here: user, assistant, user) — matching what the write side
// (chatCore.ts) now caches under once the response is generated.
cacheReasoning(
"request:req-plain-1:message:0",
"request:req-plain-1:message:1",
"deepseek",
"deepseek-v4-pro",
"Real cached plain-turn reasoning"
Expand Down Expand Up @@ -899,6 +900,60 @@ describe("Reasoning Replay Cache — Translator Replay", () => {
);
assert.equal(getReasoningCacheServiceStats().replays, 1);
});

it("write side (chatCore's messageIndex) and read side (translateRequest) agree on the same key end-to-end", () => {
// Regression for a mismatch where chatCore.ts always cached under
// `messageIndex: 0` (the position of the response within *its own* choices
// array) while translateRequest's read side looked up the message's real
// position in the *next* turn's full history — the two never agreed once a
// conversation went past its first assistant turn, so replay silently
// fell back to the placeholder in real multi-turn usage.
clearReasoningCacheAll();
clearModelsDevCapabilities();
saveModelsDevCapabilities({
deepseek: {
"deepseek-v4-pro": buildCapability({
interleaved_field: "reasoning_content",
reasoning: true,
tool_call: true,
}),
},
});

// Turn 1: the incoming request has a single user message (length 1), so
// the assistant response chatCore is about to cache will occupy index 1
// once it's appended to history for turn 2 — mirroring
// `messageIndex: bodyMessages.length` in chatCore.ts.
const turn1RequestBody = { messages: [{ role: "user", content: "hi" }] };
cacheReasoningFromAssistantMessage(
{ role: "assistant", content: "Hello! How can I help?", reasoning_content: "real reasoning" },
"deepseek",
"deepseek-v4-pro",
{ requestId: "req-e2e-1", messageIndex: turn1RequestBody.messages.length }
);

// Turn 2: client replays the full history including the cached assistant
// turn, now genuinely at index 1.
const translated = translateRequest(
FORMATS.OPENAI,
FORMATS.OPENAI,
"deepseek-v4-pro",
{
request_id: "req-e2e-1",
messages: [
{ role: "user", content: "hi" },
{ role: "assistant", content: "Hello! How can I help?" },
{ role: "user", content: "tell me more" },
],
},
false,
null,
"deepseek"
);

assert.equal(translated.messages[1].reasoning_content, "real reasoning");
assert.equal(getReasoningCacheServiceStats().replays, 1);
});
});

describe("Reasoning Replay Cache — API Route", () => {
Expand Down
Loading
Loading