@bitkyc08/opencodex 2.6.4 → 2.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/{index-MnDRzA4G.js → index-DcMEJXTd.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/kiro-retry.ts +1 -1
- package/src/codex-catalog.ts +59 -27
- package/src/config.ts +1 -0
- package/src/provider-context-cap.ts +36 -0
- package/src/server.ts +37 -3
- package/src/types.ts +2 -0
package/gui/dist/index.html
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
} catch (e) {}
|
|
17
17
|
})();
|
|
18
18
|
</script>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-DcMEJXTd.js"></script>
|
|
20
20
|
<link rel="stylesheet" crossorigin href="/assets/index-BwvDb198.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
package/package.json
CHANGED
|
@@ -70,7 +70,7 @@ async function normalizeFinalKiroHttpError(res: Response): Promise<Response> {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
export async function fetchKiroWithRetry(request: AdapterRequest, ctx: AdapterFetchContext = {}): Promise<Response> {
|
|
73
|
-
const timeoutMs = ctx.timeoutMs ??
|
|
73
|
+
const timeoutMs = ctx.timeoutMs ?? 100_000;
|
|
74
74
|
let lastError: unknown;
|
|
75
75
|
for (let attempt = 0; attempt < KIRO_RETRY_ATTEMPTS; attempt++) {
|
|
76
76
|
if (ctx.abortSignal?.aborted) throw abortError(ctx.abortSignal);
|
package/src/codex-catalog.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { OcxConfig, OcxProviderConfig } from "./types";
|
|
|
10
10
|
import { CODEX_REASONING_LEVELS, configuredReasoningEfforts, modelRecordValue, sanitizeCodexReasoningEfforts } from "./reasoning-effort";
|
|
11
11
|
import { getJawcodeModelMetadata, getJawcodeModelMetadataCaseInsensitive, listJawcodeModelMetadata, resolveJawcodeProvider } from "./generated/jawcode-model-metadata";
|
|
12
12
|
import { shouldCaseFoldMetadataModelId } from "./providers/derive";
|
|
13
|
+
import { applyProviderContextCap, providerContextCap } from "./provider-context-cap";
|
|
13
14
|
|
|
14
15
|
const BUNDLED_CATALOG_CACHE_MS = 60_000;
|
|
15
16
|
let bundledCatalogCache: { expiresAt: number; value: RawCatalog | null } | null = null;
|
|
@@ -81,7 +82,16 @@ export function nativeOpenAiSlugs(): string[] {
|
|
|
81
82
|
return live.length > 0 ? unique([...live, ...DOCUMENTED_NATIVE_OPENAI_ADDITIONS]) : NATIVE_OPENAI_MODELS;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
export interface CatalogModel {
|
|
85
|
+
export interface CatalogModel {
|
|
86
|
+
id: string;
|
|
87
|
+
provider: string;
|
|
88
|
+
owned_by?: string;
|
|
89
|
+
reasoningEfforts?: string[];
|
|
90
|
+
contextWindow?: number;
|
|
91
|
+
contextCap?: number;
|
|
92
|
+
contextCapped?: boolean;
|
|
93
|
+
inputModalities?: string[];
|
|
94
|
+
}
|
|
85
95
|
type RawEntry = Record<string, unknown>;
|
|
86
96
|
type RawCatalog = { models?: RawEntry[]; [k: string]: unknown };
|
|
87
97
|
const JAWCODE_CATALOG_AUGMENT_PROVIDERS = new Set(["opencode-go"]);
|
|
@@ -230,7 +240,7 @@ export function normalizeRoutedCatalogEntry(entry: RawEntry): RawEntry {
|
|
|
230
240
|
return ensureStrictCatalogFields(entry);
|
|
231
241
|
}
|
|
232
242
|
|
|
233
|
-
function applyJawcodeCatalogMetadata(entry: RawEntry, slug: string): void {
|
|
243
|
+
function applyJawcodeCatalogMetadata(entry: RawEntry, slug: string, contextCap?: number): void {
|
|
234
244
|
const slash = slug.indexOf("/");
|
|
235
245
|
if (slash < 0) return;
|
|
236
246
|
const provider = slug.slice(0, slash);
|
|
@@ -241,9 +251,10 @@ function applyJawcodeCatalogMetadata(entry: RawEntry, slug: string): void {
|
|
|
241
251
|
?? (shouldCaseFoldMetadataModelId(provider) ? getJawcodeModelMetadataCaseInsensitive(jawcodeProvider, modelId) : undefined);
|
|
242
252
|
if (!meta) return;
|
|
243
253
|
if (typeof meta.contextWindow === "number" && meta.contextWindow > 0) {
|
|
244
|
-
|
|
245
|
-
entry.
|
|
246
|
-
entry.
|
|
254
|
+
const contextWindow = applyProviderContextCap(meta.contextWindow, contextCap) ?? meta.contextWindow;
|
|
255
|
+
entry.context_window = contextWindow;
|
|
256
|
+
entry.max_context_window = contextWindow;
|
|
257
|
+
entry.auto_compact_token_limit = Math.floor(contextWindow * 0.9);
|
|
247
258
|
}
|
|
248
259
|
if (Array.isArray(meta.input) && meta.input.length > 0) {
|
|
249
260
|
entry.input_modalities = meta.input;
|
|
@@ -439,7 +450,7 @@ function deriveEntry(template: RawEntry | null, slug: string, desc: string, prio
|
|
|
439
450
|
}
|
|
440
451
|
applyReasoningLevels(e, model?.reasoningEfforts);
|
|
441
452
|
normalizeRoutedCatalogEntry(e);
|
|
442
|
-
applyJawcodeCatalogMetadata(e, slug);
|
|
453
|
+
applyJawcodeCatalogMetadata(e, slug, model?.contextCap);
|
|
443
454
|
applyCatalogModelMetadata(e, model);
|
|
444
455
|
} else {
|
|
445
456
|
applyNativeOpenAiContextOverride(e);
|
|
@@ -455,7 +466,7 @@ function deriveEntry(template: RawEntry | null, slug: string, desc: string, prio
|
|
|
455
466
|
};
|
|
456
467
|
if (slug.includes("/")) applyReasoningLevels(entry, model?.reasoningEfforts);
|
|
457
468
|
else applyReasoningLevels(entry);
|
|
458
|
-
applyJawcodeCatalogMetadata(entry, slug);
|
|
469
|
+
applyJawcodeCatalogMetadata(entry, slug, model?.contextCap);
|
|
459
470
|
applyCatalogModelMetadata(entry, model);
|
|
460
471
|
applyNativeOpenAiContextOverride(entry);
|
|
461
472
|
return ensureStrictCatalogFields(normalizeServiceTiers(entry));
|
|
@@ -577,33 +588,38 @@ function configuredInputModalities(prov: OcxProviderConfig, id: string): string[
|
|
|
577
588
|
return Array.isArray(modalities) && modalities.length > 0 ? [...modalities] : undefined;
|
|
578
589
|
}
|
|
579
590
|
|
|
580
|
-
function applyProviderConfigHints(name: string, prov: OcxProviderConfig, model: CatalogModel): CatalogModel {
|
|
591
|
+
function applyProviderConfigHints(name: string, prov: OcxProviderConfig, model: CatalogModel, providerCap?: number): CatalogModel {
|
|
581
592
|
void name;
|
|
582
|
-
const
|
|
593
|
+
const configuredCap = configuredContextWindow(prov, model.id);
|
|
583
594
|
const inputModalities = configuredInputModalities(prov, model.id);
|
|
584
595
|
const reasoningEfforts = configuredReasoningEfforts(prov, model.id);
|
|
585
|
-
|
|
596
|
+
const hinted = {
|
|
586
597
|
...model,
|
|
587
|
-
...(
|
|
598
|
+
...(configuredCap !== undefined
|
|
588
599
|
? {
|
|
589
600
|
contextWindow: typeof model.contextWindow === "number" && model.contextWindow > 0
|
|
590
|
-
? Math.min(model.contextWindow,
|
|
591
|
-
:
|
|
601
|
+
? Math.min(model.contextWindow, configuredCap)
|
|
602
|
+
: configuredCap,
|
|
592
603
|
}
|
|
593
604
|
: {}),
|
|
594
605
|
...(inputModalities ? { inputModalities } : {}),
|
|
595
606
|
...(reasoningEfforts !== undefined ? { reasoningEfforts } : {}),
|
|
596
607
|
};
|
|
608
|
+
const capped = applyProviderContextCap(hinted.contextWindow, providerCap);
|
|
609
|
+
if (providerCap !== undefined && capped !== hinted.contextWindow) {
|
|
610
|
+
return { ...hinted, contextWindow: capped, contextCap: providerCap, contextCapped: true };
|
|
611
|
+
}
|
|
612
|
+
return providerCap !== undefined ? { ...hinted, contextCap: providerCap, contextCapped: false } : hinted;
|
|
597
613
|
}
|
|
598
614
|
|
|
599
|
-
function catalogHintsFromProviderConfig(name: string, prov: OcxProviderConfig, id: string): Partial<CatalogModel> {
|
|
600
|
-
const hinted = applyProviderConfigHints(name, prov, { id, provider: name });
|
|
615
|
+
function catalogHintsFromProviderConfig(name: string, prov: OcxProviderConfig, id: string, contextCap?: number): Partial<CatalogModel> {
|
|
616
|
+
const hinted = applyProviderConfigHints(name, prov, { id, provider: name }, contextCap);
|
|
601
617
|
const { provider: _provider, id: _id, ...hints } = hinted;
|
|
602
618
|
return hints;
|
|
603
619
|
}
|
|
604
620
|
|
|
605
|
-
function applyConfigHintsToCachedModels(name: string, prov: OcxProviderConfig, models: CatalogModel[]): CatalogModel[] {
|
|
606
|
-
return models.map(model => applyProviderConfigHints(name, prov, model));
|
|
621
|
+
function applyConfigHintsToCachedModels(name: string, prov: OcxProviderConfig, models: CatalogModel[], contextCap?: number): CatalogModel[] {
|
|
622
|
+
return models.map(model => applyProviderConfigHints(name, prov, model, contextCap));
|
|
607
623
|
}
|
|
608
624
|
|
|
609
625
|
function isGlm52ModelId(id: string): boolean {
|
|
@@ -641,26 +657,26 @@ function catalogHintsFromModelsApiItem(providerName: string, item: ProviderModel
|
|
|
641
657
|
* fetch failure → last-known-good cache (so a provider blip doesn't drop its models), else the
|
|
642
658
|
* static config list. This is the per-provider half of jawcode's "always latest" resolver.
|
|
643
659
|
*/
|
|
644
|
-
async function fetchProviderModels(name: string, prov: OcxProviderConfig, ttlMs: number): Promise<CatalogModel[]> {
|
|
660
|
+
async function fetchProviderModels(name: string, prov: OcxProviderConfig, ttlMs: number, contextCap?: number): Promise<CatalogModel[]> {
|
|
645
661
|
if (prov.authMode === "forward") return []; // ChatGPT backend has no /models
|
|
646
662
|
const apiKey = await resolveModelsAuthToken(name, prov);
|
|
647
663
|
if (prov.authMode === "oauth" && !apiKey) return []; // not logged in → skip
|
|
648
664
|
const configured: CatalogModel[] = (prov.models ?? []).map(id => ({
|
|
649
665
|
id,
|
|
650
666
|
provider: name,
|
|
651
|
-
...catalogHintsFromProviderConfig(name, prov, id),
|
|
667
|
+
...catalogHintsFromProviderConfig(name, prov, id, contextCap),
|
|
652
668
|
}));
|
|
653
669
|
if (prov.liveModels === false) {
|
|
654
670
|
return configured;
|
|
655
671
|
}
|
|
656
672
|
const fresh = getFreshCached(name, ttlMs);
|
|
657
|
-
if (fresh) return applyConfigHintsToCachedModels(name, prov, fresh); // dedups Codex's frequent /v1/models polling within the TTL
|
|
673
|
+
if (fresh) return applyConfigHintsToCachedModels(name, prov, fresh, contextCap); // dedups Codex's frequent /v1/models polling within the TTL
|
|
658
674
|
const { url, headers } = buildModelsRequest(prov, apiKey);
|
|
659
675
|
try {
|
|
660
676
|
const res = await fetch(url, { headers, signal: AbortSignal.timeout(8000) });
|
|
661
677
|
if (!res.ok) {
|
|
662
678
|
const stale = getStaleCached(name);
|
|
663
|
-
return stale ? applyConfigHintsToCachedModels(name, prov, stale) : configured;
|
|
679
|
+
return stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap) : configured;
|
|
664
680
|
}
|
|
665
681
|
const json = await res.json() as { data?: ProviderModelsApiItem[] };
|
|
666
682
|
const live = (json.data ?? []).map(m => applyProviderConfigHints(name, prov, {
|
|
@@ -668,7 +684,7 @@ async function fetchProviderModels(name: string, prov: OcxProviderConfig, ttlMs:
|
|
|
668
684
|
provider: name,
|
|
669
685
|
owned_by: m.owned_by,
|
|
670
686
|
...catalogHintsFromModelsApiItem(name, m),
|
|
671
|
-
}));
|
|
687
|
+
}, contextCap));
|
|
672
688
|
const liveIds = new Set(live.map(m => m.id));
|
|
673
689
|
// Merge explicit config additions (e.g. a model not in the provider's /models, like a new endpoint).
|
|
674
690
|
const merged = [...live, ...configured.filter(m => !liveIds.has(m.id))];
|
|
@@ -676,7 +692,7 @@ async function fetchProviderModels(name: string, prov: OcxProviderConfig, ttlMs:
|
|
|
676
692
|
return merged;
|
|
677
693
|
} catch {
|
|
678
694
|
const stale = getStaleCached(name);
|
|
679
|
-
return stale ? applyConfigHintsToCachedModels(name, prov, stale) : configured;
|
|
695
|
+
return stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap) : configured;
|
|
680
696
|
}
|
|
681
697
|
}
|
|
682
698
|
|
|
@@ -689,9 +705,9 @@ async function fetchProviderModels(name: string, prov: OcxProviderConfig, ttlMs:
|
|
|
689
705
|
export async function gatherRoutedModels(config: OcxConfig): Promise<CatalogModel[]> {
|
|
690
706
|
const ttlMs = config.modelCacheTtlMs ?? DEFAULT_MODEL_CACHE_TTL_MS;
|
|
691
707
|
const lists = await Promise.all(
|
|
692
|
-
Object.entries(config.providers).map(([name, prov]) => fetchProviderModels(name, prov, ttlMs)),
|
|
708
|
+
Object.entries(config.providers).map(([name, prov]) => fetchProviderModels(name, prov, ttlMs, providerContextCap(config, name))),
|
|
693
709
|
);
|
|
694
|
-
const all = augmentRoutedModelsWithJawcodeMetadata(lists.flat(), Object.keys(config.providers), config.providers)
|
|
710
|
+
const all = augmentRoutedModelsWithJawcodeMetadata(lists.flat(), Object.keys(config.providers), config.providers, config)
|
|
695
711
|
// Drop image/video generation models (e.g. Grok image/video) — they are not usable by Codex and
|
|
696
712
|
// must not surface in the dashboard, /v1/models, or the routed catalog. Single choke point.
|
|
697
713
|
.filter(m => !isMediaGenerationModelId(m.id));
|
|
@@ -699,7 +715,12 @@ export async function gatherRoutedModels(config: OcxConfig): Promise<CatalogMode
|
|
|
699
715
|
return all;
|
|
700
716
|
}
|
|
701
717
|
|
|
702
|
-
export function augmentRoutedModelsWithJawcodeMetadata(
|
|
718
|
+
export function augmentRoutedModelsWithJawcodeMetadata(
|
|
719
|
+
models: CatalogModel[],
|
|
720
|
+
providerNames: string[],
|
|
721
|
+
providers?: Record<string, OcxProviderConfig>,
|
|
722
|
+
caps?: Pick<OcxConfig, "providerContextCaps">,
|
|
723
|
+
): CatalogModel[] {
|
|
703
724
|
const out = [...models];
|
|
704
725
|
const seen = new Set(out.map(m => `${m.provider}/${m.id}`));
|
|
705
726
|
for (const provider of providerNames) {
|
|
@@ -711,7 +732,18 @@ export function augmentRoutedModelsWithJawcodeMetadata(models: CatalogModel[], p
|
|
|
711
732
|
const key = `${provider}/${meta.id}`;
|
|
712
733
|
if (seen.has(key)) continue;
|
|
713
734
|
seen.add(key);
|
|
714
|
-
|
|
735
|
+
const contextCap = caps ? providerContextCap(caps, provider) : undefined;
|
|
736
|
+
const model: CatalogModel = {
|
|
737
|
+
provider,
|
|
738
|
+
id: meta.id,
|
|
739
|
+
owned_by: provider,
|
|
740
|
+
...(typeof meta.contextWindow === "number" && meta.contextWindow > 0 ? { contextWindow: meta.contextWindow } : {}),
|
|
741
|
+
...(Array.isArray(meta.input) && meta.input.length > 0 ? { inputModalities: [...meta.input] } : {}),
|
|
742
|
+
};
|
|
743
|
+
out.push({
|
|
744
|
+
...model,
|
|
745
|
+
...(providers?.[provider] ? applyProviderConfigHints(provider, providers[provider], model, contextCap) : {}),
|
|
746
|
+
});
|
|
715
747
|
}
|
|
716
748
|
}
|
|
717
749
|
return out;
|
package/src/config.ts
CHANGED
|
@@ -98,6 +98,7 @@ const configSchema = z.object({
|
|
|
98
98
|
port: z.number().int().min(0).max(65535).default(10100),
|
|
99
99
|
providers: z.record(z.string(), providerConfigSchema),
|
|
100
100
|
defaultProvider: z.string().min(1).default("openai"),
|
|
101
|
+
providerContextCaps: z.record(z.string(), z.number().int().positive()).optional(),
|
|
101
102
|
}).passthrough().superRefine((config, ctx) => {
|
|
102
103
|
for (const name of Object.keys(config.providers)) {
|
|
103
104
|
if (!isValidProviderName(name)) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { OcxConfig } from "./types";
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_PROVIDER_CONTEXT_CAP = 350_000;
|
|
4
|
+
|
|
5
|
+
function isValidContextCap(value: unknown): value is number {
|
|
6
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function providerContextCap(config: Pick<OcxConfig, "providerContextCaps">, provider: string): number | undefined {
|
|
10
|
+
const value = config.providerContextCaps?.[provider];
|
|
11
|
+
return isValidContextCap(value) ? value : undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function providerContextCaps(config: Pick<OcxConfig, "providerContextCaps">): Record<string, number> {
|
|
15
|
+
const caps = config.providerContextCaps;
|
|
16
|
+
if (!caps || typeof caps !== "object" || Array.isArray(caps)) return {};
|
|
17
|
+
const out: Record<string, number> = {};
|
|
18
|
+
for (const [provider, value] of Object.entries(caps)) {
|
|
19
|
+
if (isValidContextCap(value)) out[provider] = value;
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function applyProviderContextCap(contextWindow: number | undefined, cap: number | undefined): number | undefined {
|
|
25
|
+
if (!isValidContextCap(cap)) return contextWindow;
|
|
26
|
+
if (!isValidContextCap(contextWindow)) return contextWindow;
|
|
27
|
+
return contextWindow > cap ? cap : contextWindow;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function setProviderContextCap(config: OcxConfig, provider: string, enabled: boolean): void {
|
|
31
|
+
const next = providerContextCaps(config);
|
|
32
|
+
if (enabled) next[provider] = DEFAULT_PROVIDER_CONTEXT_CAP;
|
|
33
|
+
else delete next[provider];
|
|
34
|
+
if (Object.keys(next).length > 0) config.providerContextCaps = next;
|
|
35
|
+
else delete config.providerContextCaps;
|
|
36
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -48,6 +48,7 @@ import { enrichProviderFromCatalog, listKeyLoginProviders } from "./oauth/key-pr
|
|
|
48
48
|
import { deriveProviderPresets } from "./providers/derive";
|
|
49
49
|
import type { AdapterEvent, OcxConfig, OcxProviderConfig } from "./types";
|
|
50
50
|
import type { OcxUsage } from "./types";
|
|
51
|
+
import { DEFAULT_PROVIDER_CONTEXT_CAP, providerContextCap, providerContextCaps, setProviderContextCap } from "./provider-context-cap";
|
|
51
52
|
import {
|
|
52
53
|
appendUsageEntry,
|
|
53
54
|
readUsageEntries,
|
|
@@ -454,7 +455,7 @@ async function handleResponses(
|
|
|
454
455
|
// whose cancel() aborts the upstream — preventing leaked connections (RC2, passthrough path).
|
|
455
456
|
const upstream = new AbortController();
|
|
456
457
|
linkAbortSignal(upstream, options.abortSignal);
|
|
457
|
-
const connectMs = config.connectTimeoutMs ??
|
|
458
|
+
const connectMs = config.connectTimeoutMs ?? 100_000;
|
|
458
459
|
let upstreamResponse: Response;
|
|
459
460
|
try {
|
|
460
461
|
upstreamResponse = await fetchWithHeaderTimeout(request.url, {
|
|
@@ -586,7 +587,7 @@ async function handleResponses(
|
|
|
586
587
|
|
|
587
588
|
const upstream = new AbortController();
|
|
588
589
|
const cleanupUpstreamAbort = linkAbortSignal(upstream, options.abortSignal);
|
|
589
|
-
const connectMs = config.connectTimeoutMs ??
|
|
590
|
+
const connectMs = config.connectTimeoutMs ?? 100_000;
|
|
590
591
|
|
|
591
592
|
const request = adapter.buildRequest(parsed, { headers: selectedForwardHeaders });
|
|
592
593
|
if (typeof request.usageLog?.inputTokens === "number") {
|
|
@@ -1759,6 +1760,7 @@ async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): P
|
|
|
1759
1760
|
if (name === config.defaultProvider) return jsonResponse({ error: "cannot delete the default provider; set another default first" }, 400);
|
|
1760
1761
|
const { saveConfig: save } = await import("./config");
|
|
1761
1762
|
delete config.providers[name];
|
|
1763
|
+
setProviderContextCap(config, name, false);
|
|
1762
1764
|
save(config);
|
|
1763
1765
|
const { clearModelCache: clearCache } = await import("./model-cache");
|
|
1764
1766
|
clearCache(name);
|
|
@@ -1771,10 +1773,42 @@ async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): P
|
|
|
1771
1773
|
const disabled = new Set(config.disabledModels ?? []);
|
|
1772
1774
|
return jsonResponse(models.map(m => {
|
|
1773
1775
|
const namespaced = `${m.provider}/${m.id}`;
|
|
1774
|
-
|
|
1776
|
+
const contextCap = providerContextCap(config, m.provider);
|
|
1777
|
+
return {
|
|
1778
|
+
...m,
|
|
1779
|
+
namespaced,
|
|
1780
|
+
disabled: disabled.has(namespaced),
|
|
1781
|
+
...(contextCap !== undefined ? { contextCap, contextCapped: m.contextCapped === true } : {}),
|
|
1782
|
+
};
|
|
1775
1783
|
}));
|
|
1776
1784
|
}
|
|
1777
1785
|
|
|
1786
|
+
if (url.pathname === "/api/provider-context-caps" && req.method === "GET") {
|
|
1787
|
+
return jsonResponse({ cap: DEFAULT_PROVIDER_CONTEXT_CAP, caps: providerContextCaps(config) });
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
if (url.pathname === "/api/provider-context-caps" && req.method === "PUT") {
|
|
1791
|
+
let body: { provider?: unknown; enabled?: unknown };
|
|
1792
|
+
try { body = await req.json(); } catch { return jsonResponse({ error: "invalid JSON body" }, 400); }
|
|
1793
|
+
if (typeof body.provider !== "string" || typeof body.enabled !== "boolean") {
|
|
1794
|
+
return jsonResponse({ error: "provider string and enabled boolean are required" }, 400);
|
|
1795
|
+
}
|
|
1796
|
+
const provider = body.provider.trim();
|
|
1797
|
+
if (!isValidProviderName(provider)) {
|
|
1798
|
+
return jsonResponse({ error: "provider name must use letters, numbers, dot, underscore, or hyphen and cannot be a reserved object key" }, 400);
|
|
1799
|
+
}
|
|
1800
|
+
if (!hasOwnProvider(config.providers, provider)) {
|
|
1801
|
+
return jsonResponse({ error: "unknown provider" }, 404);
|
|
1802
|
+
}
|
|
1803
|
+
setProviderContextCap(config, provider, body.enabled);
|
|
1804
|
+
const { saveConfig: save } = await import("./config");
|
|
1805
|
+
save(config);
|
|
1806
|
+
const { clearModelCache } = await import("./model-cache");
|
|
1807
|
+
clearModelCache(provider);
|
|
1808
|
+
await refreshCodexCatalogBestEffort();
|
|
1809
|
+
return jsonResponse({ ok: true, cap: DEFAULT_PROVIDER_CONTEXT_CAP, caps: providerContextCaps(config) });
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1778
1812
|
// Enable/disable models: which routed models Codex sees. PUT hides them from the catalog +
|
|
1779
1813
|
// /v1/models and invalidates Codex's 5-min models cache so it applies on the next turn.
|
|
1780
1814
|
if (url.pathname === "/api/disabled-models" && req.method === "PUT") {
|
package/src/types.ts
CHANGED
|
@@ -205,6 +205,8 @@ export interface OcxConfig {
|
|
|
205
205
|
subagentModels?: string[];
|
|
206
206
|
/** Routed model ids ("<provider>/<model>") hidden from Codex (excluded from the catalog + /v1/models). */
|
|
207
207
|
disabledModels?: string[];
|
|
208
|
+
/** Provider-level Codex-visible context caps. Values only lower known model context windows. */
|
|
209
|
+
providerContextCaps?: Record<string, number>;
|
|
208
210
|
/** Bind hostname. Default "127.0.0.1" (loopback only). Set "0.0.0.0" to expose on all interfaces. */
|
|
209
211
|
hostname?: string;
|
|
210
212
|
/** Upstream stall timeout (seconds). After this many seconds of no upstream data, emits response.incomplete. Default 90. Min 1. */
|