From 8d569e8fea8c08c0edb3f8a3ad876af6fd3397dc Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Tue, 9 Jun 2026 15:01:36 -0300 Subject: [PATCH] fix(types): restore clean typecheck:core for v3.8.18 release gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - getPendingRequests() typed to real shape (was widened to object) → fixes unknown 'count' in the unified-requests view (#3401) - streamChunks log payload cast to its declared type (callLogs.ts) - preScreenTargets aligned to canonical IsModelAvailable signature (#3169), Promise.resolve-normalized so .catch never hits a bare boolean All 5 gates green: lint(0 err) + typecheck:core + cycles + docs-all + unit + vitest(146). --- CHANGELOG.md | 5 +++++ open-sse/services/combo.ts | 8 ++++++-- src/lib/usage/callLogs.ts | 4 +++- src/lib/usage/usageHistory.ts | 7 +++++-- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75d355b627..c9a7e53ad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - **fix(executor):** Llama / OpenAI-compat base URL normalization — a `baseURL` without a path (e.g. `llama.example.foo`) or with a non-`/v1` path (e.g. `bar.example.com/foo`) now correctly gets `/v1/chat/completions` appended, fixing the 404 on message sends while `GET /model` still worked. ([#3519](https://github.com/diegosouzapw/OmniRoute/pull/3519) — thanks @hartmark) - **fix(sse):** empty-choices chunks without usage are dropped instead of injecting retry text — a streamed chunk carrying an empty `choices` array and no `usage` is now silently skipped rather than emitting placeholder retry text into the stream, eliminating spurious content for clients that send such keepalive-style frames. ([#3513](https://github.com/diegosouzapw/OmniRoute/pull/3513) — thanks @diegosouzapw) +- **fix(types):** restored a clean `typecheck:core` — typed `getPendingRequests()` to its real shape (`Record>`) so the unified-requests view (#3401) no longer treats pending counts as `unknown`, cast the `streamChunks` log payload to its declared type, and aligned `preScreenTargets` (#3169) to the canonical `IsModelAvailable` signature (sync-or-async, normalized via `Promise.resolve`). (thanks @diegosouzapw) - **fix(opencode-plugin):** repaired the corrupted `index.ts` that broke the npm `publish-opencode-plugin` build (introduced by the #3435 branch) — removed two duplicated code blocks (apiFormat + debug-logging), dropped the local `normaliseFreeLabel` superseded by the `naming.ts` extraction, fixed an undefined `sdkBaseURL` reference, declared the missing `startupDebug` / `logLevel` feature-schema fields, and fixed `shortProviderLabel` dropping the prefix on a long displayName with no alias. Plugin now builds (DTS clean) with all 254 tests green. ([#3435](https://github.com/diegosouzapw/OmniRoute/pull/3435) — thanks @diegosouzapw) - **fix(catalog):** Codex CLI model-catalog refresh no longer errors — `GET /v1/models` now returns a top-level `models: []` array for Codex clients (detected via the `originator` / `user-agent` = `codex_*` headers it sends on `GET /v1/models?client_version=...`), so `codex_models_manager` stops failing to decode the OpenAI-standard response and no longer logs `failed to refresh available models` on every startup. The array is intentionally empty: Codex replaces its built-in per-model agent prompt (`base_instructions`, ~21k chars) with whatever a populated entry carries for the selected model, so emitting our catalog would break Codex's agent behaviour — an empty list keeps Codex on its built-in model info (same inference as before, minus the error). Non-Codex OpenAI clients receive the unchanged `{object,data}` response. ([#3481](https://github.com/diegosouzapw/OmniRoute/pull/3481) — thanks @diegosouzapw) - **fix(provider):** Cursor's Responses-API-shaped bodies on `/chat/completions` are detected and handled — a body with `input` but no `messages` is now classified as `openai-responses` (instead of forcing `openai` and building from undefined `messages` → upstream 400); standard OpenAI clients are unaffected by the `messages===undefined` guard. ([#3490](https://github.com/diegosouzapw/OmniRoute/pull/3490) — thanks @borodulin) @@ -30,6 +31,10 @@ - **feat(docs):** doc-accuracy gate — new `npm run check:fabricated-docs` (`scripts/check/check-fabricated-docs.mjs`) indexes the codebase (api routes, env vars, CLI commands) and flags API-path/env-var/CLI/hook/file-ref claims in `docs/**` + `AGENTS.md` that don't exist in source (soft-fail by default, `--strict` for CI; wired into `check:docs-all`). Also refreshes the AGENTS.md live counts against source. ([#3510](https://github.com/diegosouzapw/OmniRoute/pull/3510) — thanks @oyi77) - **chore:** ignore local quality reports and prompt artifacts (`quality-metrics.json`, `PLANO-/RELATORIO-QUALITY-GATES.md`, stray prompt `.txt` files) so they no longer surface in `git status`. (thanks @diegosouzapw) +### 🔒 Security + +- **fix(opencode-plugin):** bounded the regex quantifiers in `normaliseFreeLabel` to close a polynomial-ReDoS (CodeQL `js/polynomial-redos`) — an unbounded `\s*` before an anchored `\s*$` allowed O(n²) backtracking on attacker-influenced provider/model display names; bounded to `{0,8}`/`{1,8}`. (thanks @diegosouzapw) + --- ## [3.8.17] — 2026-06-09 diff --git a/open-sse/services/combo.ts b/open-sse/services/combo.ts index 15193ee2ce..ed78df440a 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -1839,7 +1839,7 @@ type PreScreenResult = { profile: ProviderProfile | null; available: boolean }; export async function preScreenTargets( targets: ResolvedComboTarget[], - isModelAvailable?: ((model: string, target: ResolvedComboTarget) => Promise) | null + isModelAvailable?: IsModelAvailable | null ): Promise> { if (targets.length === 0) { return new Map(); @@ -1858,7 +1858,11 @@ export async function preScreenTargets( let available = true; if (isModelAvailable) { - available = await isModelAvailable(target.modelStr, target).catch(() => true); + // IsModelAvailable may return a sync boolean or a Promise; Promise.resolve + // normalizes both so the .catch() never runs against a bare boolean. + available = await Promise.resolve(isModelAvailable(target.modelStr, target)).catch( + () => true + ); } return { key: target.executionKey, result: { profile, available } }; } diff --git a/src/lib/usage/callLogs.ts b/src/lib/usage/callLogs.ts index f74db2fe7d..e0096a29eb 100644 --- a/src/lib/usage/callLogs.ts +++ b/src/lib/usage/callLogs.ts @@ -181,7 +181,9 @@ function protectPipelinePayloads(payloads: unknown): RequestPipelinePayloads | n ) ); if (Object.keys(compacted).length > 0) { - protectedPayloads.streamChunks = protectPayloadForLog(compacted); + protectedPayloads.streamChunks = protectPayloadForLog( + compacted + ) as RequestPipelinePayloads["streamChunks"]; } continue; } diff --git a/src/lib/usage/usageHistory.ts b/src/lib/usage/usageHistory.ts index 5a1a41c3a7..bea7f91db6 100644 --- a/src/lib/usage/usageHistory.ts +++ b/src/lib/usage/usageHistory.ts @@ -441,9 +441,12 @@ export function updatePendingRequestStreamChunks( /** * Get the pending requests state (for usageStats). - * @returns {{ byModel: Object, byAccount: Object }} + * @returns {{ byModel: Record, byAccount: Record> }} */ -export function getPendingRequests(): { byModel: object; byAccount: object } { +export function getPendingRequests(): { + byModel: Record; + byAccount: Record>; +} { return pendingRequests; }