mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-19 05:32:19 +03:00
* feat(admission): per-target lane-aware probes for combo/fusion fan-out (#9654 Wave 2)
Combo and fusion fan out N targets without ever consulting the adaptive-admission
layer: the parent request holds one lease, but each fan-out target is dispatched
unconditionally. With virtual lanes enabled (OMNIROUTE_CHAT_VIRTUAL_LANES=1), a
connection whose lane queue is full now SKIPS additional fan-out targets instead
of piling more queued work onto an already-congested session.
Adds PerTargetAdmissionHook (admission/types.ts) + createPerTargetAdmissionHook
factory (chatAdmission.ts): strictly non-blocking (maxWaitMs 0 - skip, never
queue), a no-op when virtual lanes are off, keyed to the parent tenantKey, and
release-on-admit so the probe is a capacity gate, not a hold.
Threaded through every parallel fan-out path:
- priority/weighted executeTarget + round-robin skip chains (combo.ts)
- fusion panel before fan-out (fusion.ts), judge fallback prefers survivors
- chaos parallel panel (autoCombo/chaosEngine.ts)
- tryFusionDispatch / tryRuntimeUnitDispatch / buildBaseOptions (dispatchPrelude.ts)
- chat.ts primary + safety-net redirect call sites
Snapshot exposes virtualLanes so the no-op gate is cheap and honest.
Tests: tests/unit/combo-lane-awareness-9654.test.ts (10 tests) - factory
semantics, priority/RR skip, fusion panel drop + all-skipped 503, no-hook
backward-compat baseline.
* feat(flags): activation UX - env-wins adaptive virtual-lanes flag + env docs (#9654 Wave 2)
U7: make adaptive virtual admission lanes discoverable + activatable.
- New OMNIROUTE_CHAT_VIRTUAL_LANES feature flag (boolean/runtime/requiresRestart) in featureFlagDefinitions + en.json i18n key.
- lib/admissionVirtualLanes.ts: env-wins resolver (env > DB > default) + boot warm folding a DB-sourced override into the process-global runtime env via reloadAdaptiveAdmissionRuntime(options.env) - no process.env mutation, no open-sse changes. Env still wins; DB toggle gates at next boot.
- GET /api/settings/feature-flags special-cases the flag to report the gate true source (ccDiscoveryAliases precedent); flagPayload helper dedupes the payload shape.
- Wire the warm into instrumentation-node registerNodejs (non-fatal, DB-ready).
- Document the master switch in .env.example + ENVIRONMENT.md with the system-1/system-2 distinction; zero new env-doc-sync drift.
- 11 new tests (resolver precedence + warm); 60/60 across feature-flag suites; typecheck core clean; ESLint + doc gates green.
* feat(mcp): surface adaptive admission lane data in omniroute_get_health (#9654 Wave 2)
U8: make adaptive virtual-lane admission visible to agents via the MCP health tool. handleGetHealth now surfaces a curated adaptiveAdmission block from the health payload (which already carried the runtime snapshot but was dropping it): virtualLanes/pressure/utilization/laneCount/laneQueuedCount/laneQueuedCost, laneTenants capped at top-10 by queued cost, admitted/rejected/wouldReject counts, shutdown. Block omitted entirely when the health endpoint reports none.
isLaneFlagOn mirrors the runtime 1|true convention so a string serialization can never invert a boolean lane report. getHealthOutput schema extended with the matching optional shape; tool description updated.
4 new dispatch tests (full block, top-10 cap/order, omission, defensive coercion of string flags + malformed lane entries) - 22/22 in essentialTools.test.ts. README: Adaptive Admission Lane Data table + Skills & Tool Navigability audit (29/43 schema entries covered, 14 undocumented, tool_search keyword runtime discovery, full catalog in docs/frameworks/MCP-SERVER.md).
No new lint errors (4 pre-existing in server.ts), typecheck core clean, doc counts + fabricated-docs gates green.
* docs: add changelog entry for #9654 Wave 2 (#10039)
* fix(codeql): suppress js/insufficient-password-hash false positive in lane-key fingerprinting (#10039)
resolveSessionId sha256-hashes bearer/x-api-key/x-goog-api-key to derive a deterministic, non-reversible per-key lane-bucket ID for virtual admission lanes (#9654). This is not password storage or verification, so the rule is a false positive; suppress it inline (same house style as src/lib/sync/tokens.ts) to clear the codeqlAlerts ratchet (2 > baseline 1) that blocks #10039 and every PR against release/v3.8.50.
* docs(mcp): complete MCP server README tool reference (#10039)
The MCP server README covered only 29 of the 43 schema entries, listing the
remaining tools solely as a gap note with omniroute_tool_search as the runtime
fallback. Add tool-reference tables for the agent-skills trio, oneproxy trio,
web_fetch/web_search, tool_search, create_combo, set_routing_strategy,
pick_fastest_model, sync_pricing, and db_health_check so the README covers the
full schemas catalog, and fold the coverage note into the tool_search discovery
paragraph.
* fix(chat): drop unused correlationId from safety-net combo redirect (#10039)
handleComboChat's HandleComboChatOptions has no correlationId member and
the combo pipeline never consumes it; the property was copied from the
handleSingleModelChat options shape by accident and introduced a new
TS2353 under the open-sse workspace typecheck gate.
* fix(i18n): translate featureFlagChatVirtualLanesEnabledDescription into 42 locales (#10039)
en.json gained the flag description in this PR but the locale catalogs
were never mirrored, failing the pt-BR key-parity (#6695) and vi
completeness gates. Adds a real translation to every locale, keeping the
zh-CN/zh-TW glossary canonical terms (提供者/儀表板) and no ICU drift.
* chore(quality): ratchet open-sse-typecheck baseline down (#10039)
The Wave 2 admission refactor removed 66 baselined open-sse type errors;
re-freeze the baseline so the gate pins the new, tighter state.
* docs: resync provider reference to 341 and CLI tools to 34
The release branch gained an 11th no-auth provider (freeaiapikey registry
resync, #10233) and a 26th CLI Code tool without regenerating the
auto-generated docs, leaving every PR against release/v3.8.50 failing the
Docs Gates strict validator (code 341 vs doc 340, CLI 34 vs "33 tools").
Regenerate docs/reference/PROVIDER_REFERENCE.md and sync the provider/tool
counts across README.md, AGENTS.md, llm.txt plus 42 i18n mirrors,
package.json description, and the four diagram SVGs.
* fix(tests): align count expectations with live catalogs (pre-existing release drift)
Release/v3.8.50 currently fails five gates on its own tree; this PR inherits
them. Fix the stale expectations to match live code:
- feature-flags-settings: 48 -> 49 flags (Wave 2 adds OMNIROUTE_CHAT_VIRTUAL_LANES)
- cli-tools-schema / cli-catalog-counts: 33 -> 34 tools (zcode added; 26 code = 21 visible + 5 none)
- optional-transformers-dependency: onnxruntime-node ~1.24.3 -> ~1.27.0 (bump #10382)
- stryker.conf.json: register chatcore-header-drop-warn-dedupe-10315 test
- check-public-creds: freeze zcodeProtocol clientId false positive (client identifier, not a credential)
* fix(tests): follow release's onnxruntime-node revert to ~1.24.3
release/v3.8.50's #10543 pinned onnxruntime-node back to ~1.24.3 after
#10403's ~1.27.0 bump caused npm to nest a second native copy under
@huggingface/transformers and broke the Docker SONAME contract. This
PR's own drift-alignment commit (57b9c033) predates that revert and
still expected ~1.27.0; the 3-way merge did not flag it as a textual
conflict since only one side touched this exact line, but the merged
tree became internally inconsistent (package.json ~1.24.3 vs test
expecting ~1.27.0). Align the test with the now-canonical release
value.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(quality): dedupe stryker.conf.json chatcore-header-drop-warn-dedupe entry
The 3-way merge applied both sides' insertion of the same test-file entry
at different positions, producing a duplicate with broken indentation.
Adopted release's clean version of the file.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: Brandon Bennett <brandonbennett@macbookair.myfiosgateway.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: Brandon Bennett <branben@users.noreply.github.com>
216 lines
7.3 KiB
TypeScript
216 lines
7.3 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { z } from "zod";
|
|
import { isAuthenticated } from "@/shared/utils/apiAuth";
|
|
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
|
import {
|
|
FEATURE_FLAG_DEFINITIONS,
|
|
type FeatureFlagDefinition,
|
|
} from "@/shared/constants/featureFlagDefinitions";
|
|
import {
|
|
ADAPTIVE_VIRTUAL_LANES_FLAG_KEY,
|
|
resolveAdaptiveVirtualLanesFlag,
|
|
} from "@/lib/admissionVirtualLanes";
|
|
import {
|
|
getFeatureFlagOverrides,
|
|
setFeatureFlagOverride,
|
|
removeFeatureFlagOverride,
|
|
clearAllFeatureFlagOverrides,
|
|
} from "@/lib/db/featureFlags";
|
|
import { resolveAllFeatureFlags } from "@/shared/utils/featureFlags";
|
|
import { getCcAliasGlobalState } from "@/lib/db/ccDiscoveryAliases";
|
|
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
|
|
|
|
const ACTIVE_VALUES = new Set(["true", "1", "yes"]);
|
|
const CC_DISCOVERY_ALIASES_FLAG_KEY = "EXPOSE_CC_DISCOVERY_ALIASES";
|
|
|
|
function isActive(value: string): boolean {
|
|
return ACTIVE_VALUES.has(value);
|
|
}
|
|
|
|
/**
|
|
* Standard feature-flag payload shape for GET /api/settings/feature-flags.
|
|
* Flags whose gate resolves differently from the generic db-wins-over-env
|
|
* order pass their own effectiveValue/source (see the special cases below).
|
|
*/
|
|
function flagPayload(
|
|
definition: FeatureFlagDefinition,
|
|
effectiveValue: string,
|
|
source: "db" | "env" | "default"
|
|
) {
|
|
return {
|
|
key: definition.key,
|
|
label: definition.label,
|
|
description: definition.description,
|
|
category: definition.category,
|
|
type: definition.type,
|
|
enumValues: definition.enumValues ?? null,
|
|
defaultValue: definition.defaultValue,
|
|
effectiveValue,
|
|
source,
|
|
requiresRestart: definition.requiresRestart,
|
|
warningLevel: definition.warningLevel,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* GET /api/settings/feature-flags
|
|
* Returns all feature flags with their effective values and a summary.
|
|
*/
|
|
export async function GET(request: NextRequest) {
|
|
if (!(await isAuthenticated(request))) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
try {
|
|
const resolved = resolveAllFeatureFlags();
|
|
|
|
const flags = resolved.map(({ key, effectiveValue, source, definition }) => {
|
|
// Flags whose gate resolves with env-wins-over-db precedence (the
|
|
// opposite of resolveAllFeatureFlags' generic db-wins-over-env order)
|
|
// report the gate's own resolution so the dashboard never shows a source
|
|
// that doesn't match actual gate behavior.
|
|
if (key === CC_DISCOVERY_ALIASES_FLAG_KEY) {
|
|
const gateState = getCcAliasGlobalState();
|
|
return flagPayload(definition, gateState.enabled ? "true" : "false", gateState.source);
|
|
}
|
|
// #9654 U7: the adaptive virtual-lanes gate reads env at runtime
|
|
// construction — env wins over any DB override, and a DB override gates
|
|
// at next boot (see lib/admissionVirtualLanes.ts).
|
|
if (key === ADAPTIVE_VIRTUAL_LANES_FLAG_KEY) {
|
|
const state = resolveAdaptiveVirtualLanesFlag();
|
|
return flagPayload(definition, state.enabled ? "true" : "false", state.source);
|
|
}
|
|
return flagPayload(definition, effectiveValue, source);
|
|
});
|
|
|
|
const total = flags.length;
|
|
const active = flags.filter((f) => isActive(f.effectiveValue)).length;
|
|
const inactive = total - active;
|
|
const overriddenByDb = flags.filter((f) => f.source === "db").length;
|
|
const overriddenByEnv = flags.filter((f) => f.source === "env").length;
|
|
|
|
return NextResponse.json({
|
|
flags,
|
|
summary: { total, active, inactive, overriddenByDb, overriddenByEnv },
|
|
});
|
|
} catch (error) {
|
|
return NextResponse.json({ error: sanitizeErrorMessage(error) }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
const putFeatureFlagSchema = z.object({
|
|
key: z.string().min(1),
|
|
value: z.string().optional(),
|
|
});
|
|
|
|
/**
|
|
* PUT /api/settings/feature-flags
|
|
* Set or remove a feature flag override.
|
|
* Body: { key: string; value?: string }
|
|
* If value is omitted, the override is removed.
|
|
*/
|
|
export async function PUT(request: NextRequest) {
|
|
if (!(await isAuthenticated(request))) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
let rawBody: unknown;
|
|
try {
|
|
rawBody = await request.json();
|
|
} catch {
|
|
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
|
|
}
|
|
|
|
const validation = validateBody(putFeatureFlagSchema, rawBody);
|
|
if (isValidationFailure(validation)) {
|
|
return NextResponse.json({ error: validation.error }, { status: 400 });
|
|
}
|
|
|
|
const { key, value } = validation.data;
|
|
|
|
// Validate key against known definitions
|
|
const definition = FEATURE_FLAG_DEFINITIONS.find((d) => d.key === key);
|
|
if (!definition) {
|
|
return NextResponse.json({ error: `Unknown feature flag key: ${key}` }, { status: 400 });
|
|
}
|
|
|
|
// Validate enum values
|
|
if (value !== undefined && definition.type === "enum" && definition.enumValues) {
|
|
if (!definition.enumValues.includes(value)) {
|
|
return NextResponse.json(
|
|
{
|
|
error: `Invalid value "${value}" for enum flag ${key}. Allowed: ${definition.enumValues.join(", ")}`,
|
|
},
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
}
|
|
|
|
try {
|
|
// Capture previous state before modifying
|
|
const allFlagsBefore = resolveAllFeatureFlags();
|
|
const prevFlag = allFlagsBefore.find((f) => f.key === key);
|
|
const previousValue = prevFlag?.effectiveValue ?? definition.defaultValue;
|
|
const previousSource = prevFlag?.source ?? "default";
|
|
|
|
if (value === undefined) {
|
|
removeFeatureFlagOverride(key);
|
|
} else {
|
|
setFeatureFlagOverride(key, value);
|
|
}
|
|
|
|
// After write — get new effective value
|
|
const allFlagsAfter = resolveAllFeatureFlags();
|
|
const updatedFlag = allFlagsAfter.find((f) => f.key === key);
|
|
const newEffectiveValue = updatedFlag?.effectiveValue ?? definition.defaultValue;
|
|
const newSource = updatedFlag?.source ?? "default";
|
|
|
|
// Env-wins gates resolve with env > DB > default (the opposite of the
|
|
// generic db-wins helper above), so report their true resolution here too —
|
|
// the response must never tell an operator they enabled a gate that the env
|
|
// var still overrides (#9654 U7).
|
|
let reportedEffectiveValue = newEffectiveValue;
|
|
let reportedSource = newSource;
|
|
if (key === ADAPTIVE_VIRTUAL_LANES_FLAG_KEY) {
|
|
const state = resolveAdaptiveVirtualLanesFlag();
|
|
reportedEffectiveValue = state.enabled ? "true" : "false";
|
|
reportedSource = state.source;
|
|
}
|
|
|
|
return NextResponse.json({
|
|
key,
|
|
effectiveValue: reportedEffectiveValue,
|
|
source: reportedSource,
|
|
previousValue,
|
|
previousSource,
|
|
requiresRestart: definition.requiresRestart,
|
|
});
|
|
} catch (error) {
|
|
return NextResponse.json({ error: sanitizeErrorMessage(error) }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DELETE /api/settings/feature-flags
|
|
* Clear all feature flag overrides.
|
|
*/
|
|
export async function DELETE(request: NextRequest) {
|
|
if (!(await isAuthenticated(request))) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
try {
|
|
const overrides = getFeatureFlagOverrides();
|
|
const count = Object.keys(overrides).length;
|
|
|
|
clearAllFeatureFlagOverrides();
|
|
|
|
return NextResponse.json({
|
|
cleared: count,
|
|
message: `Cleared ${count} feature flag override${count !== 1 ? "s" : ""}`,
|
|
});
|
|
} catch (error) {
|
|
return NextResponse.json({ error: sanitizeErrorMessage(error) }, { status: 500 });
|
|
}
|
|
}
|