Skip to content
Closed
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
4 changes: 4 additions & 0 deletions gui/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,10 @@ export const de: Record<TKey, string> = {
"logs.modelTooltip.configuredTier": "konfigurierte Stufe",
"logs.modelTooltip.responseTier": "Antwortstufe",
"logs.modelTooltip.supportsTier": "Stufenunterstützung",
"logs.modelTooltip.tierOutcome.confirmed": "bestätigt",
"logs.modelTooltip.tierOutcome.assumed": "angenommen",
"logs.modelTooltip.tierOutcome.downgraded": "herabgestuft",
"logs.modelTooltip.tierOutcome.unknown": "unbekannt",
"logs.tokens.reported": "gemeldet",
"logs.tokens.unreported": "nicht gemeldet",
"logs.tokens.unsupported": "nicht unterstützt",
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,10 @@ export const en = {
"logs.modelTooltip.configuredTier": "configured tier",
"logs.modelTooltip.responseTier": "response tier",
"logs.modelTooltip.supportsTier": "tier support",
"logs.modelTooltip.tierOutcome.confirmed": "confirmed",
"logs.modelTooltip.tierOutcome.assumed": "assumed",
"logs.modelTooltip.tierOutcome.downgraded": "downgraded",
"logs.modelTooltip.tierOutcome.unknown": "unknown",
"logs.tokens.reported": "reported",
"logs.tokens.unreported": "unreported",
"logs.tokens.unsupported": "unsupported",
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,10 @@ export const fr: Record<TKey, string> = {
"logs.modelTooltip.configuredTier": "niveau configuré",
"logs.modelTooltip.responseTier": "niveau de réponse",
"logs.modelTooltip.supportsTier": "prise en charge du niveau",
"logs.modelTooltip.tierOutcome.confirmed": "confirmé",
"logs.modelTooltip.tierOutcome.assumed": "supposé",
"logs.modelTooltip.tierOutcome.downgraded": "rétrogradé",
"logs.modelTooltip.tierOutcome.unknown": "inconnu",
"logs.tokens.reported": "communiqués",
"logs.tokens.unreported": "non communiqués",
"logs.tokens.unsupported": "non pris en charge",
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,10 @@ export const ja: Record<TKey, string> = {
"logs.modelTooltip.configuredTier": "設定ティア",
"logs.modelTooltip.responseTier": "応答ティア",
"logs.modelTooltip.supportsTier": "ティア対応",
"logs.modelTooltip.tierOutcome.confirmed": "確認済み",
"logs.modelTooltip.tierOutcome.assumed": "推定",
"logs.modelTooltip.tierOutcome.downgraded": "降格",
"logs.modelTooltip.tierOutcome.unknown": "不明",
"logs.tokens.reported": "報告済み",
"logs.tokens.unreported": "未報告",
"logs.tokens.unsupported": "非対応",
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,10 @@ export const ko: Record<TKey, string> = {
"logs.modelTooltip.configuredTier": "설정 티어",
"logs.modelTooltip.responseTier": "응답 티어",
"logs.modelTooltip.supportsTier": "티어 지원",
"logs.modelTooltip.tierOutcome.confirmed": "확인됨",
"logs.modelTooltip.tierOutcome.assumed": "추정됨",
"logs.modelTooltip.tierOutcome.downgraded": "강등됨",
"logs.modelTooltip.tierOutcome.unknown": "알 수 없음",
"logs.tokens.reported": "측정됨",
"logs.tokens.unreported": "미보고",
"logs.tokens.unsupported": "미지원",
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,10 @@ export const ru: Record<TKey, string> = {
"logs.modelTooltip.configuredTier": "настроенный уровень",
"logs.modelTooltip.responseTier": "уровень ответа",
"logs.modelTooltip.supportsTier": "поддержка уровня",
"logs.modelTooltip.tierOutcome.confirmed": "подтверждено",
"logs.modelTooltip.tierOutcome.assumed": "предполагается",
"logs.modelTooltip.tierOutcome.downgraded": "понижено",
"logs.modelTooltip.tierOutcome.unknown": "неизвестно",
"logs.tokens.reported": "сообщено",
"logs.tokens.unreported": "не сообщено",
"logs.tokens.unsupported": "не поддерживается",
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/tr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,10 @@ export const tr: Record<TKey, string> = {
"logs.modelTooltip.configuredTier": "yapılandırılan katman",
"logs.modelTooltip.responseTier": "yanıt katmanı",
"logs.modelTooltip.supportsTier": "katman desteği",
"logs.modelTooltip.tierOutcome.confirmed": "doğrulandı",
"logs.modelTooltip.tierOutcome.assumed": "varsayıldı",
"logs.modelTooltip.tierOutcome.downgraded": "düşürüldü",
"logs.modelTooltip.tierOutcome.unknown": "bilinmiyor",
"logs.tokens.reported": "bildirilen",
"logs.tokens.unreported": "bildirilmeyen",
"logs.tokens.unsupported": "desteklenmeyen",
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,10 @@ export const zhTW: Record<TKey, string> = {
"logs.modelTooltip.configuredTier": "設定層級",
"logs.modelTooltip.responseTier": "回應層級",
"logs.modelTooltip.supportsTier": "支援層級",
"logs.modelTooltip.tierOutcome.confirmed": "已確認",
"logs.modelTooltip.tierOutcome.assumed": "假定",
"logs.modelTooltip.tierOutcome.downgraded": "已降級",
"logs.modelTooltip.tierOutcome.unknown": "未知",
"logs.tokens.reported": "已上報",
"logs.tokens.unreported": "未上報",
"logs.tokens.unsupported": "不支援",
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,10 @@ export const zh: Record<TKey, string> = {
"logs.modelTooltip.configuredTier": "配置层级",
"logs.modelTooltip.responseTier": "响应层级",
"logs.modelTooltip.supportsTier": "支持层级",
"logs.modelTooltip.tierOutcome.confirmed": "已确认",
"logs.modelTooltip.tierOutcome.assumed": "假定",
"logs.modelTooltip.tierOutcome.downgraded": "已降级",
"logs.modelTooltip.tierOutcome.unknown": "未知",
"logs.tokens.reported": "已上报",
"logs.tokens.unreported": "未上报",
"logs.tokens.unsupported": "不支持",
Expand Down
5 changes: 4 additions & 1 deletion gui/src/pages/Logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Debug from "./Debug";

import type { LogsTab } from "./logs-tab-keydown";
import { logsTabKeyDown, readTabFromHash, selectLogsTab } from "./logs-tab-keydown";
import { modelTitle } from "./logs-model-title";
import { modelTitle, type ModelTitleTierOutcome } from "./logs-model-title";
import { speedLabel } from "./logs-speed-label";
import { formatEstimatedUsd, formatEstimatedUsdValue, summarizeEstimatedCosts } from "./logs-cost-format";
import { cacheSplit, isCursorUsageProvider, tokensTitle } from "./logs-token-title";
Expand Down Expand Up @@ -152,6 +152,9 @@ export interface LogEntry {
configuredServiceTier?: string;
configuredSpeedLabel?: string;
responseServiceTier?: string;
// #2455: qualifies responseServiceTier in the model tooltip — the echoed tier alone
// cannot say whether Fast was granted on a backend whose echo is not authoritative.
tierOutcome?: ModelTitleTierOutcome;
resolvedModel?: string;
modelSupportsServiceTier?: boolean;
status: number;
Expand Down
31 changes: 30 additions & 1 deletion gui/src/pages/logs-model-title.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,39 @@
import type { TFn } from "../i18n/shared";

export interface ModelTitleTierOutcome {
confirmation?: "confirmed" | "assumed" | "downgraded" | "unknown";
fastDowngradeReason?: string;
}

export interface ModelTitleEntry {
model: string;
resolvedModel?: string;
requestedServiceTier?: string;
configuredServiceTier?: string;
responseServiceTier?: string;
modelSupportsServiceTier?: boolean;
tierOutcome?: ModelTitleTierOutcome;
}

/**
* #2455: the echoed tier alone does not say whether Fast was granted. The ChatGPT
* backend answers `default` on turns it in fact scheduled as priority, so its echo is
* marked non-authoritative and the outcome stays `assumed` (#2558) — which is the
* honest answer, but only if the operator can see it. Qualify the echoed value with
* how much it is worth, and name the reason when the tier was actually declined.
*
* The confirmation word is this proxy's own judgement about the turn, not a value the
* upstream returned, so it is translated like any other visible string. The downgrade
* reason stays verbatim: it is a diagnostic identifier (`response-declined`) that maps
* to `fastDowngradeReason` in the source, and translating it would break that link.
*/
function tierConfirmationSuffix(outcome: ModelTitleEntry["tierOutcome"], t: TFn): string {
const confirmation = outcome?.confirmation;
if (!confirmation) return "";
const reason = confirmation === "downgraded" && outcome?.fastDowngradeReason
? `: ${outcome.fastDowngradeReason}`
: "";
return ` (${t(`logs.modelTooltip.tierOutcome.${confirmation}`)}${reason})`;
}

export function modelTitle(log: ModelTitleEntry, t: TFn): string {
Expand All @@ -15,7 +42,9 @@ export function modelTitle(log: ModelTitleEntry, t: TFn): string {
log.resolvedModel ? `${t("logs.modelTooltip.resolvedModel")}=${log.resolvedModel}` : undefined,
log.requestedServiceTier ? `${t("logs.modelTooltip.requestedTier")}=${log.requestedServiceTier}` : undefined,
log.configuredServiceTier ? `${t("logs.modelTooltip.configuredTier")}=${log.configuredServiceTier}` : undefined,
log.responseServiceTier ? `${t("logs.modelTooltip.responseTier")}=${log.responseServiceTier}` : undefined,
log.responseServiceTier
? `${t("logs.modelTooltip.responseTier")}=${log.responseServiceTier}${tierConfirmationSuffix(log.tierOutcome, t)}`
: undefined,
log.modelSupportsServiceTier !== undefined
? `${t("logs.modelTooltip.supportsTier")}=${log.modelSupportsServiceTier}`
: undefined,
Expand Down
124 changes: 124 additions & 0 deletions tests/logs-model-tier-confirmation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { describe, expect, test } from "bun:test";
import { modelTitle, type ModelTitleEntry } from "../gui/src/pages/logs-model-title";
import { readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";

/**
* #2455: a user routing gpt-5.x through the proxy asked for `service_tier: priority`,
* saw the backend echo something else, and had no way to tell whether Fast was granted.
*
* The echo alone cannot answer it. The ChatGPT-internal Codex backend returns
* `service_tier: "default"` on turns it in fact scheduled as priority, so its echo is
* marked non-authoritative and the outcome stays `assumed` rather than being read as a
* downgrade (#2558). That is the honest answer — but it was computed in `tierOutcome`
* and never shown, so the tooltip printed a bare echoed value that looked like a denial.
*/

/**
* Stub translator. The tier-outcome labels are marked with a `t:` prefix on purpose: a
* hardcoded English label would render the same bare word as a translated one, so
* without the marker these assertions could not tell the two apart. The real catalogs
* are checked separately, by file, in the locale-coverage test.
*/
const t = ((key: string) => {
const leaf = key.split(".").pop() ?? key;
return key.includes("tierOutcome.") ? `t:${leaf}` : leaf;
}) as never;

const CONFIRMATIONS = ["confirmed", "assumed", "downgraded", "unknown"] as const;

function entry(over: Partial<ModelTitleEntry> = {}): ModelTitleEntry {
return { model: "gpt-5.6-terra", ...over };
}

describe("model tooltip tier confirmation (#2455)", () => {
test("qualifies the echoed tier with the outcome", () => {
const title = modelTitle(
entry({ responseServiceTier: "default", tierOutcome: { confirmation: "assumed" } }),
t,
);
expect(title).toContain("=default (t:assumed)");
});

test("names the reason when the tier was actually declined", () => {
const title = modelTitle(
entry({
responseServiceTier: "default",
tierOutcome: { confirmation: "downgraded", fastDowngradeReason: "response-declined" },
}),
t,
);
expect(title).toContain("=default (t:downgraded: response-declined)");
});

test("a confirmed grant reads as confirmed", () => {
const title = modelTitle(
entry({ responseServiceTier: "priority", tierOutcome: { confirmation: "confirmed" } }),
t,
);
expect(title).toContain("=priority (t:confirmed)");
});

test("no outcome leaves the existing tooltip unchanged", () => {
const before = modelTitle(entry({ responseServiceTier: "default" }), t);
expect(before).toContain("=default");
expect(before).not.toContain("(");
});

test("an outcome without an echoed tier adds nothing", () => {
// The qualifier explains an echoed value; with nothing echoed there is nothing to
// qualify, and a lone parenthesis would read as a malformed field.
const title = modelTitle(entry({ tierOutcome: { confirmation: "assumed" } }), t);
expect(title).not.toContain("assumed");
expect(title).toBe("model=gpt-5.6-terra");
});

test("a downgrade with no recorded reason omits the colon", () => {
const title = modelTitle(
entry({ responseServiceTier: "default", tierOutcome: { confirmation: "downgraded" } }),
t,
);
expect(title).toContain("=default (t:downgraded)");
expect(title).not.toContain("downgraded:");
});

test("every confirmation value is translated in every locale", () => {
// The label is this proxy's own judgement about the turn, so it is visible text and
// must not fall back to a raw key in any dashboard language. Read the catalogs as
// files: importing the barrel pulls in the whole GUI dependency graph.
// Named explicitly: the directory also holds label modules that are not catalogs,
// and this list is the same one LOCALES declares in i18n/shared.ts.
const LOCALE_FILES = [
"en.ts", "de.ts", "fr.ts", "ko.ts", "zh.ts", "zh-TW.ts", "ru.ts", "ja.ts", "tr.ts",
];
const dir = join(import.meta.dir, "..", "gui", "src", "i18n");
const present = readdirSync(dir);
for (const file of LOCALE_FILES) {
expect(present, `${file} disappeared from i18n/`).toContain(file);
}

for (const file of LOCALE_FILES) {
const text = readFileSync(join(dir, file), "utf8");
for (const confirmation of CONFIRMATIONS) {
const key = `"logs.modelTooltip.tierOutcome.${confirmation}":`;
const line = text.split("\n").find(l => l.includes(key));
expect(line, `${file} is missing ${key}`).toBeTruthy();
const value = line!.slice(line!.indexOf(key) + key.length).trim().replace(/^"|",?$/g, "");
expect(value.length, `${file} ${key} is empty`).toBeGreaterThan(0);
}
}
});

test("the downgrade reason stays a verbatim identifier", () => {
// `response-declined` maps to fastDowngradeReason in the source; translating it
// would break the link between what the operator reads and what to grep for.
const title = modelTitle(
entry({
responseServiceTier: "default",
tierOutcome: { confirmation: "downgraded", fastDowngradeReason: "wire-unavailable" },
}),
t,
);
expect(title).toContain("wire-unavailable");
});
});
Loading