Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
e62fbb7
fix(gemini): preserve structured tool calls for antigravity
May 25, 2026
b89faf1
fix(gemini): parse prefixed textual tool calls
May 25, 2026
beaa726
fix(antigravity): preserve textual SSE tool calls
May 25, 2026
5901a27
fix(stream): normalize textual passthrough tool calls
May 25, 2026
f6b140a
fix(stream): normalize split textual tool calls
May 25, 2026
212d046
fix(stream): suppress malformed textual tool calls
May 25, 2026
d25394b
fix(stream): suppress compact malformed tool calls
May 25, 2026
bb18a04
fix(stream): emit structured textual tool calls
May 25, 2026
68cb9f7
fix(stream): suppress unknown textual tool calls
May 25, 2026
1b6deb8
fix(stream): normalize responses textual tool calls
May 26, 2026
722aa32
chore: ignore .claude/settings.local.json (per-user Claude Code permi…
diegosouzapw May 27, 2026
72a4658
fix(opencode-go): route qwen3.x via claude messages + repair fixMissi…
jeferssonlemes May 27, 2026
619463c
fix: resolve npm install warnings — remove dead deps, relax engine co…
oyi77 May 27, 2026
aac17c0
fix: register missing web-cookie validators (claude-web, gemini-web, …
oyi77 May 27, 2026
0c6c5e2
fix: Error: Unable to inspect existing database #2771 (#2795)
hartmark May 27, 2026
83445a9
fix(oauth): repair Google loopback callback flow (#2796)
akarray May 27, 2026
f1e4c00
feat(logs): add clean history button (#2799)
apoapostolov May 27, 2026
87fad5d
[codex] home: restore settings-driven home layout and quota auto-refr…
apoapostolov May 27, 2026
0e9aed4
fix(gemini): emit signaturelessToolCallMode:text for GEMINI format mo…
herjarsa May 27, 2026
52a43d6
feat(modelSpecs): align opencode-go family with upstream provider lim…
jeferssonlemes May 27, 2026
722e9f4
chore: apply unit test fixes, polyfills, and environment precedence f…
diegosouzapw May 27, 2026
9343451
docs(agents): atualiza fluxos de release e triagem
diegosouzapw May 28, 2026
c791af7
fix(lockout): classify Gemini Antigravity resource exhaustion as quot…
May 28, 2026
a89785f
fix(reasoning): gate replay by interleaved field
May 25, 2026
5197d10
docs(rule-16): permit human Co-authored-by, restrict only AI/bot trai…
diegosouzapw May 28, 2026
017c16c
fix(gitlawb): add specialty validators for connection test — bypass /…
oyi77 May 28, 2026
ecb0d1d
test(gitlawb): add 12 unit tests for gitlawb and gitlawb-gmi specialt…
oyi77 May 28, 2026
bbf8d4c
feat(gitlawb): serve models from static registry without API-unavaila…
oyi77 May 28, 2026
471df45
refactor(gitlawb): extract shared opengateway validator factory, fix …
oyi77 May 28, 2026
b209387
fix(reasoning): guard interleaved capability lookup
May 28, 2026
997ece8
feat(gitlawb): dynamic model fetch with gmi-cloud fallback
oyi77 May 28, 2026
46b451c
i18n(pt-BR): complete missing translations and sync with en.json
alltomatos May 28, 2026
12bacb0
feat(build): nix multi-OS package manager install (#2806)
levonk May 29, 2026
b0191f1
fix(i18n): translate 144 new __MISSING__ pt-BR strings (#2816)
leninejunior May 29, 2026
05276bc
chore(docs): set coverage gate to 40/40/40/40 in CLAUDE.md
diegosouzapw May 29, 2026
0c9740c
fix(cli): respect PORT env var in serve command (#2845)
gogones May 29, 2026
f340422
fix(deepseek-web): return 400 when client sends tools[] - chat.deepse…
Tushar49 May 29, 2026
94a3489
fix(qoder): reject invalid/expired PATs returning Cosy 500 error (#2860)
herjarsa May 29, 2026
25db3de
fix(cli): register openclaw in tool-detector (#2833) (#2850)
diegosouzapw May 29, 2026
18dae1a
fix(api): include noAuth providers in /v1/models catalog (#2798) (#2814)
diegosouzapw May 29, 2026
b677dd6
fix(combo): resolve custom provider targets via combo name (#2778) (#…
diegosouzapw May 29, 2026
5f886dc
fix(translator): strip safety_identifier in openai-responses cleanup …
diegosouzapw May 29, 2026
0104196
fix(quota): honor explicit per-connection preflight opt-out (#2831) (…
diegosouzapw May 29, 2026
0a101b9
fix(usage): un-invert GitHub Copilot Free/limited quota — limited_use…
diegosouzapw May 29, 2026
283c05d
fix(nous-research): correct baseUrl to include /chat/completions (#28…
diegosouzapw May 29, 2026
fee1c17
fix(opencode): qwen3.x max/plus models lack vision support (#2822) (#…
diegosouzapw May 29, 2026
c216085
fix(translator): pass-through tool_search built-in tool type (#2766) …
diegosouzapw May 29, 2026
b741bd6
fix(github): route claude-opus-4.6 via chat completions (#2821)
marchlhw May 29, 2026
dbe7b62
docs(oauth): add Windsurf login fix design (Phase 1 hotfix + Phase 2 …
yunaamelia May 29, 2026
dd10b4b
docs(plan): Phase 1 windsurf login hotfix implementation plan
yunaamelia May 29, 2026
1a4c4f8
fix(cli): replace cli-table3 with hand-rolled formatter (#2752) (#2813)
diegosouzapw May 29, 2026
9dfe482
fix(skills): skip interception for unregistered client-native tools (…
jeferssonlemes May 29, 2026
32adf62
feat(sse): add RTK filters for kubectl, docker-build, composer, gh (#…
leninejunior May 29, 2026
c9251f9
fix(geminiHelper): support rec.image content shape + warn on dropped …
Tushar49 May 29, 2026
1442e08
fix(cli): allow nullable/optional apiKey in cliMitmStartSchema (#2857)
herjarsa May 29, 2026
1d55f96
fix(combo): preserve system messages during context handoff summary g…
herjarsa May 29, 2026
f270d20
fix: wire CLIProxyAPI fallback settings into chatCore routing engine …
oyi77 May 29, 2026
517c3c6
fix(usage): add opencode quota fetcher (#2852) (#2867)
diegosouzapw May 29, 2026
2428ad2
feat(claude): default xhigh support for newer Opus models (#2874)
rdself May 29, 2026
ed36bd7
fix(cli): restore omniroute logs command stream (#2756) (#2810)
diegosouzapw May 29, 2026
c02b6ea
fix(combo): normalize upstream Headers for Node 24 undici interop (#2…
diegosouzapw May 29, 2026
e510a57
Rename proxy log Public IP to Client IP (#2880)
rdself May 29, 2026
b971db8
fix(claude): preserve max effort for supported models (#2875)
rdself May 29, 2026
6d157e4
fix(oauth): switch windsurf provider to import_token flow
yunaamelia May 29, 2026
82999c0
fix(oauth): return 410 Gone for retired windsurf/devin-cli PKCE actions
yunaamelia May 29, 2026
dbaf250
refactor(oauth): annotate retired PKCE fields in WINDSURF_CONFIG
yunaamelia May 29, 2026
a1261cc
fix(cli,docs): use requireCliToolsAuth in logs route + document OPENC…
diegosouzapw May 29, 2026
a928d9f
fix(dashboard): force import-token panel for windsurf/devin-cli
yunaamelia May 29, 2026
e3d5bc2
fix(oauth): windsurf import-token mapTokens signature mismatch
yunaamelia May 29, 2026
53ac23c
feat(compression): expand pt-BR pack with troglodita rules (15 → 49) …
leninejunior May 29, 2026
8f4fc8b
fix(sse): repair RTK engine defaults so dedup and direct calls work (…
leninejunior May 29, 2026
cccc53c
fix(mcp): redirect console.log/warn to stderr in --mcp stdio mode (#2…
disonjer May 29, 2026
a8b4dc3
fix(gemini-cli): prefer real project IDs over default-project (#2841)
nickwizard May 29, 2026
a7e71ae
fix(opencode-go): add provider limits quota fetcher (#2861)
RajvardhanPatil07 May 29, 2026
2355bf9
Audit & add web cookie providers: fix 4 missing registry entries + Du…
oyi77 May 29, 2026
a9b5a7c
fix(antigravity): harden signatureless tool history (#2878)
dhaern May 29, 2026
d96eeef
fix: provider model sync pruning and dynamic antigravity MITM proxy m…
herjarsa May 29, 2026
1461a78
feat(usage): per-API-key token limits scoped to model/provider/global…
mugnimaestra May 29, 2026
1f5c215
fix(audio): build multipart body manually to preserve Content-Type (#…
soyelmismo May 29, 2026
ebae877
refactor: remove agent skill documentation files and streamline maint…
diegosouzapw May 29, 2026
9ff906b
Merge branch release/v3.8.6 into fix/windsurf-login-2026-05-29 (resol…
diegosouzapw May 29, 2026
1bf73fe
Merge branch release/v3.8.6 into fix/i18n-pt-BR-translations (resolve…
diegosouzapw May 29, 2026
dd5098b
Merge branch release/v3.8.6 into fix/interleaved-reasoning-replay-gat…
diegosouzapw May 29, 2026
f1eb08c
Merge branch release/v3.8.6 into fix/gemini-antigravity-tool-call-nor…
diegosouzapw May 29, 2026
fb7a9f2
test(stabilization): resolve unit test failures in blackbox-web, sche…
diegosouzapw May 29, 2026
fd0b993
fix(security): mitigate Socket.dev supply-chain findings + secrets op…
diegosouzapw May 29, 2026
5f0cf31
fix(security): redact public Firebase Web key from windsurf spec; doc…
diegosouzapw May 29, 2026
8756430
fix(ci): resolve release/v3.8.6 gate failures (docs-sync, any-budget,…
diegosouzapw May 29, 2026
7714b09
feat(combo): Zero-Latency Combos (Hedging, Proactive Compression, Pre…
herjarsa May 29, 2026
1682dcb
feat: implement automated skill workflows and update system configura…
diegosouzapw May 29, 2026
5ac1e99
test: eliminate dynamic cast warnings in cloud-sync unit test
diegosouzapw May 29, 2026
e834279
test: isolate services-branch-hardening database directory to avoid c…
diegosouzapw May 29, 2026
10111ae
feat(providers): add 7 new web-cookie providers + research catalog + …
oyi77 May 29, 2026
d78088f
fix(typecheck): resolve typecheck errors in combo spec and compressio…
diegosouzapw May 29, 2026
067e049
feat(api,oauth): add `agy` (Antigravity CLI) standalone provider with…
diegosouzapw May 29, 2026
2483b2d
fix(security): redact public Firebase Web key from windsurf spec (#2896)
diegosouzapw May 29, 2026
7d398d1
Pr 2871 (#2897)
diegosouzapw May 29, 2026
e73a2ea
merge: pull request #2837 from gitlawb specialty validators
diegosouzapw May 29, 2026
65c7ab8
merge: pull request #2887 from oyi77 web cookie providers
diegosouzapw May 29, 2026
62856d8
perf(ram): reduce server memory footprint
May 29, 2026
46a9417
fix: address Gemini review feedback
May 29, 2026
6fdd312
chore(docs): refresh generated docs collection index
diegosouzapw May 29, 2026
fee3d39
fix: wrap controller.close() in try/catch (chatgpt-web)
May 29, 2026
f86f24a
docs: update generated browser docs collection manifest
diegosouzapw May 29, 2026
e476835
fix: address kilo-code-bot review feedback
May 29, 2026
a15750d
Add self-service API key usage status (#2908)
guanbear May 29, 2026
efcd062
Fix analytics history and log token accounting (#2904)
htkarabulut May 29, 2026
72546c8
chore: merge release/v3.8.6 into perf/ram-optimizations and resolve n…
diegosouzapw May 29, 2026
9dc79e9
chore: resolve conflicts with release/v3.8.7
diegosouzapw May 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ const nextConfig = {
"thread-stream",
"pino-abstract-transport",
"better-sqlite3",
"sql.js",
"node-machine-id",
"keytar",
"wreq-js",
Expand Down Expand Up @@ -175,6 +174,43 @@ const nextConfig = {
...(config.ignoreWarnings || []),
isNextIntlExtractorDynamicImportWarning,
];
config.optimization = config.optimization || {};
config.optimization.splitChunks = {
...config.optimization.splitChunks,
cacheGroups: {
...(config.optimization.splitChunks?.cacheGroups || {}),
recharts: {
test: /[\\/]node_modules[\\/]recharts[\\/]/,
name: "vendor-recharts",
chunks: "all",
priority: 20,
},
lobeIcons: {
test: /[\\/]node_modules[\\/]@lobehub[\\/]icons[\\/]/,
name: "vendor-lobe-icons",
chunks: "all",
priority: 20,
},
monaco: {
test: /[\\/]node_modules[\\/]monaco-editor[\\/]/,
name: "vendor-monaco",
chunks: "all",
priority: 20,
},
xyflow: {
test: /[\\/]node_modules[\\/]@xyflow[\\/]/,
name: "vendor-xyflow",
chunks: "all",
priority: 20,
},
mermaid: {
test: /[\\/]node_modules[\\/]mermaid[\\/]/,
name: "vendor-mermaid",
chunks: "all",
priority: 20,
},
},
};

if (isMinimalBuild) {
// Mirror the turbopack.resolveAlias entries for webpack-built artifacts.
Expand Down
86 changes: 68 additions & 18 deletions open-sse/config/audioRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ export interface AudioProvider {
models: AudioModel[];
}

export const AUDIO_TRANSCRIPTION_PROVIDERS: Record<string, AudioProvider> = {
let _AUDIO_TRANSCRIPTION_PROVIDERS: Record<string, AudioProvider> | null = null;

function getOrCreateTranscriptionProviders(): Record<string, AudioProvider> {
if (!_AUDIO_TRANSCRIPTION_PROVIDERS) {
_AUDIO_TRANSCRIPTION_PROVIDERS = {
openai: {
id: "openai",
baseUrl: "https://api.openai.com/v1/audio/transcriptions",
Expand Down Expand Up @@ -145,9 +149,38 @@ export const AUDIO_TRANSCRIPTION_PROVIDERS: Record<string, AudioProvider> = {
{ id: "elevenlabs/audio-isolation", name: "ElevenLabs Audio Isolation" },
],
},
};
};
}
return _AUDIO_TRANSCRIPTION_PROVIDERS;
}

export const AUDIO_SPEECH_PROVIDERS: Record<string, AudioProvider> = {
export const AUDIO_TRANSCRIPTION_PROVIDERS: Record<string, AudioProvider> = new Proxy({} as Record<string, AudioProvider>, {
get(_, key: string) {
return getOrCreateTranscriptionProviders()[key];
},
ownKeys() {
return Reflect.ownKeys(getOrCreateTranscriptionProviders());
},
has(_, key) {
return key in getOrCreateTranscriptionProviders();
},
getOwnPropertyDescriptor(_, key) {
if (key in getOrCreateTranscriptionProviders()) {
return { configurable: true, enumerable: true, value: getOrCreateTranscriptionProviders()[key as string] };
}
return undefined;
},
});

export function getTranscriptionProviders(): Record<string, AudioProvider> {
return getOrCreateTranscriptionProviders();
}

let _AUDIO_SPEECH_PROVIDERS: Record<string, AudioProvider> | null = null;

function getOrCreateSpeechProviders(): Record<string, AudioProvider> {
if (!_AUDIO_SPEECH_PROVIDERS) {
_AUDIO_SPEECH_PROVIDERS = {
openai: {
id: "openai",
baseUrl: "https://api.openai.com/v1/audio/speech",
Expand Down Expand Up @@ -367,22 +400,39 @@ export const AUDIO_SPEECH_PROVIDERS: Record<string, AudioProvider> = {
{ id: "mimo-v2.5-tts-voiceclone", name: "MiMo V2.5 Voice Clone" },
],
},
};
};
}
return _AUDIO_SPEECH_PROVIDERS;
}

export const AUDIO_SPEECH_PROVIDERS: Record<string, AudioProvider> = new Proxy({} as Record<string, AudioProvider>, {
get(_, key: string) {
return getOrCreateSpeechProviders()[key];
},
ownKeys() {
return Reflect.ownKeys(getOrCreateSpeechProviders());
},
has(_, key) {
return key in getOrCreateSpeechProviders();
},
getOwnPropertyDescriptor(_, key) {
if (key in getOrCreateSpeechProviders()) {
return { configurable: true, enumerable: true, value: getOrCreateSpeechProviders()[key as string] };
}
return undefined;
},
});

/**
* Get transcription provider config by ID
*/
export function getTranscriptionProvider(providerId: string): AudioProvider | null {
return AUDIO_TRANSCRIPTION_PROVIDERS[providerId] || null;
export function getSpeechProviders(): Record<string, AudioProvider> {
return getOrCreateSpeechProviders();
}

/**
* Get speech provider config by ID
*/
export function getTranscriptionProvider(providerId: string): AudioProvider | null {
return getOrCreateTranscriptionProviders()[providerId] || null;
}
export function getSpeechProvider(providerId: string): AudioProvider | null {
return AUDIO_SPEECH_PROVIDERS[providerId] || null;
return getOrCreateSpeechProviders()[providerId] || null;
}

export interface ProviderNodeRow {
prefix: string;
name: string;
Expand Down Expand Up @@ -446,11 +496,11 @@ export function parseTranscriptionModel(
modelStr: string | null,
dynamicProviders?: AudioProvider[]
) {
return parseAudioModel(modelStr, AUDIO_TRANSCRIPTION_PROVIDERS, dynamicProviders);
return parseAudioModel(modelStr, getOrCreateTranscriptionProviders(), dynamicProviders);
}

export function parseSpeechModel(modelStr: string | null, dynamicProviders?: AudioProvider[]) {
return parseAudioModel(modelStr, AUDIO_SPEECH_PROVIDERS, dynamicProviders);
return parseAudioModel(modelStr, getOrCreateSpeechProviders(), dynamicProviders);
}

/**
Expand All @@ -459,7 +509,7 @@ export function parseSpeechModel(modelStr: string | null, dynamicProviders?: Aud
export function getAllAudioModels() {
const models = [];

for (const [providerId, config] of Object.entries(AUDIO_TRANSCRIPTION_PROVIDERS)) {
for (const [providerId, config] of Object.entries(getOrCreateTranscriptionProviders())) {
for (const model of config.models) {
models.push({
id: model.id.startsWith(`${providerId}/`) ? model.id : `${providerId}/${model.id}`,
Expand All @@ -470,7 +520,7 @@ export function getAllAudioModels() {
}
}

for (const [providerId, config] of Object.entries(AUDIO_SPEECH_PROVIDERS)) {
for (const [providerId, config] of Object.entries(getOrCreateSpeechProviders())) {
for (const model of config.models) {
models.push({
id: model.id.startsWith(`${providerId}/`) ? model.id : `${providerId}/${model.id}`,
Expand Down
6 changes: 3 additions & 3 deletions open-sse/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,9 @@ export const PROVIDER_PROFILES = {
// These are intentionally HIGH — they won't restrict normal usage.
// Real limits are learned from provider response headers.
export const DEFAULT_API_LIMITS = {
requestsPerMinute: 100, // 100 RPM (most APIs allow 60-600 RPM)
minTimeBetweenRequests: 200, // 200ms minimum gap
concurrentRequests: 10, // Max 10 parallel per provider
requestsPerMinute: 60, // 60 RPM (reduced from 100 — saves Bottleneck queue memory)
minTimeBetweenRequests: 350, // 350ms minimum gap (increased from 200)
concurrentRequests: 6, // Max 6 parallel per provider (reduced from 10)
};

// Skip patterns - requests containing these texts will bypass provider
Expand Down
45 changes: 37 additions & 8 deletions open-sse/config/embeddingRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ export function buildDynamicEmbeddingProvider(node: EmbeddingProviderNodeRow): E
};
}

export const EMBEDDING_PROVIDERS: Record<string, EmbeddingProvider> = {
let _EMBEDDING_PROVIDERS: Record<string, EmbeddingProvider> | null = null;

function getOrCreateEmbeddingProviders(): Record<string, EmbeddingProvider> {
if (!_EMBEDDING_PROVIDERS) {
_EMBEDDING_PROVIDERS = {
cohere: {
id: "cohere",
baseUrl: "https://api.cohere.com/v2/embed",
Expand Down Expand Up @@ -233,7 +237,32 @@ export const EMBEDDING_PROVIDERS: Record<string, EmbeddingProvider> = {
{ id: "jina-colbert-v2", name: "Jina ColBERT v2", dimensions: 128 },
],
},
};
};
}
return _EMBEDDING_PROVIDERS;
}

export const EMBEDDING_PROVIDERS: Record<string, EmbeddingProvider> = new Proxy({} as Record<string, EmbeddingProvider>, {
get(_, key: string) {
return getOrCreateEmbeddingProviders()[key];
},
ownKeys() {
return Reflect.ownKeys(getOrCreateEmbeddingProviders());
},
has(_, key) {
return key in getOrCreateEmbeddingProviders();
},
getOwnPropertyDescriptor(_, key) {
if (key in getOrCreateEmbeddingProviders()) {
return { configurable: true, enumerable: true, value: getOrCreateEmbeddingProviders()[key as string] };
}
return undefined;
},
});

export function getEmbeddingProviders(): Record<string, EmbeddingProvider> {
return getOrCreateEmbeddingProviders();
}

const EMBEDDING_PROVIDER_ALIASES: Record<string, string> = {
jina: "jina-ai",
Expand All @@ -246,7 +275,7 @@ function resolveEmbeddingProviderId(providerId: string): string {

function normalizeProviderScopedModelId(providerId: string, modelId: string): string {
const resolvedProvider = resolveEmbeddingProviderId(providerId);
const provider = EMBEDDING_PROVIDERS[resolvedProvider];
const provider = getOrCreateEmbeddingProviders()[resolvedProvider];
if (provider?.models.some((model) => model.id === modelId)) return modelId;

const providerScopedModelId = `${resolvedProvider}/${modelId}`;
Expand All @@ -265,7 +294,7 @@ function toProviderScopedModelId(providerId: string, modelId: string): string {
* Get embedding provider config by ID
*/
export function getEmbeddingProvider(providerId: string): EmbeddingProvider | null {
return EMBEDDING_PROVIDERS[resolveEmbeddingProviderId(providerId)] || null;
return getOrCreateEmbeddingProviders()[resolveEmbeddingProviderId(providerId)] || null;
}

/**
Expand All @@ -284,15 +313,15 @@ export function parseEmbeddingModel(
const rawProvider = modelStr.slice(0, slashIdx);
const resolvedProvider = resolveEmbeddingProviderId(rawProvider);

if (EMBEDDING_PROVIDERS[resolvedProvider]) {
if (getOrCreateEmbeddingProviders()[resolvedProvider]) {
return {
provider: resolvedProvider,
model: normalizeProviderScopedModelId(resolvedProvider, modelStr.slice(slashIdx + 1)),
};
}

// Phase 1: Try each hardcoded provider prefix
for (const [providerId] of Object.entries(EMBEDDING_PROVIDERS)) {
for (const [providerId] of Object.entries(getOrCreateEmbeddingProviders())) {
if (modelStr.startsWith(providerId + "/")) {
return {
provider: providerId,
Expand All @@ -315,7 +344,7 @@ export function parseEmbeddingModel(
}

// No provider prefix — search hardcoded providers for the model
for (const [providerId, config] of Object.entries(EMBEDDING_PROVIDERS)) {
for (const [providerId, config] of Object.entries(getOrCreateEmbeddingProviders())) {
if (config.models.some((m) => m.id === modelStr)) {
return { provider: providerId, model: modelStr };
}
Expand All @@ -334,7 +363,7 @@ export function getAllEmbeddingModels() {
provider: string;
dimensions: number | undefined;
}> = [];
for (const [providerId, config] of Object.entries(EMBEDDING_PROVIDERS)) {
for (const [providerId, config] of Object.entries(getOrCreateEmbeddingProviders())) {
for (const model of config.models) {
models.push({
id: toProviderScopedModelId(providerId, model.id),
Expand Down
47 changes: 36 additions & 11 deletions open-sse/config/imageRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,16 @@ function resolveImageModelAlias(modelStr) {
}

function findImageModelConfig(providerId, modelId) {
const provider = IMAGE_PROVIDERS[providerId];
const provider = getOrCreateImageProviders()[providerId];
if (!provider) return null;
return provider.models.find((model) => model.id === modelId) || null;
}

export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
let _IMAGE_PROVIDERS: Record<string, ImageProviderConfig> | null = null;

function getOrCreateImageProviders(): Record<string, ImageProviderConfig> {
if (!_IMAGE_PROVIDERS) {
_IMAGE_PROVIDERS = {
openai: {
id: "openai",
baseUrl: "https://api.openai.com/v1/images/generations",
Expand Down Expand Up @@ -540,14 +544,35 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
supportedSizes: ["1024x1024", "1024x1280", "1280x1024"],
},
};
}
return _IMAGE_PROVIDERS;
}

/**
* Get image provider config by ID
*/
export function getImageProvider(providerId) {
return IMAGE_PROVIDERS[providerId] || null;
export function getImageProviders(): Record<string, ImageProviderConfig> {
return getOrCreateImageProviders();
}

export const IMAGE_PROVIDERS = new Proxy({} as Record<string, ImageProviderConfig>, {
get(_, key: string) {
return getOrCreateImageProviders()[key];
},
ownKeys() {
return Reflect.ownKeys(getOrCreateImageProviders());
},
has(_, key) {
return key in getOrCreateImageProviders();
},
getOwnPropertyDescriptor(_, key) {
if (key in getOrCreateImageProviders()) {
return { configurable: true, enumerable: true, value: getOrCreateImageProviders()[key as string] };
}
return undefined;
},
});

export function getImageProvider(providerId) {
return getOrCreateImageProviders()[providerId] || null;
}
/**
* Parse image model string (format: "provider/model")
* Returns { provider, model }
Expand All @@ -561,7 +586,7 @@ export function parseImageModel(modelStr) {
}

// Try each provider prefix
for (const [providerId, config] of Object.entries(IMAGE_PROVIDERS)) {
for (const [providerId, config] of Object.entries(getOrCreateImageProviders())) {
if (modelStr.startsWith(providerId + "/")) {
const model = modelStr.slice(providerId.length + 1);
const aliased =
Expand All @@ -578,7 +603,7 @@ export function parseImageModel(modelStr) {
}

// No provider prefix — try to find the model in every provider
for (const [providerId, config] of Object.entries(IMAGE_PROVIDERS)) {
for (const [providerId, config] of Object.entries(getOrCreateImageProviders())) {
if (config.models.some((m) => m.id === modelStr)) {
return { provider: providerId, model: modelStr };
}
Expand All @@ -592,7 +617,7 @@ export function parseImageModel(modelStr) {
*/
export function getAllImageModels() {
const models = [];
for (const [providerId, config] of Object.entries(IMAGE_PROVIDERS)) {
for (const [providerId, config] of Object.entries(getOrCreateImageProviders())) {
for (const model of config.models) {
models.push({
id: `${providerId}/${model.id}`,
Expand All @@ -606,7 +631,7 @@ export function getAllImageModels() {
}
for (const [alias, target] of Object.entries(IMAGE_MODEL_ALIASES)) {
if (!target.listInCatalog) continue;
const providerConfig = IMAGE_PROVIDERS[target.provider];
const providerConfig = getOrCreateImageProviders()[target.provider];
const modelConfig = findImageModelConfig(target.provider, target.model);
models.push({
id: alias,
Expand Down
Loading