mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 20:32:20 +03:00
* fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (#6605) fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148). Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148). Supersedes #6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.) * deps: bump the development group across 1 directory with 6 updates (#6588) deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (#6605). * fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620) fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump. undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green. Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.) * feat(db): add provider param filter config store (key_value namespace) Add paramFilters.ts module for CRUD against provider_param_filters namespace in the key_value table, with in-memory cache + generation counter invalidation. Supports denylist/allowlist per provider and per model, plus auto-learn flag. Migration 118 documents the namespace (no schema change). Issue: #6625 * feat(proxy): add detectUnsupportedParam regex for auto-learning Add UNSUPPORTED_PARAM_RE and detectUnsupportedParam() to extract the offending parameter name from upstream 400 error messages like 'Unsupported parameter(s): thinking'. Issue: #6625 * feat(proxy): extend stripUnsupportedParams with config-driven denylist/allowlist Add applyConfigFilters() called after hardcoded STRIP_RULES in stripUnsupportedParams(). Config-driven rules (DB-backed via paramFilters.ts) support provider-level and model-level: 1. Provider denylist (delete body[key]) 2. Model denylist (delete body[key]) 3. Provider allowlist (restore from pre-strip snapshot) 4. Model allowlist (restore from pre-strip snapshot) Allowlist only restores keys the client actually sent — never introduces new params. Issue: #6625 * feat(proxy): wire auto-learn of unsupported params into 400-downgrade loop When a provider returns 400 with 'Unsupported parameter: X' and the provider config has autoLearn enabled, auto-detect the param name via detectUnsupportedParam(), persist it to the provider's block list via addParamToBlocklist(), then strip and retry. Issue: #6625 * test: add tests for provider param filter denylist/allowlist/auto-learn Three new test files: - param-filters-apply.test.ts — hardcoded rules regression + direct applyConfigFilters tests (no DB dependency) - param-filters-db.test.ts — CRUD against key_value, cache invalidation, full filter pipeline (DB-backed config → stripUnsupportedParams), 16 tests in isolated temp DB - param-filters-auto-learn.test.ts — UNSUPPORTED_PARAM_RE regex matching and detectUnsupportedParam edge cases All existing tests unchanged and passing. Issue: #6625 * feat(proxy): add global auto-learn flag for unsupported params Add isAutoLearnGloballyEnabled() and setGlobalAutoLearnEnabled() to paramFilters.ts. The global flag (stored as key __global__ in the provider_param_filters namespace) acts as a master switch: when enabled, ALL providers auto-learn unsupported params from 400 errors. In base.ts, the auto-learn check now evaluates: shouldAutoLearn = isAutoLearnGloballyEnabled() || perProviderConfig?.autoLearn Global flag defaults to false (opt-in). Tests cover enable/disable/ default/no-interference-with-per-provider-config. Issue: #6625 * fix: apply PR#6649 review feedback — model-scoped auto-learn and precedence order Fixes from gemini-code-assist[bot] review: - HIGH: Auto-learn now scoped to the specific model that triggered the 400 (addParamToBlocklist(this.provider, autoLearned, model)) instead of adding to the provider-level blocklist globally - HIGH: Reordered applyConfigFilters so model-level operations run AFTER provider-level operations (model denylist → model allowlist override provider allowlist → provider denylist) - MEDIUM: Include model name in auto-learn log message Adds regression test verifying model-level denylist beats provider-level allowlist. Issue: #6625 PR: #6649 * feat(ui): add provider-level param filter section to detail page Add ProviderParamFilterSection component rendered on each provider detail page, backed by GET|PUT|DELETE /api/providers/[id]/param-filters. UI allows operators to configure: - Blocked params (comma-separated, stripped from outgoing requests) - Allowed params (comma-separated, re-added after denylist stripping) - Auto-learn toggle (per-provider, enables auto-learning from 400 errors) Wired into ProviderDetailPageClient.tsx between the Playground panel and the Modals section. Issue: #6625 PR: #6649 * feat(ui): add model-level param filter fields in compat popover Extend ModelCompatPopover with Blocked params and Allowed params text inputs for model-level denylist/allowlist overrides. Model-specific block/allow data is persisted via the param-filters API endpoint (PUT /api/providers/:id/param-filters) with the model scope under the models key. Both ModelRow and PassthroughModelRow now pass providerId and modelId to the popover. Issue: #6625 PR: #6649 * chore: gitignore .claude-flow/ * fix(param-filters): review follow-ups — auth gate, error sanitization, Zod body validation, typecheck, file-size, i18n keys Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(param-filters): drop unrelated main-drift from the fork branch (deps/electron/proxy files belong to #6620/#6605/#6588, not this PR) * refactor(param-filters): split oversized functions — keep complexity gate at baseline Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(param-filters): decompose config parser helpers — keep cognitive-complexity gate at baseline Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(changelog): restore sibling #6648 bullet eaten by merge auto-resolve + re-insert #6649 entry Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
69 lines
2.8 KiB
TypeScript
69 lines
2.8 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
// This file tests the regex/matching logic of detectUnsupportedParam and the
|
|
// UNSUPPORTED_PARAM_RE constant. These are pure functions with no DB dependency.
|
|
// DB-backed persistence tests (addParamToBlocklist) live in param-filters-db.test.ts.
|
|
import {
|
|
UNSUPPORTED_PARAM_RE,
|
|
detectUnsupportedParam,
|
|
} from "../../open-sse/config/providerFieldStrips.ts";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// UNSUPPORTED_PARAM_RE
|
|
// ---------------------------------------------------------------------------
|
|
|
|
test("UNSUPPORTED_PARAM_RE matches NIM-style 'Unsupported parameter(s): thinking'", () => {
|
|
const m = UNSUPPORTED_PARAM_RE.exec("Unsupported parameter(s): thinking");
|
|
assert.notEqual(m, null);
|
|
assert.equal(m![1], "thinking");
|
|
});
|
|
|
|
test("UNSUPPORTED_PARAM_RE matches 'Unsupported parameter: max_tokens'", () => {
|
|
const m = UNSUPPORTED_PARAM_RE.exec("Unsupported parameter: max_tokens");
|
|
assert.notEqual(m, null);
|
|
assert.equal(m![1], "max_tokens");
|
|
});
|
|
|
|
test("UNSUPPORTED_PARAM_RE matches 'Unsupported parameter 'reasoning_budget''", () => {
|
|
const m = UNSUPPORTED_PARAM_RE.exec("Unsupported parameter 'reasoning_budget'");
|
|
assert.notEqual(m, null);
|
|
assert.equal(m![1], "reasoning_budget");
|
|
});
|
|
|
|
test("UNSUPPORTED_PARAM_RE matches case-insensitively", () => {
|
|
const m = UNSUPPORTED_PARAM_RE.exec("unsupported parameter(s): thinking");
|
|
assert.notEqual(m, null);
|
|
assert.equal(m![1], "thinking");
|
|
});
|
|
|
|
test("UNSUPPORTED_PARAM_RE does not match unrelated error text", () => {
|
|
assert.equal(UNSUPPORTED_PARAM_RE.exec("rate limit exceeded"), null);
|
|
assert.equal(UNSUPPORTED_PARAM_RE.exec("Internal server error"), null);
|
|
assert.equal(UNSUPPORTED_PARAM_RE.exec(""), null);
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// detectUnsupportedParam
|
|
// ---------------------------------------------------------------------------
|
|
|
|
test("detectUnsupportedParam extracts param name from NIM-style errors", () => {
|
|
assert.equal(detectUnsupportedParam("Unsupported parameter(s): thinking"), "thinking");
|
|
assert.equal(
|
|
detectUnsupportedParam("Unsupported parameter: reasoning_budget"),
|
|
"reasoning_budget"
|
|
);
|
|
assert.equal(detectUnsupportedParam("Unsupported parameter 'max_tokens'"), "max_tokens");
|
|
});
|
|
|
|
test("detectUnsupportedParam returns null for non-matching text", () => {
|
|
assert.equal(detectUnsupportedParam("all good"), null);
|
|
assert.equal(detectUnsupportedParam(""), null);
|
|
assert.equal(detectUnsupportedParam("400 Bad Request"), null);
|
|
});
|
|
|
|
test("detectUnsupportedParam returns null for null/undefined", () => {
|
|
assert.equal(detectUnsupportedParam(null as unknown as string), null);
|
|
assert.equal(detectUnsupportedParam(undefined as unknown as string), null);
|
|
});
|