mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-19 21:52:21 +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>
63 lines
2.4 KiB
TypeScript
63 lines
2.4 KiB
TypeScript
// Fields that, when literally named in an upstream 400 body, are safe to strip and
|
|
// retry once (FCC NIM-style recovery). Mirrors the existing context_management 400
|
|
// fallback in base.ts, generalized to these OpenAI-compat / NIM reasoning fields.
|
|
// `context_management` (9router#1468): Claude Code sends it top-level; strict
|
|
// anthropic-compatible gateways 400 with "context_management: Extra inputs are not
|
|
// permitted". The dedicated base.ts fallback only fires when OmniRoute's own
|
|
// contextEditing feature is enabled, so a client-sent field passed through
|
|
// untouched when the feature is off — this generic strip covers that case.
|
|
export const KNOWN_OFFENDING_FIELDS: readonly string[] = [
|
|
"reasoning_budget",
|
|
"chat_template",
|
|
"reasoning_content",
|
|
"context_management",
|
|
];
|
|
|
|
/** Return the first known-offending field literally named in a 400 body, or null. */
|
|
export function findOffendingField(bodyText: string): string | null {
|
|
if (typeof bodyText !== "string" || !bodyText) return null;
|
|
for (const field of KNOWN_OFFENDING_FIELDS) {
|
|
if (bodyText.includes(field)) return field;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Regex to extract an unsupported parameter name from upstream 400 error text.
|
|
* Matches:
|
|
* - "Unsupported parameter(s): thinking"
|
|
* - "Unsupported parameter: max_tokens"
|
|
* - "Unsupported parameter 'reasoning_budget'"
|
|
*/
|
|
export const UNSUPPORTED_PARAM_RE =
|
|
/unsupported\s+parameter\w*(?:\s*\(s\))?[:\s]+["'`]?(\w+)["'`]?/i;
|
|
|
|
/**
|
|
* Extract a single unsupported parameter name from a 400 error body,
|
|
* or null if the error does not match the known pattern.
|
|
*/
|
|
export function detectUnsupportedParam(bodyText: string): string | null {
|
|
if (typeof bodyText !== "string" || !bodyText) return null;
|
|
const match = UNSUPPORTED_PARAM_RE.exec(bodyText);
|
|
return match?.[1] ?? null;
|
|
}
|
|
|
|
/** Immutably drop request fields Groq rejects with a 400. */
|
|
export function stripGroqUnsupportedFields<T extends Record<string, unknown>>(body: T): T {
|
|
if (!body || typeof body !== "object") return body;
|
|
const next: Record<string, unknown> = { ...body };
|
|
delete next.logprobs;
|
|
delete next.logit_bias;
|
|
delete next.top_logprobs;
|
|
if (Array.isArray(next.messages)) {
|
|
next.messages = next.messages.map((m) => {
|
|
if (m && typeof m === "object" && "name" in m) {
|
|
const { name: _name, ...rest } = m as Record<string, unknown>;
|
|
return rest;
|
|
}
|
|
return m;
|
|
});
|
|
}
|
|
return next as T;
|
|
}
|