- Move !enforceScopes guard before MCP_TOOL_MAP lookup in evaluateToolScopes()
- Add inlineScopes parameter for dynamic tool scope resolution
- Add scopes to all 33 dynamic tool definitions across 5 tool files
- Wire toolDef.scopes through withScopeEnforcement in server.ts
- Preserves existing behavior: tool_definition_missing returned when
enforceScopes=true and no scopes found anywhere
The agy provider was registered in providerRegistry.ts with
executor: "antigravity" but the executor map in executors/index.ts
only had an "antigravity" entry. getExecutor("agy") fell through to
DefaultExecutor, which returned undefined for baseUrl (agy only has
baseUrls), causing fetch(undefined) → TypeError: Cannot read properties
of undefined (reading 'toString').
Closesdiegosouzapw/OmniRoute#2932
5 new test files covering all 13 changed production files:
- estimateSizeFast.test.ts: 16 tests for fast size estimator (circular ref
protection, early exit, nested structures, Map safety)
- eviction-guards-apiKeyRotator.test.ts: 5 tests for Map eviction guards
(!has() check prevents evicting existing keys on update)
- eviction-guards-codexQuotaFetcher.test.ts: 4 tests for connectionRegistry
and quotaCache eviction guards
- rateLimitManager-idle-eviction.test.ts: 6 tests for idle limiter cleanup,
limiterLastUsed tracking, and shutdown behavior
- registry-direct-exports.test.ts: 20 tests verifying all 8 registries export
plain objects (no Proxy traps, no lazy getters, mutable entries)
Extract estimateSizeFast/isSmallEnoughForSemanticCache into standalone
open-sse/utils/estimateSize.ts to make them testable without importing
the entire chatCore.ts dependency tree.
- estimateSizeFast: add WeakSet cycle detection to prevent infinite
loop on circular object references
- trace(): wrap JSON.stringify(extra) in try-catch to handle BigInt,
circular refs, or other non-serializable values gracefully
- Registry API change (Comment 3): verified all callers already use
new getter functions — no broken call sites
- Add !has(key) guard before eviction to avoid evicting entries
that are about to be updated (combo.ts, apiKeyRotator.ts,
codexQuotaFetcher.ts)
- Use optional chaining for provider?.toUpperCase() null safety
- Replace Object.values() with for-in in estimateSizeFast hot path
Root cause: model string provider prefix (e.g. "xiaomi" from
"xiaomi/mimo-v2-flash") differs from the credential DB provider ID
(e.g. "opengate") when a combo target has a custom providerId. The
pre-selection and execution flows both looked up credentials using the
model-inferred provider, which didn't match any DB entry.
Fix A (chat.ts): checkModelAvailable now uses target.providerId when
available, falling back to modelInfo.provider.
Fix B (chat.ts): handleSingleModelChat now accepts runtimeOptions.providerId
and preferentially uses it for credential lookup instead of re-resolving
the provider from the model string.
Fix C (model.ts): add "xiaomi" alias for "xiaomi-mimo" so direct
(non-combo) model requests to xiaomi/mimo-* also resolve correctly.
The CPA executor now cloaks non-Claude-Code tool names (not just mcp_*), so the
prior "does not rewrite non-mcp_ tool names" assertion no longer holds:
my_tool is aliased to MyTool and restored on the response via _toolNameMap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses confirmed findings from an adversarial review of the prior commits:
- schema sanitizer: a truncation placeholder in a SCALAR annotation keyword
(description/title/pattern/format) was coerced to {}, which is itself invalid
draft-2020-12 and re-triggered the exact "input_schema is invalid" 400 the
sanitizer exists to prevent. Placeholders are now only coerced to {} in
subschema-expecting positions; scalar keywords are left untouched.
- schema sanitizer: numeric-string coercion is folded into
stripInvalidSchemaConstructs so it also covers contains / propertyNames /
additionalItems (which coerceSchemaNumericFields never visited).
- schema sanitizer: stop stripping the valid `default` keyword on the Claude
native/passthrough surface (the #1782 default-strip is a translator concern;
tool schemas here were previously forwarded verbatim). sanitizeClaudeToolSchema
is now a single stripInvalidSchemaConstructs pass.
- tool-name cloak: consult TOOL_RENAME_MAP / EXTRA_TOOL_RENAME_MAP before the
generic PascalCase fallback, so the CLIProxyAPI path uses the established
fingerprint-evasion aliases (subagents->SubDispatch, session_status->CheckStatus,
webfetch->WebFetch, ...) identically to the native path instead of weaker
first-letter casing.
- kill-switch: CLAUDE_DISABLE_TOOL_NAME_CLOAK is now honoured inside
cloakThirdPartyToolNames, so BOTH the native and CLIProxyAPI executor paths
respect it (previously only base.ts did); .env.example + ENVIRONMENT.md updated.
Regression tests added for each. Verified end-to-end through the live CPA path:
mixture_of_agents, subagents, and a tool carrying placeholder descriptions and
`default` values all return 200 with original names restored on the response.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The native Claude OAuth guard in executors/base.ts is bypassed when
`upstream_proxy_config.mode = cliproxyapi` routes the request through the
CliproxyAPI executor — it has its own execute()/transformRequest() and never
reaches BaseExecutor.execute(), so the cloak/sanitizer never ran for that
(common) deployment. Wire the same guards into
CliproxyapiExecutor.transformRequest (Anthropic-shape branch), composing with
the existing bisected `mcp_*` reserved-namespace rewrite:
- sanitizeClaudeToolSchemas() on transformed.tools.
- cloakThirdPartyToolNames() with skip = mcp-reserved, so applyMcpToolNameRewrite
keeps authority over `mcp_*` (its bisected `Mcp_X` form) and the two reverse
maps stay disjoint / single-hop. Both merge into the non-enumerable
_toolNameMap the response stream already uses to restore the caller's names.
cloakThirdPartyToolNames is now non-mutating (clones changed entries) to respect
transformRequest's no-input-mutation contract, and takes an optional `skip`
predicate.
Verified end-to-end through the live CPA path: a real ~100-tool harness payload
that returned the "out of extra usage" placeholder now returns 200 with original
tool names restored on the response stream; `mcp_*` tools and genuine PascalCase
Claude Code tools are unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up commit on PR #2943 review:
- Preserve boolean schemas in `sanitizeClaudeToolSchemas` (Gemini Code Assist,
high severity). `additionalProperties: false` is the canonical JSON Schema
lock-down for object tools; the previous coercion silently turned it into the
permissive `{}`, which would invite models to hallucinate extra arguments
during tool calling. Same rule now applies to per-property boolean schemas
under `properties`. Placeholder strings still get the permissive `{}` slot —
booleans get preserved verbatim.
- Defensive null guards in `cloakThirdPartyToolNames` for `tools[]` and
`messages[]` entries that might be `null`/`undefined`. Prevents a runtime
`TypeError` if a malformed payload reaches the cloak.
- Document `CLAUDE_DISABLE_TOOL_NAME_CLOAK` in `.env.example` and
`docs/reference/ENVIRONMENT.md` (env/docs contract was failing in CI).
- Regression tests covering all of the above (5 boolean preservation cases,
2 null-tolerance cases).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract 3 high-value CPU/RAM optimizations from perf branch:
1. estimateSizeFast() — fast object-tree size estimator replacing
JSON.stringify().length in isSmallEnoughForSemanticCache(). Walks
object tree with a stack, zero string allocation, early exit at 256KB.
2. Consolidate settings reads — move getCachedSettings() to a single
early read in handleChatCore(), eliminating a redundant second read
200 lines later. Also removes the isDetailedLoggingEnabled() wrapper
call (reads settings internally) in favor of direct field check.
3. Registry Proxy→direct export — convert 8 registries from lazy
Proxy+getOrCreate pattern to simple exported const objects. Eliminates
Proxy trap overhead on every provider property access during routing.
Affected: audio, embedding, image, moderation, music, rerank, search,
video registries (-451 lines of Proxy boilerplate).
These changes are independent of the CPU leak fix (limiter eviction)
and complement it by reducing per-request CPU overhead.
Native Claude OAuth (claude->claude passthrough) forwards client tool
definitions verbatim. Anthropic's first-party Messages API then rejects:
- invalid tool input_schemas (deep-truncation placeholders such as
`enum: "[MaxDepth]"`, or index-keyed objects where arrays are required), and
- tool names it fingerprints as a third-party agent harness (specific
blacklisted names like `mixture_of_agents`, or a large enough set of
recognizable snake_case agent tool names),
both surfaced as a misleading `400 You're out of extra usage` placeholder
(the SSE stream is refused — not a real billing event). The same request
succeeds on translator-backed providers (OpenAI/Codex), which already sanitize
and re-shape tool payloads — so the gap is specific to the native passthrough.
Adds the missing guards on the native Claude OAuth path (executors/base.ts):
- sanitizeClaudeToolSchemas(): coerce/drop invalid draft-2020-12 constructs
(non-array enum/required/anyOf/..., placeholder schema slots -> {}).
- cloakThirdPartyToolNames(): deterministically alias non-Claude-Code tool
names (Claude Code canonical mapping where one exists, else PascalCase),
tracked in the existing per-request _toolNameMap so remapToolNamesInResponse
restores the caller's original names. Opt out via
CLAUDE_DISABLE_TOOL_NAME_CLOAK=true.
Genuine Claude Code tool names (PascalCase) and already-valid schemas are
left untouched, so existing first-party traffic is unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root cause: Bottleneck rate limiter instances in rateLimitManager accumulate
without cleanup. Each instance runs an internal heartbeat setInterval every
250ms. Under heavy load with many provider:connection:model combinations,
hundreds of limiters accumulate causing CPU to grow ~0.1%/min until server
collapse (~2% after 5 minutes of intensive use).
Changes:
- rateLimitManager: Add idle limiter eviction in watchdogTick() using the
previously defined but unused INACTIVE_LIMITER_MS threshold. Populate
limiterLastUsed on every getLimiter() call. Clean up all 3 Maps
(limiters, lastDispatchAt, limiterLastUsed) consistently.
- combo.ts: Add size-based FIFO eviction to rrCounters, resetAwareConnectionCache,
and resetAwareQuotaCache Maps. Convert per-target log.info calls in combo
execution loops to log.debug?. to reduce serialization overhead.
- chatCore.ts: Fix double-serialization in estimateTokens(JSON.stringify(x))
calls (estimateTokens already handles objects). Make trace() conditional
on OMNIRROUTE_TRACE/DEBUG env vars. Make per-request usage logging conditional.
- apiKeyRotator.ts: Add eviction guards to _keyHealth and _connectionExtraKeys
Maps (MAX 500 entries each). Ensure removeConnectionIndex cleans all 3 Maps.
- codexQuotaFetcher.ts: Add eviction guard to connectionRegistry and quotaCache
Maps (MAX 200 entries each).
Remove the AuditLogTab from the dashboard logs page now that audit logs
live under the dedicated /dashboard/audit route. Update integration wiring
expectations and add metadata frontmatter to studio framework docs.
Keep existing object-argument cleanup behavior, but avoid parsing and stripping arbitrary JSON-string arguments for unrelated tools where empty strings or arrays may be valid payloads. Add regression coverage for non-Read and non-object Read arguments.
Only translate Claude Code web_search_YYYYMMDD server tools to native Responses web_search when the final target is OpenAI Responses. Keep the Chat Completions target on function-tool shape and cover the full translateRequest path.
Translate Claude Code web_search_YYYYMMDD server tools to the native OpenAI Responses web_search tool and preserve filters/location. Convert forced Claude tool_choice for web_search to the native Responses tool choice while leaving ordinary custom functions unchanged.
Closes#2936