Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
8 changes: 4 additions & 4 deletions electron/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"electron-updater": "^6.8.9"
},
"devDependencies": {
"electron": "^42.5.1",
"electron": "^43.1.0",
"electron-builder": "^26.15.3"
},
"overrides": {
Expand All @@ -42,6 +42,7 @@
"appId": "online.omniroute.desktop",
"productName": "OmniRoute",
"copyright": "Copyright © 2025 OmniRoute",
"buildDependenciesFromSource": true,
"directories": {
"output": "dist-electron",
"buildResources": "assets"
Expand Down
11 changes: 10 additions & 1 deletion open-sse/executors/mimocode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*
* Only the "mimo-auto" model is supported (1M context, 128K output).
* Supports multiple accounts: N fingerprints → N JWTs → round-robin with cooldown.
* On 429, account enters cooldown (exponential backoff). On 401/403, JWT is re-bootstrapped.
* On 429 or 400, account enters cooldown (exponential backoff). On 401/403, JWT is re-bootstrapped.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Revert this comment change since we should not treat HTTP 400 as a cooldown trigger.

Suggested change
* On 429 or 400, account enters cooldown (exponential backoff). On 401/403, JWT is re-bootstrapped.
* On 429, account enters cooldown (exponential backoff). On 401/403, JWT is re-bootstrapped.

*/

import * as crypto from "node:crypto";
Expand Down Expand Up @@ -504,6 +504,15 @@ export class MimocodeExecutor extends BaseExecutor {
continue;
}

if (resp.status === 400) {
this.markCooldown(account);
log?.warn?.(
"MIMOCODE",
`Bad request on account ${account.fingerprint.slice(0, 8)}, trying next…`
);
continue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Critical Bug: Request-Specific 400 Errors Triggering Account Cooldown Cascade

Treating HTTP 400 Bad Request responses as account-specific errors and putting the account on cooldown introduces a critical bug:

  1. Request-Specific vs. Account-Specific: HTTP 400 indicates that the request payload itself is malformed or semantically incorrect (e.g., missing reasoning_content in multi-turn tool calls, or passing a list instead of a string in tool messages). These are client-side/request-specific issues, not account-specific or proxy-specific issues.
  2. Account Cooldown Cascade: If a client sends a request that triggers a 400 error, this code will mark the current account on cooldown, then rotate to the next account and send the exact same request. The next account will also return 400, and also be marked on cooldown. This will repeat until all accounts in the pool are marked on cooldown, effectively disabling the entire Mimocode provider for all other users/sessions.
  3. Misleading Error to Client: Instead of receiving the actual 400 Bad Request error with the upstream's error message (which helps them debug their request), the client will receive a generic 502 All accounts exhausted error.

Style Guide Violation

Additionally, this PR changes production code under open-sse/ but does not include any tests, violating Rule 9 of the Repository Style Guide.

Recommendation

Remove this 400 status check entirely. Let 400 responses fall through to markSuccess and return immediately to the client so they can see the actual error and fix their request.

References
  1. Always include tests when changing production code (src/, open-sse/, electron/, bin/). (link)


this.markSuccess(account);
const respHeaders: Record<string, string> = {};
resp.headers.forEach((v, k) => {
Expand Down
6 changes: 6 additions & 0 deletions open-sse/utils/proxyDispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,12 @@ export function createProxyDispatcher(proxyUrl: string): Dispatcher {
// valid; the cast suppresses the spurious missing-`port` error.
dispatcher = new ProxyAgent({
uri: cleanUri,
// undici 8.6+ forwards plain-HTTP requests through the proxy as an origin
// request (GET http://host/…) instead of a CONNECT tunnel; upstream proxies
// that only speak CONNECT then reject it (501). OmniRoute tunnels ALL proxied
// traffic (HTTP + HTTPS) via CONNECT, so force tunneling. Unknown option on
// undici <8.6 → silently ignored (that version already tunneled by default).
proxyTunnel: true,
...proxyDispatcherOptions,
...(family !== null
? { proxyTls: { family, autoSelectFamily: false } as ProxyAgent.Options["proxyTls"] }
Expand Down
Loading
Loading