Files
OmniRoute/tests/unit/i18n-missing-placeholder-fallback.test.ts
Hernan Javier Ardila Sanchez 3be0a5d290 fix: combo input-bound, Responses->Chat image strip, qwen-web toolCalling, empty-response exhaustion (#8476)
* test(tail): retire stale i18n __MISSING__ repro + fix qianfan website URL

Base-red slice 6, rebased onto the advanced release/v3.8.49 (91fd5f9). The oauth
grok-cli #7610 guard was already fixed on the base by #8027 (it reads the warning
from grokCliAuthJson.ts) — dropped from this slice to avoid a conflicting duplicate.
Remaining two, still red on the current base:

- i18n #7258: the "focused repro" asserted zh-TW.json STILL carries raw __MISSING__:
  placeholders. That backlog was filled (the "no locale has a raw __MISSING__: leaf"
  invariant is the durable guard); retired the now-inverted repro.
- qianfan: Baidu renamed the product page (product/wenxinworkshop -> product-s/
  qianfan_home); updated the expected website URL.

Validated (clean env): i18n 4/0, qianfan 5/0; oauth-modal-grok 2/0 already green on base.

* fix(resilience): short-circuit combo on input-bound failures (context_length_exceeded) (#8375)

isInputBoundRequestFailure() predicate detects deterministic input-bound
errors (context_length_exceeded/context_window_exceeded). The combo loop
propagates the original 400 immediately instead of burning MAX_GLOBAL_ATTEMPTS
retrying identical oversized inputs against every account.

Test: combo-input-bound-failure-8375.test.ts (1 test, 2 assertions)

* fix(resilience): add early-exit in combo dispatcher for input-bound failures (#8375)

When isInputBoundRequestFailure detects context_length_exceeded,
the combo loop returns {ok:false, response} immediately instead of
re-dispatching the oversized request.

Test: node --import tsx/esm --test tests/unit/combo-input-bound-failure-8375.test.ts
- 1 test, 2 assertions, 0 fail

* fix(translator): strip input_image from tool outputs in Responses->Chat downgrade (#8459)

toolOutputContentToString() extracts input_text/output_text parts and
replaces input_image with a placeholder instead of JSON.stringify'ing
the content-part array (which embedded raw ~52KB base64 as inert text).

Applied to both function_call_output and custom_tool_call_output branches.

Existing translator tests: 88/88 pass.
New tests: 4/4 pass.

* fix(providers): set qwen-web toolCalling to false — web-cookie provider has no native function calling (#8437)

qwen-web is a web-cookie provider that emulates tools via synthetic system
prompt text and <tool> XML parsing, never sending a native tools[] field
upstream. The filterTargetsByRequestCompatibility gate filters out non-tool-
calling targets when the request carries tools, but qwen-web's registry entry
had toolCalling=true, so the filter let it through and a tool-using session
failing over to qwen-web would silently degrade to text-only chat with
'Tool X does not exists' errors.

Sibling web-cookie providers (chatgpt-web, yuanbao-web, claude-web, etc.)
all correctly set toolCalling: false — qwen-web was an outlier introduced
in PR #7874.

Verification:
- LSP diagnostics: clean
- Pattern matches chatgpt-web, yuanbao-web, and other web-cookie providers

* fix(backend): empty upstream response mislabeled as exhausted_connection (#8397)

isEmptyContentFailure guard only matched '/empty content/i' but the actual
error text from detectMalformedNonStream is 'returned an empty response
(no usable choices/output)' — which lacks the word 'content'. Expanded
regex to also match '/empty response/i' so these transient upstream glitches
don't get classified as connection-level exhaustion in combo diagnostics.

Test: 28 existing combo-target-exhaustion tests pass (no new test needed)

* test(#8397): add regression test for empty-response 502 not marking provider/connection exhausted

* test(qwen-web): align registry snapshot with toolCalling:false

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(resilience): scope #8375 input-bound short-circuit to homogeneous remainders

The isInputBoundFailure short-circuit (context_length_exceeded /
context_window_exceeded) fired unconditionally on the first target, aborting
the whole combo even when later targets are a different model with a larger
context window — regressing the intentional heterogeneous-combo fallback that
isContextOverflow400 (#6637) protects. Reproduced with a 2-target combo
(small-context model fails, larger-context model would have succeeded): the
combo never reached target 2.

Scope the short-circuit to remainders where every remaining target shares the
same modelStr as the one that just failed — the "retrying will fail
identically" premise for context_length_exceeded only holds within a
homogeneous same-model pool.

Rebaselines open-sse/services/combo.ts's frozen file-size cap (3642->3679)
for this PR's own combo.ts growth (config/quality/file-size-baseline.json).

Refs #8375

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: Probe Test <probe@example.com>
Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-07-26 03:53:32 -03:00

114 lines
4.6 KiB
TypeScript

/**
* Regression test for #7258 — zh-TW (and other locales) rendering the raw
* `__MISSING__:<english>` sentinel written by `scripts/i18n/sync-ui-keys.mjs`
* instead of falling back to the clean English value.
*
* `deepMergeFallback` (src/i18n/request.ts) previously only substituted the
* EN value when a key was entirely `undefined`; a key that existed but still
* carried the untranslated placeholder passed through untouched and was
* rendered verbatim to the user.
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync, readdirSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";
import { deepMergeFallback, PLACEHOLDER_PREFIX } from "../../src/i18n/request.ts";
const messagesDir = path.join(
path.dirname(fileURLToPath(import.meta.url)),
"..",
"..",
"src",
"i18n",
"messages"
);
function loadLocale(locale: string): Record<string, unknown> {
const raw = readFileSync(path.join(messagesDir, `${locale}.json`), "utf8");
return JSON.parse(raw) as Record<string, unknown>;
}
function collectPlaceholderLeaves(node: unknown, pathPrefix: string, out: string[]): void {
if (node === null || typeof node !== "object") {
if (typeof node === "string" && node.startsWith(PLACEHOLDER_PREFIX)) {
out.push(pathPrefix);
}
return;
}
if (Array.isArray(node)) return;
for (const [key, value] of Object.entries(node as Record<string, unknown>)) {
collectPlaceholderLeaves(value, pathPrefix ? `${pathPrefix}.${key}` : key, out);
}
}
// ---------------------------------------------------------------------------
// 1. (Retired) The original repro asserted zh-TW.json STILL carried raw
// __MISSING__: placeholders. That translation backlog has since been filled, so
// the sentinel no longer ships on disk — the invariant "no locale has a raw
// __MISSING__: leaf" (test 3 below) is the durable guard. Keeping a test that
// requires the backlog to EXIST would fail exactly when the content is healthy.
// ---------------------------------------------------------------------------
test("#7258: deepMergeFallback replaces an untranslated __MISSING__ placeholder with the EN fallback value", () => {
const target: Record<string, unknown> = {
localUsageCommand: `${PLACEHOLDER_PREFIX}Run this command locally`,
};
const source: Record<string, unknown> = {
localUsageCommand: "Run this command locally",
};
const result = deepMergeFallback(target, source);
assert.equal(result.localUsageCommand, "Run this command locally");
assert.ok(!(result.localUsageCommand as string).startsWith(PLACEHOLDER_PREFIX));
});
test("#7258: deepMergeFallback still lets a real (non-placeholder) locale value win", () => {
const target: Record<string, unknown> = { greeting: "Hola" };
const source: Record<string, unknown> = { greeting: "Hello" };
const result = deepMergeFallback(target, source);
assert.equal(result.greeting, "Hola");
});
test("#7258: deepMergeFallback replaces nested placeholder leaves too", () => {
const target: Record<string, unknown> = {
ns: { a: `${PLACEHOLDER_PREFIX}English A`, b: "translated B" },
};
const source: Record<string, unknown> = {
ns: { a: "English A", b: "English B" },
};
const result = deepMergeFallback(target, source);
const ns = result.ns as Record<string, unknown>;
assert.equal(ns.a, "English A");
assert.equal(ns.b, "translated B", "already-translated sibling key is untouched");
});
// ---------------------------------------------------------------------------
// 2. General regression: for every shipped locale, the REAL production merge
// (locale ⟵ EN fallback) leaves zero raw __MISSING__: leaves.
// ---------------------------------------------------------------------------
test("#7258: after the real EN-fallback merge, no locale has a raw __MISSING__: leaf", () => {
const en = loadLocale("en");
const locales = readdirSync(messagesDir)
.filter((f) => f.endsWith(".json"))
.map((f) => f.replace(/\.json$/, ""))
.filter((locale) => locale !== "en");
assert.ok(locales.length > 0, "expected at least one non-EN locale file");
const offenders: Record<string, string[]> = {};
for (const locale of locales) {
const localeMessages = loadLocale(locale);
const merged = deepMergeFallback({ ...localeMessages }, en);
const leaves: string[] = [];
collectPlaceholderLeaves(merged, "", leaves);
if (leaves.length > 0) offenders[locale] = leaves;
}
assert.deepEqual(
offenders,
{},
`expected zero __MISSING__: leaves after EN fallback merge, found: ${JSON.stringify(offenders)}`
);
});