Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
cf06adf
chore(release): open v3.8.22 development cycle
diegosouzapw Jun 11, 2026
bb6d5a2
refactor(dashboard): extract ProviderDetailPageClient — #3501 Phase 0…
diegosouzapw Jun 11, 2026
3a2cfd6
refactor(dashboard): extract auth-import modals — #3501 Phase 1a (#3634)
diegosouzapw Jun 11, 2026
f6632c0
fix(db): reclassify localDb unexported modules as intentionally-inter…
diegosouzapw Jun 11, 2026
1d147b6
refactor(db): move call_logs aggregations into callLogStats db module…
diegosouzapw Jun 11, 2026
ee53ced
refactor(dashboard): extract EditCompatibleNodeModal — #3501 Phase 1b…
diegosouzapw Jun 11, 2026
9e9262b
refactor(db): move community_servers SQL into gamification db module …
diegosouzapw Jun 11, 2026
581878a
refactor(db): move usage_history SQL into usageAnalytics module (#350…
diegosouzapw Jun 11, 2026
53af75f
refactor(db): move skills UPDATE + db-backups SQL into db modules (#3…
diegosouzapw Jun 11, 2026
870f825
refactor(db): move usage_logs/semantic_cache/proxy_logs SQL into db m…
diegosouzapw Jun 11, 2026
c4bdd4a
chore(db-gate): reclassify external-DB reads, fully close #3500 (#3649)
diegosouzapw Jun 11, 2026
2ec54d0
refactor(dashboard): extract pure helpers to providerPageHelpers — #3…
diegosouzapw Jun 11, 2026
d8b9759
refactor(dashboard): extract remaining shared helpers to providerPage…
diegosouzapw Jun 11, 2026
c5b2141
fix(reasoning): replay reasoning_content on plain DeepSeek turns (#16…
adivekar-utexas Jun 11, 2026
8ecfe75
fix(kiro): route enterprise IAM Identity Center accounts to their reg…
artickc Jun 11, 2026
9574d57
refactor: small code cleanup (#3523)
androw Jun 11, 2026
337f16c
fix(combo): skip same-provider targets on 408/500/502/503/504/524 err…
herjarsa Jun 11, 2026
5a5d2ee
feat(providers): add MiMoCode free-tier provider with bootstrap JWT a…
pizzav-xyz Jun 11, 2026
3813fa4
Log Responses WebSocket calls in history (#3616)
kkkayye Jun 11, 2026
2b39b83
Add Claude Code routing preference for unprefixed Claude models (#3540)
Witroch4 Jun 11, 2026
a702a9e
docs(changelog): credit #3632/#3631/#3637/#3659/#3540/#3616/#3523 (v3…
diegosouzapw Jun 11, 2026
60e38a7
fix(mimocode): add required authHeader:"none" to registry entry (#365…
diegosouzapw Jun 11, 2026
3ce157d
fix(responses): detect stream readiness for tool-call-only and object…
diegosouzapw Jun 11, 2026
bc164d0
fix(mitm): remove duplicated 'Command failed:' error prefix (#3641) (…
diegosouzapw Jun 11, 2026
0303932
fix(cli): honor HERMES_HOME for Hermes Agent config path (#3628) (#3663)
diegosouzapw Jun 11, 2026
8c7f373
fix(api): fetch live OpenCode model catalog for no-auth model picker …
diegosouzapw Jun 11, 2026
47155fa
fix(api): flag provider topology error state by current status, not s…
diegosouzapw Jun 11, 2026
7e7fdbd
fix(electron): launch peer-stamping server-ws.mjs entrypoint to avoid…
diegosouzapw Jun 11, 2026
34c2783
fix(dashboard): restore home topology live in-flight pulse (#3507) (#…
diegosouzapw Jun 11, 2026
8a2d86a
fix(oauth): name Kiro/AWS auto-imported accounts and dedupe by profil…
diegosouzapw Jun 11, 2026
eb32352
fix(resilience): clear stale transient connection cooldowns on startu…
diegosouzapw Jun 11, 2026
8f281ed
fix(i18n): use logical CSS direction utilities for sidebar and key ov…
diegosouzapw Jun 11, 2026
acadb59
fix(dashboard): honor auto-hide and switch to visible filter on passt…
diegosouzapw Jun 11, 2026
9196a54
refactor(dashboard): extract AddApiKeyModal + EditConnectionModal — #…
diegosouzapw Jun 11, 2026
a325339
docs(changelog): reconcile v3.8.22 — credit #3621/#3622 + MiMoCode fo…
diegosouzapw Jun 11, 2026
623806b
refactor(dashboard): extract ConnectionRow + ModelCompatPopover + Sil…
diegosouzapw Jun 11, 2026
dbbcc65
feat(obsidian): add WebDAV config route + encrypt creds at rest (#348…
diegosouzapw Jun 11, 2026
bbace56
feat(obsidian): add /api/v1/webdav file server for Obsidian vault syn…
diegosouzapw Jun 11, 2026
ef82f10
fix(combo): stop premature context compaction — real auto-combo windo…
diegosouzapw Jun 11, 2026
592554d
feat(dashboard): deactivate/activate accounts from the quota overview…
leninejunior Jun 11, 2026
89fb1dd
fix(dashboard): close review gaps in bulk provider connection actions…
leninejunior Jun 11, 2026
57f8137
refactor(dashboard): extract useModelCompatState hook + model section…
diegosouzapw Jun 11, 2026
831f8c7
refactor(dashboard): extract useProviderConnections/Settings/Models h…
diegosouzapw Jun 11, 2026
b7300a5
feat: gemini - handle known ratelimits
hartmark Jun 11, 2026
c89aaa7
Merge remote-tracking branch 'origin/release/v3.8.23' into feature/ge…
diegosouzapw Jun 12, 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

---

## [3.8.22] — TBD
## [3.8.23] — TBD

---
Expand All @@ -28,13 +29,15 @@
- **Provider-detail god-component decomposition — Phase 2b (remaining shared helpers→leaf)** ([#3501]): extended `providers/[id]/providerPageHelpers.ts` with all remaining pure helpers needed by the heavy modals (`AddApiKeyModal`/`EditConnectionModal`) before they can be extracted. Moved 22 symbols: web-session credential label/hint/check/title helpers; upstream-headers helpers (`upstreamHeadersRecordsEqual`, `headerRowsToRecord`, `effectiveUpstreamHeadersForProtocol`, `anyUpstreamHeadersBadge`, `getProtoSlice`) plus their `HeaderDraftRow`/`CompatModelRow`/`CompatModelMap`/`CompatByProtocolMap` types; Codex consts and helpers (`CODEX_REASONING_STRENGTH_OPTIONS`, `CODEX_ACCOUNT_SERVICE_TIER_VALUES`, `CODEX_GLOBAL_SERVICE_MODE_VALUES`, `getCodexServiceTierLabel`, `normalizeCodexLimitPolicy`, `getCodexRequestDefaults`, `getClaudeCodeCompatibleRequestDefaults`); misc helpers (`compatProtocolLabelKey`, `extractCommandCodeCredentialInput`, `normalizeAndValidateHttpBaseUrl`, `SILICONFLOW_ENDPOINTS`, `CommandCodeAuthFlowState`). New transitive imports wired into the leaf: `MODEL_COMPAT_PROTOCOL_KEYS` (`@/shared/constants/modelCompat`), `CodexServiceTier`/`getCodexRequestDefaults`/`getClaudeCodeCompatibleRequestDefaults` (`@/lib/providers/requestDefaults`), `CodexGlobalServiceMode` (`@/lib/providers/codexFastTier`), `WebSessionCredentialRequirement` (`./webSessionCredentials`). `ProviderDetailPageClient.tsx`: 10,288 → 9,980 LOC. Leaf module: 589 LOC (acyclic). 25-assertion unit test suite passes; smoke test 3/3; no import cycles. Co-authored with @oyi77.
- **Provider-detail god-component decomposition — Phase 2 (helpers→lib)** ([#3501]): extracted the pure shared helpers — `ProviderMessageTranslator`/`LocalProviderMetadata` types, `providerText`/`providerCountText`/`readBooleanToggle`, and the provider base-URL + routing-tag/excluded-model parse/format block — into a new leaf `providers/[id]/providerPageHelpers.ts` (imports only `@/shared`, so the client and modals share them with no import cycle). `ProviderDetailPageClient.tsx`: 10,435 → 10,288 LOC. Unblocks extracting the heavier `AddApiKeyModal`/`EditConnectionModal` (which depend on these helpers) without cycling. The Phase 0 smoke test caught a missing transitive import (`isSelfHostedChatProvider`) at mount — now wired + locked by a new helpers unit test (12 assertions). Co-authored with @oyi77.

- **#3500 fully resolved** — Hard Rule #5 (no raw SQL in route handlers): all 13 internal offenders migrated to `src/lib/db/` modules across slices (call_logs, usage_history/daily_usage_summary, community_servers, usage_logs, semantic_cache, proxy_logs, skills UPDATE, db-backups). The gate's `KNOWN_RAW_SQL` set is renamed to `EXTERNAL_DB_ALLOWED` (with a back-compat alias) and now holds only the **2 external-DB reads** (`oauth/cursor/auto-import`, `oauth/kiro/auto-import`) — these open *another app's* SQLite to import credentials, so by design they cannot live in OmniRoute's `db/` domain. The gate still blocks any NEW raw SQL against OmniRoute's DB.
- **#3500 fully resolved** — Hard Rule #5 (no raw SQL in route handlers): all 13 internal offenders migrated to `src/lib/db/` modules across slices (call*logs, usage_history/daily_usage_summary, community_servers, usage_logs, semantic_cache, proxy_logs, skills UPDATE, db-backups). The gate's `KNOWN_RAW_SQL` set is renamed to `EXTERNAL_DB_ALLOWED` (with a back-compat alias) and now holds only the **2 external-DB reads** (`oauth/cursor/auto-import`, `oauth/kiro/auto-import`) — these open \_another app's* SQLite to import credentials, so by design they cannot live in OmniRoute's `db/` domain. The gate still blocks any NEW raw SQL against OmniRoute's DB.
- **chore(db-gate):** reclassify `KNOWN_UNEXPORTED` → `INTENTIONALLY_INTERNAL` in `scripts/check/check-db-rules.mjs` ([#3499]): a full audit of all 25 db modules confirmed each is consumed via direct/dynamic import per Hard Rule #2 ("Never barrel-import from localDb.ts"). The old framing labelled them as "debt", which was misleading — they are the correct pattern. The gate's blocking behaviour is unchanged (a NEW unexported module still fails); only the name, comments, and per-module justifications were updated to reflect audited truth. Four modules flagged `DEAD?` (`compressionScheduler`, `discovery`, `pluginMetrics`, `prompts`) have zero production importers and are documented as schema-reserved. A new regression-guard test (`tests/unit/check-db-rules-classification.test.ts`) asserts every non-dead module in the set has ≥1 real importer, so a future consumer removal surfaces as a test failure requiring explicit reclassification.
- **refactor(db): move `call_logs` aggregations into `callLogStats` db module** ([#3500]): extracted raw SQL from three route handlers (`/api/provider-metrics`, `/api/search/stats`, `/api/v1/search/analytics`) into a new `src/lib/db/callLogStats.ts` domain module (`getProviderMetrics`, `getSearchProviderStats`, `getRecentSearchLogs`, `getSearchAggregateStats`, `getSearchProviderCounts`). First slice of #3500 (call_logs cluster). Behavior unchanged; the three routes are removed from `KNOWN_RAW_SQL` in the gate. Validated with TDD unit tests (6 assertions seeding an in-memory SQLite fixture).
- **refactor(db): move `usage_history`/`daily_usage_summary` SQL into `usageAnalytics` db module** ([#3500]): extracted all inline `db.prepare(...)` calls from two route handlers (`/api/usage/analytics`, `/api/settings/export-json`) into a new `src/lib/db/usageAnalytics.ts` module and extended `src/lib/db/callLogStats.ts` with `getFallbackStats`. New exports: `buildUnifiedSource`, `buildPresetUnifiedSource` (UNION CTE builders), plus 12 typed query functions covering summary, daily, daily-cost, heatmap, model, provider, account, api-key, service-tier, weekly-pattern, and preset-cost aggregations, plus `getAllUsageHistory`/`getAllDomainCostHistory`/`getAllDomainBudgets` for backup export. Second slice of #3500. `KNOWN_RAW_SQL` drops from 12 → 10. Validated with 21 TDD unit tests (`tests/unit/db-usage-analytics-3500.test.ts`) seeding a temp SQLite fixture.

- **refactor(db): move `community_servers` auth look-up into `gamification` db module** ([#3500]): extracted raw SQL from two federation route handlers (`/api/gamification/federation/leaderboard`, `/api/gamification/federation/score`) into a new `getConnectedServerByKeyHash(apiKeyHash)` function in `src/lib/db/gamification.ts`. Third slice of #3500 (gamification federation cluster). Behavior unchanged; the two routes are removed from `KNOWN_RAW_SQL` in the gate. Validated with TDD unit tests (3 assertions seeding a temp SQLite fixture).

- **refactor(db): move `skills UPDATE` + `db-backups` SQL into db modules** ([#3500]): fifth slice of #3500. Extracted the dynamic `UPDATE skills SET …` from `src/app/api/skills/[id]/route.ts` into a new `src/lib/db/skills.ts` module (`updateSkill(id, patch)`). The dynamic SET clause is injection-safe: column names are validated against a hard-coded allowlist of known writable columns before being interpolated; unknown keys are silently ignored. Extended `src/lib/db/backup.ts` with three new functions: `exportAllSummaryRows()` (multi-table SELECT for key_value / combos / provider_connections / api_keys, used by exportAll), `getTableNamesFromAdapter()` (sqlite_master introspection via an adapter arg, used by import validation), and `countImportedRows()` (post-import COUNT(*) per table). The backup domain module is the correct home for sqlite_master introspection — it is not "raw SQL in a route" once moved there. `KNOWN_RAW_SQL` drops by 3 (from 8 → 5). Validated with 11 TDD unit tests (`tests/unit/db-backups-skills-3500.test.ts`).
- **refactor(db): move `skills UPDATE` + `db-backups` SQL into db modules** ([#3500]): fifth slice of #3500. Extracted the dynamic `UPDATE skills SET …` from `src/app/api/skills/[id]/route.ts` into a new `src/lib/db/skills.ts` module (`updateSkill(id, patch)`). The dynamic SET clause is injection-safe: column names are validated against a hard-coded allowlist of known writable columns before being interpolated; unknown keys are silently ignored. Extended `src/lib/db/backup.ts` with three new functions: `exportAllSummaryRows()` (multi-table SELECT for key_value / combos / provider_connections / api_keys, used by exportAll), `getTableNamesFromAdapter()` (sqlite_master introspection via an adapter arg, used by import validation), and `countImportedRows()` (post-import COUNT(\*) per table). The backup domain module is the correct home for sqlite_master introspection — it is not "raw SQL in a route" once moved there. `KNOWN_RAW_SQL` drops by 3 (from 8 → 5). Validated with 11 TDD unit tests (`tests/unit/db-backups-skills-3500.test.ts`).
- **refactor(db): move `usage_logs`/`semantic_cache`/`proxy_logs` SQL into db modules** ([#3500]): extracted raw `db.prepare(...)` SQL from three route handlers (`/api/analytics/auto-routing` → `usageLogs.ts`; `/api/cache/entries` → `semanticCache.ts`; `/api/logs/export` → `proxyLogs.ts`) into new `src/lib/db/` domain modules. New exports: `getAutoRoutingTotalCount`, `getAutoRoutingVariantBreakdown`, `getAutoRoutingTopProviders` (usage_logs), `listSemanticCacheEntries`, `deleteSemanticCacheBySignature`, `deleteSemanticCacheByModel` (semantic_cache), and `exportProxyLogsSince` (proxy_logs). Fourth slice of #3500. `KNOWN_RAW_SQL` drops from 8 → 5. Validated with 13 TDD unit tests (`tests/unit/db-logs-cache-3500.test.ts`) seeding temp SQLite fixtures.

Expand Down
34 changes: 34 additions & 0 deletions open-sse/config/geminiRateLimits.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"gemini-2.5-flash": { "rpm": 5, "rpd": 20, "tpm": 250000 },
"gemini-2.5-pro": { "rpm": 0, "rpd": 0, "tpm": 0 },
"gemini-2-flash": { "rpm": 0, "rpd": 0, "tpm": 0 },
"gemini-2-flash-lite": { "rpm": 0, "rpd": 0, "tpm": 0 },
"gemini-2.5-flash-tts": { "rpm": 3, "rpd": 10, "tpm": 10000 },
"gemini-2.5-pro-tts": { "rpm": 0, "rpd": 0, "tpm": 0 },
"imagen-4-generate": { "rpm": -1, "rpd": 25, "tpm": -1 },
"imagen-4-ultra-generate": { "rpm": -1, "rpd": 25, "tpm": -1 },
"imagen-4-fast-generate": { "rpm": -1, "rpd": 25, "tpm": -1 },
"gemma-4-26b-it": { "rpm": 15, "rpd": 1500, "tpm": -1 },
"gemma-4-31b-it": { "rpm": 15, "rpd": 1500, "tpm": -1 },
"gemini-embedding-exp-03-07": { "rpm": 100, "rpd": 1000, "tpm": 30000 },
"gemini-3.5-flash": { "rpm": 5, "rpd": 20, "tpm": 250000 },
"gemini-3.1-flash-lite": { "rpm": 15, "rpd": 500, "tpm": 250000 },
"gemini-3.1-pro": { "rpm": 0, "rpd": 0, "tpm": 0 },
"gemini-2.5-flash-lite": { "rpm": 10, "rpd": 20, "tpm": 250000 },
"nano-banana-gemini-2.5-flash-preview-image": { "rpm": 0, "rpd": 0, "tpm": 0 },
"nano-banana-pro-gemini-3-pro-image": { "rpm": 0, "rpd": 0, "tpm": 0 },
"nano-banana-2-gemini-3.1-flash-image": { "rpm": 0, "rpd": 0, "tpm": 0 },
"lyria-3-clip": { "rpm": 0, "rpd": 0, "tpm": 0 },
"lyria-3-pro": { "rpm": 0, "rpd": 0, "tpm": 0 },
"veo-3-generate": { "rpm": 0, "rpd": 0, "tpm": -1 },
"veo-3-fast-generate": { "rpm": 0, "rpd": 0, "tpm": -1 },
"veo-3-lite-generate": { "rpm": 0, "rpd": 0, "tpm": -1 },
"gemini-3.1-flash-tts": { "rpm": 3, "rpd": 10, "tpm": 10000 },
"gemini-robotics-er-1.5-preview": { "rpm": 10, "rpd": 20, "tpm": 250000 },
"gemini-robotics-er-1.6-preview": { "rpm": 5, "rpd": 20, "tpm": 250000 },
"computer-use-preview": { "rpm": 0, "rpd": 0, "tpm": 0 },
"gemini-embedding-exp-04-07": { "rpm": 100, "rpd": 1000, "tpm": 30000 },
"gemini-3.5-live-translate": { "rpm": -1, "rpd": -1, "tpm": 20000 },
"gemini-2.5-flash-native-audio-dialog": { "rpm": -1, "rpd": -1, "tpm": 1000000 },
"gemini-3-flash-live": { "rpm": -1, "rpd": -1, "tpm": 65000 }
}
7 changes: 7 additions & 0 deletions open-sse/handlers/chatCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ import {
getModelScopeRetryDelayMs,
isModelScopeProvider,
} from "../services/modelscopePolicy.ts";
import { incrementRequestCount } from "../services/geminiRateLimitTracker.ts";

const MEMORY_EXTRACTION_TEXT_LIMIT = 64 * 1024;

Expand Down Expand Up @@ -3797,6 +3798,12 @@ export async function handleChatCore({
});
const res = normalizeExecutorResult(rawExecutorResult);
trace("post_executor", { status: res?.response?.status });

// Track Gemini RPM + RPD request counts for 429 classification
if (provider === "gemini") {
incrementRequestCount(modelToCall);
}

updatePendingRequest(model, provider, connectionId, {
stage: "provider_response_started",
});
Expand Down
28 changes: 19 additions & 9 deletions open-sse/services/accountFallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
type FailureKind,
} from "../../src/shared/utils/classify429";
import { resolveUseUpstream429BreakerHints } from "../../src/shared/utils/providerHints";
import { isRpdExhausted, isRpmExhausted } from "./geminiRateLimitTracker.ts";

export type ProviderProfile = {
baseCooldownMs: number;
Expand Down Expand Up @@ -150,9 +151,6 @@ export const CREDITS_EXHAUSTED_SIGNALS = [
"credits exhausted",
"out of credits",
"payment required",
"resource has been exhausted",
"resource_exhausted",
"check quota",
"free tier of the model has been exhausted",
];

Expand Down Expand Up @@ -933,8 +931,9 @@ export function parseRetryFromErrorText(errorText: unknown): number | null {
// 2026-05-17T10:00:00Z" or "Please wait until 2026-05-17T10:00:00.000Z").
// Convert to a future-duration in milliseconds if it parses.
const isoMatch =
/\b(?:try again at|wait until|reset(?:s)? at|available at|retry after)\s+(\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)/i
.exec(msg);
/\b(?:try again at|wait until|reset(?:s)? at|available at|retry after)\s+(\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)/i.exec(
msg
);
if (isoMatch) {
const parsedTs = Date.parse(isoMatch[1]);
if (Number.isFinite(parsedTs)) {
Expand Down Expand Up @@ -1021,10 +1020,8 @@ export function classifyErrorText(errorText: unknown): RateLimitReasonValue {
const configuredRule = matchErrorRuleByText(errorText);
if (configuredRule?.reason) return configuredRule.reason;
if (lower.includes("rate_limit")) return RateLimitReason.RATE_LIMIT_EXCEEDED;
if (
lower.includes("resource exhausted") ||
lower.includes("high demand")
) return RateLimitReason.MODEL_CAPACITY;
if (lower.includes("resource exhausted") || lower.includes("high demand"))
return RateLimitReason.MODEL_CAPACITY;
if (
lower.includes("unauthorized") ||
lower.includes("invalid api key") ||
Expand Down Expand Up @@ -1414,6 +1411,19 @@ export function checkFallbackError(
}
}

// Gemini-specific: use known published RPM/RPD limits to distinguish 429 types.
// Gemini returns the same error body for both, so we use per-model request
// counters to decide: if daily count >= RPD → quota_exhausted (midnight lockout);
// if minute count >= RPM → rate_limit_exceeded (exponential backoff).
if (provider === "gemini" && status === HTTP_STATUS.RATE_LIMITED && _model) {
if (isRpdExhausted(_model)) {
return buildRetryableFallback(RateLimitReason.QUOTA_EXHAUSTED);
}
if (isRpmExhausted(_model)) {
return buildRetryableFallback(RateLimitReason.RATE_LIMIT_EXCEEDED);
}
}

const configuredRule =
isRateLimitStatus && !preserveQuota429
? matchErrorRuleByStatus(status)
Expand Down
Loading