mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
This commit is contained in:
committed by
GitHub
parent
8a9524b364
commit
c568ab9cbb
@@ -10,6 +10,8 @@
|
||||
|
||||
### 🔧 Bug Fixes
|
||||
|
||||
- **dashboard (memory engine):** the memory engine status page no longer mixes English and Portuguese. The embedding / vector-store / rerank **status detail strings** were hardcoded in Portuguese in the backend (`resolveEmbeddingSource`, `engineStatus`), e.g. `auto: nenhuma fonte de embedding disponível` and `sqlite-vec ativo, dim=…`, while the surrounding UI labels render from the English i18n bundle — so an English user saw a half-translated page. The backend detail strings are now English (`auto: no embedding source available`, `sqlite-vec active, dim=…`, etc.), matching the rest of the page. Regression guard: `tests/unit/memory-engine-status.test.ts`. ([#5596](https://github.com/diegosouzapw/OmniRoute/issues/5596))
|
||||
|
||||
- **providers (cline):** stop falsely mapping valid **Cline** (OAuth) responses to `502 empty_choices` + account cooldown. `detectMalformedNonStream` only recognized `choices[].message.content` as a **string**, but some OpenAI-compatible upstreams — Cline via OAuth among them — return `content` as an **array of Anthropic-style text blocks** inside an OpenAI envelope. A non-empty response (recvBytes > 0) was therefore classified as `empty_choices` and turned into a 502 that also cooled the account down. The malformed-response detector now also treats a content array carrying at least one non-empty `text` block as real output. Regression guard: `tests/unit/diagnostics.test.ts`. ([#5559](https://github.com/diegosouzapw/OmniRoute/issues/5559))
|
||||
|
||||
- **embedded services (Windows):** fix **CLIProxyAPI install failing instantly with `spawn unzip ENOENT`** on Windows. The binary extractor spawned `unzip`, which is not a Windows system command — it only ships inside Git for Windows' `usr/bin`, a directory Node's `spawn` PATH never sees, so even users with Git installed hit the error. On Windows the extractor now uses PowerShell's built-in `Expand-Archive` (via `execFileAsync`, no shell — paths pass as a single non-interpreted arg, with `''`-escaping + `-LiteralPath` as defense in depth); other platforms keep using `unzip`. This is distinct from #5379 (that was `npm.cmd` needing `shell: true`). Regression guard: `tests/unit/binary-manager-extract-zip-5590.test.ts`. ([#5590](https://github.com/diegosouzapw/OmniRoute/issues/5590))
|
||||
|
||||
@@ -56,7 +56,7 @@ export function resolveEmbeddingSource(settings: MemorySettingsExtended): Embedd
|
||||
model: null,
|
||||
dimensions: null,
|
||||
signature: makeSignature(null, null, null),
|
||||
reason: "no_key: embeddingProviderModel não configurado",
|
||||
reason: "no_key: embeddingProviderModel not configured",
|
||||
};
|
||||
}
|
||||
// We can't do async here, so we report it as potentially available
|
||||
@@ -67,7 +67,7 @@ export function resolveEmbeddingSource(settings: MemorySettingsExtended): Embedd
|
||||
model,
|
||||
dimensions: null,
|
||||
signature: makeSignature("remote", model, null),
|
||||
reason: `provider remoto configurado: ${model}`,
|
||||
reason: `remote provider configured: ${model}`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export function resolveEmbeddingSource(settings: MemorySettingsExtended): Embedd
|
||||
model: null,
|
||||
dimensions: null,
|
||||
signature: makeSignature(null, null, null),
|
||||
reason: "static desabilitado nas configurações",
|
||||
reason: "static disabled in settings",
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -86,7 +86,7 @@ export function resolveEmbeddingSource(settings: MemorySettingsExtended): Embedd
|
||||
model: STATIC_MODEL,
|
||||
dimensions: 256,
|
||||
signature: makeSignature("static", STATIC_MODEL, 256),
|
||||
reason: "static (potion-base-8M) selecionado explicitamente",
|
||||
reason: "static (potion-base-8M) explicitly selected",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ export function resolveEmbeddingSource(settings: MemorySettingsExtended): Embedd
|
||||
model: null,
|
||||
dimensions: null,
|
||||
signature: makeSignature(null, null, null),
|
||||
reason: "transformers desabilitado nas configurações",
|
||||
reason: "transformers disabled in settings",
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -105,7 +105,7 @@ export function resolveEmbeddingSource(settings: MemorySettingsExtended): Embedd
|
||||
model: TRANSFORMERS_MODEL,
|
||||
dimensions: 384,
|
||||
signature: makeSignature("transformers", TRANSFORMERS_MODEL, 384),
|
||||
reason: "transformers.js (MiniLM-L6-v2) selecionado explicitamente",
|
||||
reason: "transformers.js (MiniLM-L6-v2) explicitly selected",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ export function resolveEmbeddingSource(settings: MemorySettingsExtended): Embedd
|
||||
model: providerModel,
|
||||
dimensions: null,
|
||||
signature: makeSignature("remote", providerModel, null),
|
||||
reason: `auto: provider ${providerId} configurado`,
|
||||
reason: `auto: provider ${providerId} configured`,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ export function resolveEmbeddingSource(settings: MemorySettingsExtended): Embedd
|
||||
model: STATIC_MODEL,
|
||||
dimensions: 256,
|
||||
signature: makeSignature("static", STATIC_MODEL, 256),
|
||||
reason: "auto: potion-base-8M (static) disponível",
|
||||
reason: "auto: potion-base-8M (static) available",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -149,14 +149,14 @@ export function resolveEmbeddingSource(settings: MemorySettingsExtended): Embedd
|
||||
model: TRANSFORMERS_MODEL,
|
||||
dimensions: 384,
|
||||
signature: makeSignature("transformers", TRANSFORMERS_MODEL, 384),
|
||||
reason: "auto: transformers.js (MiniLM-L6-v2) disponível",
|
||||
reason: "auto: transformers.js (MiniLM-L6-v2) available",
|
||||
};
|
||||
}
|
||||
|
||||
return noSource("auto: nenhuma fonte de embedding disponível");
|
||||
return noSource("auto: no embedding source available");
|
||||
}
|
||||
|
||||
return noSource("fonte de embedding desconhecida");
|
||||
return noSource("unknown embedding source");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -843,7 +843,7 @@ export async function retrievePreview(
|
||||
}
|
||||
// Qdrant returned nothing — fall through to sqlite-vec for parity
|
||||
// with production (so the Playground reflects the same fallback).
|
||||
fallbackReason = "Qdrant retornou 0 resultados — fallback p/ sqlite-vec";
|
||||
fallbackReason = "Qdrant returned 0 results — falling back to sqlite-vec";
|
||||
} catch (err: unknown) {
|
||||
fallbackReason = sanitizeErrorMessage(
|
||||
err instanceof Error ? err.message : String(err)
|
||||
@@ -969,7 +969,7 @@ export async function retrievePreview(
|
||||
log.warn("memory.preview.vector.fail", { error: fallbackReason });
|
||||
}
|
||||
} else {
|
||||
fallbackReason = "sqlite-vec não disponível (degradado para FTS5)";
|
||||
fallbackReason = "sqlite-vec not available (degraded to FTS5)";
|
||||
}
|
||||
} else {
|
||||
// EmbeddingError
|
||||
@@ -1073,7 +1073,7 @@ export async function engineStatus(): Promise<MemoryEngineStatus> {
|
||||
let vecAvailable = false;
|
||||
let vecRowCount = 0;
|
||||
let vecNeedsReindex = 0;
|
||||
let vecReason = "sqlite-vec não disponível";
|
||||
let vecReason = "sqlite-vec not available";
|
||||
|
||||
if (vec) {
|
||||
vecBackend = "sqlite-vec";
|
||||
@@ -1082,12 +1082,12 @@ export async function engineStatus(): Promise<MemoryEngineStatus> {
|
||||
const s = await vec.stats();
|
||||
vecRowCount = s.rowCount;
|
||||
vecNeedsReindex = s.needsReindex;
|
||||
vecReason = `sqlite-vec ativo, dim=${s.activeDim ?? "null"}`;
|
||||
vecReason = `sqlite-vec active, dim=${s.activeDim ?? "null"}`;
|
||||
} catch {
|
||||
vecReason = "sqlite-vec ativo mas stats falharam";
|
||||
vecReason = "sqlite-vec active but stats failed";
|
||||
}
|
||||
} else {
|
||||
vecReason = "sqlite-vec não disponível — usando apenas FTS5";
|
||||
vecReason = "sqlite-vec not available — using FTS5 only";
|
||||
}
|
||||
|
||||
// Qdrant
|
||||
@@ -1112,7 +1112,7 @@ export async function engineStatus(): Promise<MemoryEngineStatus> {
|
||||
if (qdrantHealthy && settings.vectorStore === "qdrant") {
|
||||
vecBackend = "qdrant";
|
||||
vecAvailable = true;
|
||||
vecReason = `Qdrant configurado em ${qdrantCfg.host}:${qdrantCfg.port}`;
|
||||
vecReason = `Qdrant configured at ${qdrantCfg.host}:${qdrantCfg.port}`;
|
||||
}
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
@@ -1121,12 +1121,12 @@ export async function engineStatus(): Promise<MemoryEngineStatus> {
|
||||
|
||||
// Rerank
|
||||
let rerankAvailable = false;
|
||||
let rerankReason = "rerank desabilitado";
|
||||
let rerankReason = "rerank disabled";
|
||||
if (settings.rerankEnabled && settings.rerankProviderModel) {
|
||||
rerankAvailable = true;
|
||||
rerankReason = `rerank ativo: ${settings.rerankProviderModel}`;
|
||||
rerankReason = `rerank active: ${settings.rerankProviderModel}`;
|
||||
} else if (settings.rerankEnabled && !settings.rerankProviderModel) {
|
||||
rerankReason = "rerank habilitado mas provider não configurado";
|
||||
rerankReason = "rerank enabled but provider not configured";
|
||||
}
|
||||
|
||||
const rerankParts = settings.rerankProviderModel?.split("/") ?? [];
|
||||
|
||||
@@ -27,8 +27,8 @@ describe("resolveEmbeddingSource", () => {
|
||||
assert.strictEqual(res.source, null);
|
||||
// The reason must indicate the lack of any source — not just be non-empty.
|
||||
assert.ok(
|
||||
res.reason.toLowerCase().includes("nenhuma"),
|
||||
`expected reason to mention "nenhuma", got: ${res.reason}`
|
||||
res.reason.toLowerCase().includes("no embedding source"),
|
||||
`expected reason to mention "no embedding source", got: ${res.reason}`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -77,8 +77,8 @@ describe("resolveEmbeddingSource", () => {
|
||||
assert.strictEqual(res.source, null);
|
||||
// The reason must reference the missing key, not just be non-empty.
|
||||
assert.ok(
|
||||
res.reason.includes("no_key") || res.reason.includes("configurado"),
|
||||
`expected reason to mention "no_key" or "configurado", got: ${res.reason}`
|
||||
res.reason.includes("no_key") || res.reason.includes("configured"),
|
||||
`expected reason to mention "no_key" or "configured", got: ${res.reason}`
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -129,6 +129,41 @@ test("engineStatus(): rerank section when not configured", async () => {
|
||||
assert.equal(typeof status.rerank.reason, "string", "rerank.reason must be a string");
|
||||
});
|
||||
|
||||
test("engineStatus(): detail strings are English, not mixed Portuguese (#5596)", async () => {
|
||||
core.getDbInstance();
|
||||
|
||||
const { engineStatus } = await import("../../src/lib/memory/retrieval.ts");
|
||||
const status = await engineStatus();
|
||||
|
||||
// Exact English values for the default (no-config, vec-disabled) state the
|
||||
// reporter saw rendered in Portuguese next to English UI labels.
|
||||
assert.equal(status.embedding.reason, "auto: no embedding source available");
|
||||
assert.equal(status.vectorStore.reason, "sqlite-vec not available — using FTS5 only");
|
||||
assert.equal(status.rerank.reason, "rerank disabled");
|
||||
|
||||
// Guard: no leftover Portuguese in any surfaced reason string.
|
||||
const ptWords = [
|
||||
"não",
|
||||
"disponível",
|
||||
"configurado",
|
||||
"desabilitado",
|
||||
"nenhuma",
|
||||
"desconhecida",
|
||||
"habilitado",
|
||||
"degradado",
|
||||
"selecionado",
|
||||
];
|
||||
for (const reason of [
|
||||
status.embedding.reason,
|
||||
status.vectorStore.reason,
|
||||
status.rerank.reason,
|
||||
]) {
|
||||
for (const w of ptWords) {
|
||||
assert.ok(!reason.includes(w), `reason "${reason}" still contains Portuguese "${w}"`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("engineStatus(): no throw when called multiple times", async () => {
|
||||
core.getDbInstance();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user