Repository navigation
Conversation
…rence#3587) (cheaper-inference#3588) Integrated into release/v3.8.21
…on) + cheaper-inference#3588 (combo reasoning buffer) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…drop shadow/guardrails doc-fiction (cheaper-inference#3496) (cheaper-inference#3602) Integrated into release/v3.8.21 — implements GET /api/guardrails + POST /api/guardrails/test, removes shadow/guardrails doc-fiction. TDD-validated (5/5) + check-docs-symbols/typecheck/eslint green.
Split-out PR C from cheaper-inference#3584. Isolates textual reasoning wrappers (<think>/<thinking>/<thought>/<internal_thought>, including malformed/open tags) into reasoning_content across both the non-streaming sanitizer and the Gemini streaming translator, with split-chunk buffering. Additive to the existing textual tool-call pipeline; does not touch the cheaper-inference#3569 native functionResponse path. Integrated into release/v3.8.21. Thanks @dhaern!
…nce#3603) Split-out PR A from cheaper-inference#3584. Normalizes the Antigravity/agy Gemini 3.5 Flash tier IDs to clean public names (gemini-3.5-flash-low/medium/high), maps them to the live upstream IDs at the executor boundary, and removes Antigravity from the global model resolver so the executor owns wire normalization. Maintainer follow-up: kept gemini-3.5-flash-preview as a hidden backward-compat alias routing to the High tier (so saved combos/configs keep working). Live-validated the tier set via the agy CLI catalog. Integrated into release/v3.8.21. Thanks @dhaern!
…s port 443 (cheaper-inference#3606) (cheaper-inference#3608) Integrated into release/v3.8.21 (cheaper-inference#3606)
…500 (cheaper-inference#3589) (cheaper-inference#3609) Integrated into release/v3.8.21 (cheaper-inference#3589)
…de-plugin (cheaper-inference#3419) (cheaper-inference#3613) Integrated into release/v3.8.21 (cheaper-inference#3419)
…ference#3604) Split-out PR B from cheaper-inference#3584. Normalizes Antigravity/agy provider quotas: prefers retrieveUserQuota for live consumption, falls back to fetchAvailableModels and local usage_history, sanitizes cached Provider Limits so retired upstream IDs are not re-exposed, and schedules a deduplicated post-usage refresh. Maintainer follow-up: decoupled the post-usage refresh via a lightweight usageEvents bus (usageHistory no longer dynamic-imports providerLimits) so it does not pull the executors/translator graph into the typecheck-core surface — typecheck:core stays at 0. Integrated into release/v3.8.21. Thanks @dhaern!
…ode (cheaper-inference#3331) (cheaper-inference#3614) Integrated into release/v3.8.21 (cheaper-inference#3331)
…aper-inference#3604 (provider quotas) + cheaper-inference#3605 (reasoning wrappers) Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
…nference#3594) Breaks the 12,882-line providers/[id]/page.tsx into 6 domain-focused files: - types.ts — all shared interfaces/types - utils.tsx — helper functions and utility components (ModelSourceBadge, CooldownTimer, ModelCompatPopover, etc.) - modelManagement.tsx — ModelRow, ModelVisibilityToolbar, PassthroughModelsSection, CustomModelsSection, CompatibleModelsSection - connectionManagement.tsx — ConnectionRow, EditConnectionModal, EditCompatibleNodeModal - authModals.tsx — AddApiKeyModal, SiliconFlowEndpointModal, Import/Apply (Codex/Claude/Gemini) auth modals - playground.tsx — ProviderPlaygroundPanel, renderKindPanel page.tsx reduced from 12,882 → 4,687 lines (63% reduction). TypeScript typecheck passes cleanly. ESLint: 0 errors, only 3 pre-existing warnings. Pre-parse and de-duplicate all imports per file to eliminate unused imports. Resolves part of cheaper-inference#3594.
There was a problem hiding this comment.
Code Review
This pull request soft-deprecates @omniroute/opencode-provider in favor of @omniroute/opencode-plugin, introduces CLI shorthands for autostart, implements real guardrail API endpoints, secures public OAuth client IDs, adds a max_tokens buffer for reasoning models, and translates chat responses back to the legacy text-completion shape for /v1/completions. The code review feedback highlights critical issues, including potential browser-side ReferenceErrors due to Buffer usage in client-side components, strict-mode TypeScript compilation errors from unknown type annotations in the modularized chatCore handlers, unnecessary CPU overhead from infinite interval timers, and a state-mutation bug in the single-model execution path.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| function extractEmailFromJwtLocal(idToken: string): string | null { | ||
| try { | ||
| const parts = idToken.split("."); | ||
| if (parts.length !== 3) return null; | ||
| const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8")); | ||
| return typeof payload.email === "string" ? payload.email : null; | ||
| } catch { | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
Using Buffer in a client-side React component ("use client") will cause a runtime ReferenceError: Buffer is not defined in browser environments where Buffer is not globally polyfilled. Since this is a client-side utility file, we should use the standard browser-safe atob function to decode the base64url-encoded JWT payload.
function extractEmailFromJwtLocal(idToken: string): string | null {
try {
const parts = idToken.split(".");
if (parts.length !== 3) return null;
const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
const payload = JSON.parse(atob(padded));
return typeof payload.email === "string" ? payload.email : null;
} catch {
return null;
}
}
| function extractEmailFromGeminiJwt(idToken: string): string | null { | ||
| try { | ||
| const parts = idToken.split("."); | ||
| if (parts.length !== 3) return null; | ||
| const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8")); | ||
| return typeof payload.email === "string" ? payload.email : null; | ||
| } catch { | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
Using Buffer in a client-side React component ("use client") will cause a runtime ReferenceError: Buffer is not defined in browser environments where Buffer is not globally polyfilled. Since this is a client-side utility file, we should use the standard browser-safe atob function to decode the base64url-encoded JWT payload.
function extractEmailFromGeminiJwt(idToken: string): string | null {
try {
const parts = idToken.split(".");
if (parts.length !== 3) return null;
const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
const payload = JSON.parse(atob(padded));
return typeof payload.email === "string" ? payload.email : null;
} catch {
return null;
}
}
| clientRawRequest: unknown; | ||
| provider: string; | ||
| model: string; | ||
| effectiveServiceTier: unknown; | ||
| startTime: number; | ||
| log: unknown; | ||
| }) { |
There was a problem hiding this comment.
Typing clientRawRequest and log as unknown will cause TypeScript compilation errors under strict mode when accessing properties like clientRawRequest?.headers or calling log?.debug?.(...). We should type them as any to allow dynamic property access and method calls.
| clientRawRequest: unknown; | |
| provider: string; | |
| model: string; | |
| effectiveServiceTier: unknown; | |
| startTime: number; | |
| log: unknown; | |
| }) { | |
| clientRawRequest: any; | |
| provider: string; | |
| model: string; | |
| effectiveServiceTier: any; | |
| startTime: number; | |
| log: any; | |
| }) { |
| semanticCacheEnabled: boolean; | ||
| body: Record<string, unknown>; | ||
| clientRawRequest: unknown; | ||
| model: string; | ||
| provider: string; | ||
| stream: boolean; | ||
| reqLogger: unknown; | ||
| effectiveServiceTier: unknown; | ||
| connectionId: string | null; | ||
| startTime: number; | ||
| log: unknown; | ||
| persistAttemptLogs: (args: unknown) => void; | ||
| }) { |
There was a problem hiding this comment.
Typing body as Record<string, unknown>, and clientRawRequest, reqLogger, and log as unknown will cause TypeScript compilation errors under strict mode when accessing properties like body.messages, clientRawRequest?.headers, or calling reqLogger.logConvertedResponse(...) and log?.debug?.(...). We should type them as any to allow dynamic property access and method calls.
| semanticCacheEnabled: boolean; | |
| body: Record<string, unknown>; | |
| clientRawRequest: unknown; | |
| model: string; | |
| provider: string; | |
| stream: boolean; | |
| reqLogger: unknown; | |
| effectiveServiceTier: unknown; | |
| connectionId: string | null; | |
| startTime: number; | |
| log: unknown; | |
| persistAttemptLogs: (args: unknown) => void; | |
| }) { | |
| semanticCacheEnabled: boolean; | |
| body: any; | |
| clientRawRequest: any; | |
| model: string; | |
| provider: string; | |
| stream: boolean; | |
| reqLogger: any; | |
| effectiveServiceTier: any; | |
| connectionId: string | null; | |
| startTime: number; | |
| log: any; | |
| persistAttemptLogs: (args: any) => void; | |
| }) { |
| }: { | ||
| body: Record<string, unknown>; | ||
| memoryOwnerId: string | null; | ||
| provider: string; | ||
| effectiveModel: string; | ||
| sourceFormat: string; | ||
| targetFormat: string; | ||
| backgroundReason: string | null; | ||
| log: unknown; | ||
| }) { |
There was a problem hiding this comment.
Typing body as Record<string, unknown> and log as unknown will cause TypeScript compilation errors under strict mode when accessing properties like body.messages, body.input, body.tools, or calling log?.debug?.(...). We should type them as any to allow dynamic property access and method calls.
| }: { | |
| body: Record<string, unknown>; | |
| memoryOwnerId: string | null; | |
| provider: string; | |
| effectiveModel: string; | |
| sourceFormat: string; | |
| targetFormat: string; | |
| backgroundReason: string | null; | |
| log: unknown; | |
| }) { | |
| }: { | |
| body: any; | |
| memoryOwnerId: string | null; | |
| provider: string; | |
| effectiveModel: string; | |
| sourceFormat: string; | |
| targetFormat: string; | |
| backgroundReason: string | null; | |
| log: any; | |
| }) { |
| export function sanitizeChatRequestBody( | ||
| body: Record<string, unknown>, | ||
| sourceFormat: string, | ||
| targetFormat: string | ||
| ): Record<string, unknown> { |
There was a problem hiding this comment.
Typing body as Record<string, unknown> will cause TypeScript compilation errors under strict mode when accessing, mutating, or deleting properties like body.max_output_tokens, body.max_completion_tokens, body.max_tokens, body.messages, body.input, and body.tools. We should type body as any to allow dynamic property access and mutations.
| export function sanitizeChatRequestBody( | |
| body: Record<string, unknown>, | |
| sourceFormat: string, | |
| targetFormat: string | |
| ): Record<string, unknown> { | |
| export function sanitizeChatRequestBody( | |
| body: any, | |
| sourceFormat: string, | |
| targetFormat: string | |
| ): any { |
| useEffect(() => { | ||
| const checkCooldown = () => { | ||
| const cooldown = | ||
| connection.rateLimitedUntil && new Date(connection.rateLimitedUntil).getTime() > Date.now(); | ||
| setIsCooldown(cooldown); | ||
| }; | ||
|
|
||
| checkCooldown(); | ||
| // Update every second while in cooldown | ||
| const interval = connection.rateLimitedUntil ? setInterval(checkCooldown, 1000) : null; | ||
| return () => { | ||
| if (interval) clearInterval(interval); | ||
| }; | ||
| }, [connection.rateLimitedUntil]); |
There was a problem hiding this comment.
If connection.rateLimitedUntil is in the past, the interval timer is still created and runs every second indefinitely, repeatedly setting isCooldown to false. This causes unnecessary CPU overhead. We should only create the interval if the cooldown is actually in the future, and clear it as soon as the cooldown expires.
useEffect(() => {
let interval: any = null;
const checkCooldown = () => {
const cooldown =
connection.rateLimitedUntil && new Date(connection.rateLimitedUntil).getTime() > Date.now();
setIsCooldown(cooldown);
if (!cooldown && interval) {
clearInterval(interval);
interval = null;
}
};
checkCooldown();
const cooldownActive =
connection.rateLimitedUntil && new Date(connection.rateLimitedUntil).getTime() > Date.now();
if (cooldownActive) {
interval = setInterval(checkCooldown, 1000);
}
return () => {
if (interval) clearInterval(interval);
};
}, [connection.rateLimitedUntil]);
| useEffect(() => { | ||
| const updateRemaining = () => { | ||
| const diff = new Date(until).getTime() - Date.now(); | ||
| if (diff <= 0) { | ||
| setRemaining(""); | ||
| return; | ||
| } | ||
| const secs = Math.floor(diff / 1000); | ||
| if (secs < 60) { | ||
| setRemaining(`${secs}s`); | ||
| } else if (secs < 3600) { | ||
| setRemaining(`${Math.floor(secs / 60)}m ${secs % 60}s`); | ||
| } else { | ||
| const hrs = Math.floor(secs / 3600); | ||
| const mins = Math.floor((secs % 3600) / 60); | ||
| setRemaining(`${hrs}h ${mins}m`); | ||
| } | ||
| }; | ||
|
|
||
| updateRemaining(); | ||
| const interval = setInterval(updateRemaining, 1000); | ||
| return () => clearInterval(interval); | ||
| }, [until]); |
There was a problem hiding this comment.
If the cooldown has already expired initially (or once it expires), the setInterval timer continues to run every second indefinitely. We should only create the interval if the cooldown is in the future, and clear it as soon as the remaining time reaches zero.
useEffect(() => {
let interval: any = null;
const updateRemaining = () => {
const diff = new Date(until).getTime() - Date.now();
if (diff <= 0) {
setRemaining("");
if (interval) {
clearInterval(interval);
interval = null;
}
return;
}
const secs = Math.floor(diff / 1000);
if (secs < 60) {
setRemaining(`${secs}s`);
} else if (secs < 3600) {
setRemaining(`${Math.floor(secs / 60)}m ${secs % 60}s`);
} else {
const hrs = Math.floor(secs / 3600);
const mins = Math.floor((secs % 3600) / 60);
setRemaining(`${hrs}h ${mins}m`);
}
};
updateRemaining();
const isActive = new Date(until).getTime() > Date.now();
if (isActive) {
interval = setInterval(updateRemaining, 1000);
}
return () => {
if (interval) clearInterval(interval);
};
}, [until]);
| if (supportsReasoning(modelStr)) { | ||
| const bodyRecord = attemptBody as Record<string, unknown>; | ||
| const currentMaxTokens = Number(bodyRecord.max_tokens) || 0; | ||
| if (currentMaxTokens > 0) { | ||
| // Add 50% buffer + 1000 floor to ensure reasoning + content both fit | ||
| const bufferedMaxTokens = Math.max( | ||
| currentMaxTokens + 1000, | ||
| Math.ceil(currentMaxTokens * 1.5) | ||
| ); | ||
| bodyRecord.max_tokens = bufferedMaxTokens; | ||
| log.info( | ||
| "COMBO", | ||
| `Reasoning model ${modelStr}: buffered max_tokens ${currentMaxTokens} -> ${bufferedMaxTokens}` | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
In the single-model section, attemptBody is cast to Record<string, unknown> and mutated directly (bodyRecord.max_tokens = bufferedMaxTokens;). This can cause the mutation to persist and potentially compound across retries or sibling calls. To prevent any side-effects, we should apply the buffer to a shallow copy of attemptBody, just like in the round-robin section.
if (supportsReasoning(modelStr)) {
const currentMaxTokens = Number((attemptBody as Record<string, unknown>).max_tokens) || 0;
if (currentMaxTokens > 0) {
const bufferedMaxTokens = Math.max(
currentMaxTokens + 1000,
Math.ceil(currentMaxTokens * 1.5)
);
attemptBody = {
...(attemptBody as Record<string, unknown>),
max_tokens: bufferedMaxTokens,
} as typeof attemptBody;
log.info(
"COMBO",
`Reasoning model ${modelStr}: buffered max_tokens ${currentMaxTokens} -> ${bufferedMaxTokens}`
);
}
}|
Hi @oyi77 — thank you for taking on the single biggest file in the repo. Tackling To keep everyone aligned, here's a detailed picture of what this PR does versus what's planned and now in progress under the tracking issue #3501 (which carries the owner-approved 6-phase plan). What this PR does
What the #3501 plan calls for (and how we're landing it)The registered plan follows the repo's existing big-page convention and a strangler-fig sequence so each step is independently shippable and the
We've started shipping these against the active One heads-up on this branchThis PR currently targets Thanks again — the grouping of symbols here was a useful reference. Follow #3501 to track the remaining phases. This is an automated message from the quality-gate workflow. |
- Replaced Buffer with browser-safe atob in utils.tsx - Fixed memory leaks/infinite intervals in cooldown timers - Typed chatCore handlers with 'any' instead of 'unknown' to fix strict-mode property access - Fixed single-model reasoning payload mutation in combo.ts
… hydration, secure upstream-ca test endpoint
|
Hi @oyi77 — thank you for taking the time to tackle this big refactor 🙏 We've now completed the
Result: 12,882 → 4,062 LOC (−68%) so far, fully cycle-checked, with smoke tests at every step and the file-size ratchet enforcing it stays shrunk. Because the work landed phase-by-phase on the release branch (each phase independently typechecked/tested/merged), this PR is now superseded — so I'm closing it. You're credited as co-author on every #3501 phase commit, so your contribution is on the record. Thanks again for pushing on this, and please keep the PRs coming! 🚀 |
- Replaced Buffer with browser-safe atob in utils.tsx - Fixed memory leaks/infinite intervals in cooldown timers - Typed chatCore handlers with 'any' instead of 'unknown' to fix strict-mode property access - Fixed single-model reasoning payload mutation in combo.ts
- Replaced Buffer with browser-safe atob in utils.tsx - Fixed memory leaks/infinite intervals in cooldown timers - Typed chatCore handlers with 'any' instead of 'unknown' to fix strict-mode property access - Fixed single-model reasoning payload mutation in combo.ts
- Replaced Buffer with browser-safe atob in utils.tsx - Fixed memory leaks/infinite intervals in cooldown timers - Typed chatCore handlers with 'any' instead of 'unknown' to fix strict-mode property access - Fixed single-model reasoning payload mutation in combo.ts
- Replaced Buffer with browser-safe atob in utils.tsx - Fixed memory leaks/infinite intervals in cooldown timers - Typed chatCore handlers with 'any' instead of 'unknown' to fix strict-mode property access - Fixed single-model reasoning payload mutation in combo.ts
- Replaced Buffer with browser-safe atob in utils.tsx - Fixed memory leaks/infinite intervals in cooldown timers - Typed chatCore handlers with 'any' instead of 'unknown' to fix strict-mode property access - Fixed single-model reasoning payload mutation in combo.ts
Summary
This PR addresses the highest priority massive file from Issue #3594:
src/app/(dashboard)/dashboard/providers/[id]/page.tsxat 12,882 lines — the largest file in the entire codebase.Changes
Splits the monolithic page component into 6 domain-focused files organized by responsibility:
types.tsutils.tsxmodelManagement.tsxconnectionManagement.tsxauthModals.tsxplayground.tsxResults
Verification
npm run typecheck:core— passesResolves part of #3594.