From 66997a6404e33eb542e34cab0bb4af0b254eecc0 Mon Sep 17 00:00:00 2001 From: rafaeldrincon Date: Tue, 28 Jul 2026 14:07:07 -0400 Subject: [PATCH] fix(logs): avoid giant provider pills for failed auto family requests (#8867) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(logs): avoid giant provider pills for failed auto family requests * refactor(logs): extract resolveRejectedComboProvider + cover it The provider label was decided inline in handleChat, which has no test harness — the change shipped untested and pushed chat.ts over its frozen size (1848 > 1845). Moved to rejectedRequestUsage.ts next to summarizeComboAttemptedModels, the helper it replaces on this path. chat.ts shrinks back under its baseline (no rebaseline needed) and the logic gets three cases in the suite that already covers its sibling: auto/* collapses to "auto", a named combo keeps its name, and bare "auto" (no slash) is NOT collapsed — that one is a combo request, not a family request. Also rebased on the current release tip and added the changelog fragment. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw Co-authored-by: rafaeldrincon Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --- .../8867-rejected-combo-provider-label.md | 1 + src/sse/handlers/chat.ts | 4 ++-- src/sse/handlers/rejectedRequestUsage.ts | 17 ++++++++++++++ tests/unit/rejected-request-usage.test.ts | 23 ++++++++++++++++++- 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 changelog.d/fixes/8867-rejected-combo-provider-label.md diff --git a/changelog.d/fixes/8867-rejected-combo-provider-label.md b/changelog.d/fixes/8867-rejected-combo-provider-label.md new file mode 100644 index 0000000000..670a943c79 --- /dev/null +++ b/changelog.d/fixes/8867-rejected-combo-provider-label.md @@ -0,0 +1 @@ +- **Logs**: a failed `auto/` request no longer writes every attempted model into `call_logs.provider` — the logs page builds its quick-filter pills from that column, so one failure produced a giant chip that flooded the filter row. Rejected combo requests are now labelled `auto` (for `auto/*`) or by the combo's own name diff --git a/src/sse/handlers/chat.ts b/src/sse/handlers/chat.ts index 1eedbe86e2..da10e9a550 100644 --- a/src/sse/handlers/chat.ts +++ b/src/sse/handlers/chat.ts @@ -908,13 +908,13 @@ export async function handleChat( // (success:false) so gate/breaker-rejected traffic is counted per key — support-mesh 2026-07-08. if (!response.ok) { try { - const { recordRejectedRequestUsage, summarizeComboAttemptedModels } = + const { recordRejectedRequestUsage, resolveRejectedComboProvider } = await import("./rejectedRequestUsage"); await recordRejectedRequestUsage({ status: response.status, model: body?.model || resolvedModelStr, requestedModel: body?.model || resolvedModelStr, - provider: summarizeComboAttemptedModels(combo?.models), + provider: resolveRejectedComboProvider(body?.model || resolvedModelStr, combo.name), endpoint: clientRawRequest?.endpoint, error: await getComboFailureLogError(response, combo.name), comboName: combo.name, diff --git a/src/sse/handlers/rejectedRequestUsage.ts b/src/sse/handlers/rejectedRequestUsage.ts index dc70e09e76..46b8099014 100644 --- a/src/sse/handlers/rejectedRequestUsage.ts +++ b/src/sse/handlers/rejectedRequestUsage.ts @@ -127,3 +127,20 @@ export function summarizeComboAttemptedModels(models: unknown): string { .filter((entry): entry is string => Boolean(entry)); return modelStrings.length > 0 ? modelStrings.join(", ") : "-"; } + +/** + * Provider label for a REJECTED combo request (#8867). + * + * summarizeComboAttemptedModels() joins every attempted model, which is right for + * diagnostics but wrong for `call_logs.provider`: the logs page builds its quick-filter + * pills from that column, so one failed `auto/gemma` turned into a chip listing a dozen + * models and flooded the filter row. Keep it short and categorical instead — `auto` for + * auto/* requests, otherwise the combo's own name. + */ +export function resolveRejectedComboProvider( + model: string | null | undefined, + comboName: string | null | undefined +): string { + if (typeof model === "string" && model.startsWith("auto/")) return "auto"; + return comboName || "combo"; +} diff --git a/tests/unit/rejected-request-usage.test.ts b/tests/unit/rejected-request-usage.test.ts index 44aa110ee5..a25ada202b 100644 --- a/tests/unit/rejected-request-usage.test.ts +++ b/tests/unit/rejected-request-usage.test.ts @@ -24,7 +24,7 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const usageHistory = await import("../../src/lib/usage/usageHistory.ts"); const callLogs = await import("../../src/lib/usage/callLogs.ts"); -const { recordRejectedRequestUsage, summarizeComboAttemptedModels } = +const { recordRejectedRequestUsage, summarizeComboAttemptedModels, resolveRejectedComboProvider } = await import("../../src/sse/handlers/rejectedRequestUsage.ts"); test.beforeEach(() => { @@ -175,3 +175,24 @@ test("summarizeComboAttemptedModels falls back to '-' for empty, missing, or inv assert.equal(summarizeComboAttemptedModels("not-an-array"), "-"); assert.equal(summarizeComboAttemptedModels([{ kind: "combo-ref" }, { foo: "bar" }]), "-"); }); + +test("#8867: resolveRejectedComboProvider keeps the provider label short for auto/* failures", () => { + // The logs page builds quick-filter pills from call_logs.provider — a failed + // auto/ used to write every attempted model there and flood the filter row. + assert.equal(resolveRejectedComboProvider("auto/gemma", "my-combo"), "auto"); + assert.equal(resolveRejectedComboProvider("auto/gemini", null), "auto"); + assert.equal(resolveRejectedComboProvider("auto/anything-at-all", undefined), "auto"); +}); + +test("#8867: a non-auto combo failure is labelled with the combo name", () => { + assert.equal(resolveRejectedComboProvider("gpt-5.6-sol", "balanced-load"), "balanced-load"); + assert.equal(resolveRejectedComboProvider(null, "balanced-load"), "balanced-load"); +}); + +test("#8867: falls back to 'combo' when there is no name, and 'auto' never leaks into it", () => { + assert.equal(resolveRejectedComboProvider("gpt-5.6-sol", null), "combo"); + assert.equal(resolveRejectedComboProvider(undefined, undefined), "combo"); + assert.equal(resolveRejectedComboProvider("", ""), "combo"); + // bare "auto" (no slash) is NOT a family request — it must not be collapsed + assert.equal(resolveRejectedComboProvider("auto", "named-combo"), "named-combo"); +});