mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-17 20:52:15 +03:00
* Bypass proxy compaction for native Codex context
* Add native ChatGPT Web provider pipeline
* Add managed browser and tunnel deployment
* Add ChatGPT Web setup and doctor UI
* Document and test ChatGPT Web integration
* fix(security): register chatgpt-web-codex-doctor in LOCAL_ONLY_API_PATTERNS
The diagnostic route under /api/providers/{id}/chatgpt-web-codex-doctor
was not registered in the spawn-capable route guard. Adding it for
parity with the existing /login pattern.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(providers): route chatgpt-web-codex admin routes through a service boundary
The provider CRUD/doctor routes imported chatgpt-web-codex helpers
(finalizeValidatedChatGptWebCodexSecrets, encode/decodeChatGptWebCodexSecrets,
getChatGptWebCodexDoctorStatus) directly from open-sse/executors/**, which
no-restricted-imports (EXECUTOR_IMPORT_RESTRICTION) forbids for src/app/**
files — executor implementations must stay behind an open-sse handler or
service boundary.
Add open-sse/services/chatgptWebCodexAdmin.ts as a thin re-export boundary
(mirroring the existing tokenRefresh.ts re-export pattern) and import from
there instead. No behavior change.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
111 lines
4.5 KiB
TypeScript
111 lines
4.5 KiB
TypeScript
/**
|
|
* Known context-overflow rejection, extracted from comboStructure.ts to keep
|
|
* that file under the file-size cap (#7177).
|
|
*
|
|
* Fixes: routing a request to a combo whose targets all have a KNOWN (not
|
|
* unknown/fail-open) context window too small for the request used to be
|
|
* discovered only after every target was tried and failed upstream — burning
|
|
* retries/cooldowns on a request that could never succeed. This lets the
|
|
* combo dispatcher reject it up front, before exhausting providers.
|
|
*
|
|
* getKnownContextLimit/hasEstimableContent also
|
|
* live here (moved from comboStructure.ts, same file-size-cap motivation):
|
|
* they are the "how big is a target's known context window" primitives, so
|
|
* they belong next to the overflow check that is their main consumer.
|
|
* comboStructure.ts's own compatibility filter now decides fit via its
|
|
* evaluateContextLimit (#7052); only hasEstimableContent is imported back.
|
|
*/
|
|
|
|
import { getResolvedModelCapabilities } from "../modelCapabilities.ts";
|
|
import { deriveRequestCompatibilityRequirements } from "./comboStructure.ts";
|
|
import type { ResolvedComboTarget } from "./types.ts";
|
|
|
|
export type KnownContextOverflow = {
|
|
estimatedInputTokens: number;
|
|
requestedOutputTokens: number;
|
|
requiredContextTokens: number;
|
|
maxKnownContextTokens: number;
|
|
targetCount: number;
|
|
};
|
|
|
|
// #7177: an empty array/object (e.g. a default `messages: []` some combo entrypoints inject
|
|
// when the caller sent none) has no real content — counting it would charge a few phantom
|
|
// "structural" tokens (JSON.stringify braces/brackets) toward the estimate, which is enough
|
|
// to falsely trip the exact-boundary known-context-overflow check for a request that has no
|
|
// actual input at all.
|
|
export function hasEstimableContent(value: unknown): boolean {
|
|
if (value === undefined || value === null) return false;
|
|
if (Array.isArray(value)) return value.length > 0;
|
|
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
return true;
|
|
}
|
|
|
|
// #7177: known context limit that accounts for the request's own requested
|
|
// output tokens — a target whose input+output would together exceed
|
|
// maxInputTokens is exactly as incompatible as one whose contextWindow is too
|
|
// small, so both bounds go through the same min() so far the tightest wins.
|
|
export function getKnownContextLimit(
|
|
capabilities: {
|
|
maxInputTokens?: number | null;
|
|
contextWindow?: number | null;
|
|
},
|
|
requestedOutputTokens = 0
|
|
): number | null {
|
|
const limits: number[] = [];
|
|
if (capabilities.maxInputTokens != null) {
|
|
limits.push(capabilities.maxInputTokens + requestedOutputTokens);
|
|
}
|
|
if (capabilities.contextWindow != null) {
|
|
limits.push(capabilities.contextWindow);
|
|
}
|
|
return limits.length > 0 ? Math.min(...limits) : null;
|
|
}
|
|
|
|
/**
|
|
* Return a hard context-overflow decision only when every target has a known
|
|
* context limit and every one of those limits is too small for the request.
|
|
* Unknown metadata deliberately keeps the legacy fail-open behavior.
|
|
*/
|
|
export function getKnownContextOverflow(
|
|
targets: ResolvedComboTarget[],
|
|
body: Record<string, unknown>,
|
|
options: { clientManagedResponsesContext?: boolean } = {}
|
|
): KnownContextOverflow | null {
|
|
if (targets.length === 0) return null;
|
|
// Native Codex Responses clients compact their own item history. Let the concrete
|
|
// Codex target enforce its effective context limit (including operator overrides)
|
|
// instead of rejecting early against a smaller catalog hint. Keep this scoped to
|
|
// pools made exclusively from native Codex-capable targets so other Responses
|
|
// clients/providers retain the hard preflight.
|
|
if (
|
|
options.clientManagedResponsesContext === true &&
|
|
targets.every(
|
|
(target) => target.provider === "codex" || target.provider === "chatgpt-web-codex"
|
|
)
|
|
) {
|
|
return null;
|
|
}
|
|
const requirements = deriveRequestCompatibilityRequirements(body);
|
|
if (requirements.requiredContextTokens <= 0) return null;
|
|
|
|
const limits = targets.map((target) =>
|
|
getKnownContextLimit(
|
|
getResolvedModelCapabilities(target.modelStr),
|
|
requirements.requestedOutputTokens
|
|
)
|
|
);
|
|
if (limits.some((limit) => limit === null)) return null;
|
|
|
|
const knownLimits = limits as number[];
|
|
const maxKnownContextTokens = Math.max(...knownLimits);
|
|
if (maxKnownContextTokens >= requirements.requiredContextTokens) return null;
|
|
|
|
return {
|
|
estimatedInputTokens: requirements.estimatedInputTokens,
|
|
requestedOutputTokens: requirements.requestedOutputTokens,
|
|
requiredContextTokens: requirements.requiredContextTokens,
|
|
maxKnownContextTokens,
|
|
targetCount: targets.length,
|
|
};
|
|
}
|