Repository files navigation

SwitchLM

npm versionlicense

Local OpenAI-compatible routing proxy for Codex. SwitchLM exposes the Responses API and routes coding requests between Luna for simple work and Sol for heavier reasoning.

SwitchLM is published on npm as switchlm. The current release is 1.0.2.

Key benefits

  • Automatically routes simple tasks to Luna and heavier tasks to Sol using transparent deterministic heuristics.
  • Supports explicit model selection through router/luna and router/sol when automatic routing is not desired.
  • Connects to ChatGPT/Codex through OAuth, including local token storage and automatic access-token refresh.
  • Preserves OpenAI Responses API compatibility, including server-sent event streaming.
  • Exposes routing and token statistics through GET /stats and switchlm stats.
  • Runs locally with a small configuration and no database or additional classifier model.

Install

Install the published package globally:

npm install --global switchlm

Upgrade to the latest published version:

npm update --global switchlm

To run from source instead:

npm install
npm run build

Configure

Create the global user config at ~/.switchlm/config.json (%USERPROFILE%\.switchlm\config.json on Windows) so SwitchLM commands work from any directory. A project-level ./switchlm.config.json overrides the global config when both exist.

To move an existing project config on PowerShell:

New-Item-ItemType Directory -Force "$HOME\.switchlm"Move-Item .\switchlm.config.json "$HOME\.switchlm\config.json"

Configuration example:

{
"host": "127.0.0.1",
"port": 8787,
"bodyLimit": 16777216,
"routing": {
"solThreshold": 5
},
"providers": {
"luna": {
"type": "codex-chatgpt",
"responsesUrl": "https://chatgpt.com/backend-api/codex/responses",
"model": "gpt-5.6-luna",
"account": "default"
},
"sol": {
"type": "codex-chatgpt",
"responsesUrl": "https://chatgpt.com/backend-api/codex/responses",
"model": "gpt-5.6-sol",
"account": "default"
}
},
"logLevel": "info"
}

bodyLimit is the maximum request body size in bytes. The default is 16 MiB; increase it if Codex sends a larger repository context.

Authenticate once before starting SwitchLM:

switchlm login chatgpt

OAuth tokens are stored in ~/.switchlm/auth.json and refreshed automatically when possible.

OpenAI-compatible providers with API keys are also supported:

{
"providers": {
"luna": {
"baseUrl": "https://luna.example.com/v1",
"model": "luna-code",
"apiKeyEnv": "LUNA_API_KEY"
},
"sol": {
"baseUrl": "https://sol.example.com/v1",
"model": "sol-reasoning",
"apiKeyEnv": "SOL_API_KEY"
}
}
}

Provider entries without type are treated as openai-compatible. Set their credentials through the configured environment variables.

ChatGPT OAuth defaults:

authorizeUrl: https://auth.openai.com/oauth/authorize
tokenUrl: https://auth.openai.com/oauth/token
clientId: app_EMoamEEZ73f0CkXaXp7hrann
scopes: openid profile email offline_access
redirectUri: http://localhost:1455/auth/callback

Override them with env if needed:

set SWITCHLM_CHATGPT_AUTHORIZE_URL=...
set SWITCHLM_CHATGPT_TOKEN_URL=...
set SWITCHLM_CHATGPT_CLIENT_ID=...
set SWITCHLM_CHATGPT_SCOPES=openid profile

Run

switchlm start

Or from TypeScript during development:

npm run dev

Check health:

switchlm status

Show token usage:

npx switchlm stats

ChatGPT auth:

switchlm login chatgpt
switchlm auth status
switchlm logout chatgpt

API

Health:

curl http://127.0.0.1:8787/health

Responses:

curl http://127.0.0.1:8787/v1/responses ^
-H "content-type: application/json" ^
-d "{\"model\":\"router/auto\",\"input\":\"Fix this TypeScript error\"}"

Streaming:

curl http://127.0.0.1:8787/v1/responses ^
-H "content-type: application/json" ^
-d "{\"model\":\"router/auto\",\"input\":\"Fix this TypeScript error\",\"stream\":true}"

Virtual models:

  • router/auto routes by deterministic heuristics over user messages only; developer/system context and tool items are ignored.
  • router/luna always routes to Luna.
  • router/sol always routes to Sol.

Streaming requests are passed through as server-sent events.

The codex-chatgpt provider authenticates through the SwitchLM OAuth flow and uses the configured Codex Responses transport URL.

Token statistics:

curl http://127.0.0.1:8787/stats

The response contains routing and token totals for Luna, Sol, and both providers combined. routedRequests counts model selections, while measuredResponses counts completed responses with valid provider usage. The CLI shows their difference as missing. Statistics reset when SwitchLM restarts.

With logLevel: "info", each routing log includes the requested virtual model, selected target, score, and matching reasons without logging prompt contents.

The codex-chatgpt provider uses the configured Codex Responses transport URL. SwitchLM does not bundle provider-specific OAuth client credentials; set them explicitly through env.

Codex

Add the following provider to the user-level ~/.codex/config.toml:

model = "router/auto"model_provider = "switchlm"
[model_providers.switchlm]
name = "SwitchLM"base_url = "http://127.0.0.1:8787/v1"wire_api = "responses"

Use router/luna or router/sol when a request must bypass automatic routing.

License

SwitchLM is distributed under the MIT License. See LICENSE.

Parts of the ChatGPT/Codex OAuth integration are based on or adapted from OmniRoute. See THIRD_PARTY_NOTICES.md for attribution and third-party license terms.

About

Route coding requests to the right model based on complexity. OpenAI-compatible proxy for Codex with automatic escalation, routing strategies, and manual overrides.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

SwitchLM

npm versionlicense

Local OpenAI-compatible routing proxy for Codex. SwitchLM exposes the Responses API and routes coding requests between Luna for simple work and Sol for heavier reasoning.

SwitchLM is published on npm as switchlm. The current release is 1.0.2.

Key benefits

  • Automatically routes simple tasks to Luna and heavier tasks to Sol using transparent deterministic heuristics.
  • Supports explicit model selection through router/luna and router/sol when automatic routing is not desired.
  • Connects to ChatGPT/Codex through OAuth, including local token storage and automatic access-token refresh.
  • Preserves OpenAI Responses API compatibility, including server-sent event streaming.
  • Exposes routing and token statistics through GET /stats and switchlm stats.
  • Runs locally with a small configuration and no database or additional classifier model.

Install

Install the published package globally:

npm install --global switchlm

Upgrade to the latest published version:

npm update --global switchlm

To run from source instead:

npm install
npm run build

Configure

Create the global user config at ~/.switchlm/config.json (%USERPROFILE%\.switchlm\config.json on Windows) so SwitchLM commands work from any directory. A project-level ./switchlm.config.json overrides the global config when both exist.

To move an existing project config on PowerShell:

New-Item-ItemType Directory -Force "$HOME\.switchlm"Move-Item .\switchlm.config.json "$HOME\.switchlm\config.json"

Configuration example:

{
"host": "127.0.0.1",
"port": 8787,
"bodyLimit": 16777216,
"routing": {
"solThreshold": 5
},
"providers": {
"luna": {
"type": "codex-chatgpt",
"responsesUrl": "https://chatgpt.com/backend-api/codex/responses",
"model": "gpt-5.6-luna",
"account": "default"
},
"sol": {
"type": "codex-chatgpt",
"responsesUrl": "https://chatgpt.com/backend-api/codex/responses",
"model": "gpt-5.6-sol",
"account": "default"
}
},
"logLevel": "info"
}

bodyLimit is the maximum request body size in bytes. The default is 16 MiB; increase it if Codex sends a larger repository context.

Authenticate once before starting SwitchLM:

switchlm login chatgpt

OAuth tokens are stored in ~/.switchlm/auth.json and refreshed automatically when possible.

OpenAI-compatible providers with API keys are also supported:

{
"providers": {
"luna": {
"baseUrl": "https://luna.example.com/v1",
"model": "luna-code",
"apiKeyEnv": "LUNA_API_KEY"
},
"sol": {
"baseUrl": "https://sol.example.com/v1",
"model": "sol-reasoning",
"apiKeyEnv": "SOL_API_KEY"
}
}
}

Provider entries without type are treated as openai-compatible. Set their credentials through the configured environment variables.

ChatGPT OAuth defaults:

authorizeUrl: https://auth.openai.com/oauth/authorize
tokenUrl: https://auth.openai.com/oauth/token
clientId: app_EMoamEEZ73f0CkXaXp7hrann
scopes: openid profile email offline_access
redirectUri: http://localhost:1455/auth/callback

Override them with env if needed:

set SWITCHLM_CHATGPT_AUTHORIZE_URL=...
set SWITCHLM_CHATGPT_TOKEN_URL=...
set SWITCHLM_CHATGPT_CLIENT_ID=...
set SWITCHLM_CHATGPT_SCOPES=openid profile

Run

switchlm start

Or from TypeScript during development:

npm run dev

Check health:

switchlm status

Show token usage:

npx switchlm stats

ChatGPT auth:

switchlm login chatgpt
switchlm auth status
switchlm logout chatgpt

API

Health:

curl http://127.0.0.1:8787/health

Responses:

curl http://127.0.0.1:8787/v1/responses ^
-H "content-type: application/json" ^
-d "{\"model\":\"router/auto\",\"input\":\"Fix this TypeScript error\"}"

Streaming:

curl http://127.0.0.1:8787/v1/responses ^
-H "content-type: application/json" ^
-d "{\"model\":\"router/auto\",\"input\":\"Fix this TypeScript error\",\"stream\":true}"

Virtual models:

  • router/auto routes by deterministic heuristics over user messages only; developer/system context and tool items are ignored.
  • router/luna always routes to Luna.
  • router/sol always routes to Sol.

Streaming requests are passed through as server-sent events.

The codex-chatgpt provider authenticates through the SwitchLM OAuth flow and uses the configured Codex Responses transport URL.

Token statistics:

curl http://127.0.0.1:8787/stats

The response contains routing and token totals for Luna, Sol, and both providers combined. routedRequests counts model selections, while measuredResponses counts completed responses with valid provider usage. The CLI shows their difference as missing. Statistics reset when SwitchLM restarts.

With logLevel: "info", each routing log includes the requested virtual model, selected target, score, and matching reasons without logging prompt contents.

The codex-chatgpt provider uses the configured Codex Responses transport URL. SwitchLM does not bundle provider-specific OAuth client credentials; set them explicitly through env.

Codex

Add the following provider to the user-level ~/.codex/config.toml:

model = "router/auto"model_provider = "switchlm"
[model_providers.switchlm]
name = "SwitchLM"base_url = "http://127.0.0.1:8787/v1"wire_api = "responses"

Use router/luna or router/sol when a request must bypass automatic routing.

License

SwitchLM is distributed under the MIT License. See LICENSE.

Parts of the ChatGPT/Codex OAuth integration are based on or adapted from OmniRoute. See THIRD_PARTY_NOTICES.md for attribution and third-party license terms.

About

Route coding requests to the right model based on complexity. OpenAI-compatible proxy for Codex with automatic escalation, routing strategies, and manual overrides.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

SwitchLM

npm versionlicense

Local OpenAI-compatible routing proxy for Codex. SwitchLM exposes the Responses API and routes coding requests between Luna for simple work and Sol for heavier reasoning.

SwitchLM is published on npm as switchlm. The current release is 1.0.2.

Key benefits

  • Automatically routes simple tasks to Luna and heavier tasks to Sol using transparent deterministic heuristics.
  • Supports explicit model selection through router/luna and router/sol when automatic routing is not desired.
  • Connects to ChatGPT/Codex through OAuth, including local token storage and automatic access-token refresh.
  • Preserves OpenAI Responses API compatibility, including server-sent event streaming.
  • Exposes routing and token statistics through GET /stats and switchlm stats.
  • Runs locally with a small configuration and no database or additional classifier model.

Install

Install the published package globally:

npm install --global switchlm

Upgrade to the latest published version:

npm update --global switchlm

To run from source instead:

npm install
npm run build

Configure

Create the global user config at ~/.switchlm/config.json (%USERPROFILE%\.switchlm\config.json on Windows) so SwitchLM commands work from any directory. A project-level ./switchlm.config.json overrides the global config when both exist.

To move an existing project config on PowerShell:

New-Item-ItemType Directory -Force "$HOME\.switchlm"Move-Item .\switchlm.config.json "$HOME\.switchlm\config.json"

Configuration example:

{
"host": "127.0.0.1",
"port": 8787,
"bodyLimit": 16777216,
"routing": {
"solThreshold": 5
},
"providers": {
"luna": {
"type": "codex-chatgpt",
"responsesUrl": "https://chatgpt.com/backend-api/codex/responses",
"model": "gpt-5.6-luna",
"account": "default"
},
"sol": {
"type": "codex-chatgpt",
"responsesUrl": "https://chatgpt.com/backend-api/codex/responses",
"model": "gpt-5.6-sol",
"account": "default"
}
},
"logLevel": "info"
}

bodyLimit is the maximum request body size in bytes. The default is 16 MiB; increase it if Codex sends a larger repository context.

Authenticate once before starting SwitchLM:

switchlm login chatgpt

OAuth tokens are stored in ~/.switchlm/auth.json and refreshed automatically when possible.

OpenAI-compatible providers with API keys are also supported:

{
"providers": {
"luna": {
"baseUrl": "https://luna.example.com/v1",
"model": "luna-code",
"apiKeyEnv": "LUNA_API_KEY"
},
"sol": {
"baseUrl": "https://sol.example.com/v1",
"model": "sol-reasoning",
"apiKeyEnv": "SOL_API_KEY"
}
}
}

Provider entries without type are treated as openai-compatible. Set their credentials through the configured environment variables.

ChatGPT OAuth defaults:

authorizeUrl: https://auth.openai.com/oauth/authorize
tokenUrl: https://auth.openai.com/oauth/token
clientId: app_EMoamEEZ73f0CkXaXp7hrann
scopes: openid profile email offline_access
redirectUri: http://localhost:1455/auth/callback

Override them with env if needed:

set SWITCHLM_CHATGPT_AUTHORIZE_URL=...
set SWITCHLM_CHATGPT_TOKEN_URL=...
set SWITCHLM_CHATGPT_CLIENT_ID=...
set SWITCHLM_CHATGPT_SCOPES=openid profile

Run

switchlm start

Or from TypeScript during development:

npm run dev

Check health:

switchlm status

Show token usage:

npx switchlm stats

ChatGPT auth:

switchlm login chatgpt
switchlm auth status
switchlm logout chatgpt

API

Health:

curl http://127.0.0.1:8787/health

Responses:

curl http://127.0.0.1:8787/v1/responses ^
-H "content-type: application/json" ^
-d "{\"model\":\"router/auto\",\"input\":\"Fix this TypeScript error\"}"

Streaming:

curl http://127.0.0.1:8787/v1/responses ^
-H "content-type: application/json" ^
-d "{\"model\":\"router/auto\",\"input\":\"Fix this TypeScript error\",\"stream\":true}"

Virtual models:

  • router/auto routes by deterministic heuristics over user messages only; developer/system context and tool items are ignored.
  • router/luna always routes to Luna.
  • router/sol always routes to Sol.

Streaming requests are passed through as server-sent events.

The codex-chatgpt provider authenticates through the SwitchLM OAuth flow and uses the configured Codex Responses transport URL.

Token statistics:

curl http://127.0.0.1:8787/stats

The response contains routing and token totals for Luna, Sol, and both providers combined. routedRequests counts model selections, while measuredResponses counts completed responses with valid provider usage. The CLI shows their difference as missing. Statistics reset when SwitchLM restarts.

With logLevel: "info", each routing log includes the requested virtual model, selected target, score, and matching reasons without logging prompt contents.

The codex-chatgpt provider uses the configured Codex Responses transport URL. SwitchLM does not bundle provider-specific OAuth client credentials; set them explicitly through env.

Codex

Add the following provider to the user-level ~/.codex/config.toml:

model = "router/auto"model_provider = "switchlm"
[model_providers.switchlm]
name = "SwitchLM"base_url = "http://127.0.0.1:8787/v1"wire_api = "responses"

Use router/luna or router/sol when a request must bypass automatic routing.

License

SwitchLM is distributed under the MIT License. See LICENSE.

Parts of the ChatGPT/Codex OAuth integration are based on or adapted from OmniRoute. See THIRD_PARTY_NOTICES.md for attribution and third-party license terms.

About

Route coding requests to the right model based on complexity. OpenAI-compatible proxy for Codex with automatic escalation, routing strategies, and manual overrides.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

SwitchLM

npm versionlicense

Local OpenAI-compatible routing proxy for Codex. SwitchLM exposes the Responses API and routes coding requests between Luna for simple work and Sol for heavier reasoning.

SwitchLM is published on npm as switchlm. The current release is 1.0.2.

Key benefits

  • Automatically routes simple tasks to Luna and heavier tasks to Sol using transparent deterministic heuristics.
  • Supports explicit model selection through router/luna and router/sol when automatic routing is not desired.
  • Connects to ChatGPT/Codex through OAuth, including local token storage and automatic access-token refresh.
  • Preserves OpenAI Responses API compatibility, including server-sent event streaming.
  • Exposes routing and token statistics through GET /stats and switchlm stats.
  • Runs locally with a small configuration and no database or additional classifier model.

Install

Install the published package globally:

npm install --global switchlm

Upgrade to the latest published version:

npm update --global switchlm

To run from source instead:

npm install
npm run build

Configure

Create the global user config at ~/.switchlm/config.json (%USERPROFILE%\.switchlm\config.json on Windows) so SwitchLM commands work from any directory. A project-level ./switchlm.config.json overrides the global config when both exist.

To move an existing project config on PowerShell:

New-Item-ItemType Directory -Force "$HOME\.switchlm"Move-Item .\switchlm.config.json "$HOME\.switchlm\config.json"

Configuration example:

{
"host": "127.0.0.1",
"port": 8787,
"bodyLimit": 16777216,
"routing": {
"solThreshold": 5
},
"providers": {
"luna": {
"type": "codex-chatgpt",
"responsesUrl": "https://chatgpt.com/backend-api/codex/responses",
"model": "gpt-5.6-luna",
"account": "default"
},
"sol": {
"type": "codex-chatgpt",
"responsesUrl": "https://chatgpt.com/backend-api/codex/responses",
"model": "gpt-5.6-sol",
"account": "default"
}
},
"logLevel": "info"
}

bodyLimit is the maximum request body size in bytes. The default is 16 MiB; increase it if Codex sends a larger repository context.

Authenticate once before starting SwitchLM:

switchlm login chatgpt

OAuth tokens are stored in ~/.switchlm/auth.json and refreshed automatically when possible.

OpenAI-compatible providers with API keys are also supported:

{
"providers": {
"luna": {
"baseUrl": "https://luna.example.com/v1",
"model": "luna-code",
"apiKeyEnv": "LUNA_API_KEY"
},
"sol": {
"baseUrl": "https://sol.example.com/v1",
"model": "sol-reasoning",
"apiKeyEnv": "SOL_API_KEY"
}
}
}

Provider entries without type are treated as openai-compatible. Set their credentials through the configured environment variables.

ChatGPT OAuth defaults:

authorizeUrl: https://auth.openai.com/oauth/authorize
tokenUrl: https://auth.openai.com/oauth/token
clientId: app_EMoamEEZ73f0CkXaXp7hrann
scopes: openid profile email offline_access
redirectUri: http://localhost:1455/auth/callback

Override them with env if needed:

set SWITCHLM_CHATGPT_AUTHORIZE_URL=...
set SWITCHLM_CHATGPT_TOKEN_URL=...
set SWITCHLM_CHATGPT_CLIENT_ID=...
set SWITCHLM_CHATGPT_SCOPES=openid profile

Run

switchlm start

Or from TypeScript during development:

npm run dev

Check health:

switchlm status

Show token usage:

npx switchlm stats

ChatGPT auth:

switchlm login chatgpt
switchlm auth status
switchlm logout chatgpt

API

Health:

curl http://127.0.0.1:8787/health

Responses:

curl http://127.0.0.1:8787/v1/responses ^
-H "content-type: application/json" ^
-d "{\"model\":\"router/auto\",\"input\":\"Fix this TypeScript error\"}"

Streaming:

curl http://127.0.0.1:8787/v1/responses ^
-H "content-type: application/json" ^
-d "{\"model\":\"router/auto\",\"input\":\"Fix this TypeScript error\",\"stream\":true}"

Virtual models:

  • router/auto routes by deterministic heuristics over user messages only; developer/system context and tool items are ignored.
  • router/luna always routes to Luna.
  • router/sol always routes to Sol.

Streaming requests are passed through as server-sent events.

The codex-chatgpt provider authenticates through the SwitchLM OAuth flow and uses the configured Codex Responses transport URL.

Token statistics:

curl http://127.0.0.1:8787/stats

The response contains routing and token totals for Luna, Sol, and both providers combined. routedRequests counts model selections, while measuredResponses counts completed responses with valid provider usage. The CLI shows their difference as missing. Statistics reset when SwitchLM restarts.

With logLevel: "info", each routing log includes the requested virtual model, selected target, score, and matching reasons without logging prompt contents.

The codex-chatgpt provider uses the configured Codex Responses transport URL. SwitchLM does not bundle provider-specific OAuth client credentials; set them explicitly through env.

Codex

Add the following provider to the user-level ~/.codex/config.toml:

model = "router/auto"model_provider = "switchlm"
[model_providers.switchlm]
name = "SwitchLM"base_url = "http://127.0.0.1:8787/v1"wire_api = "responses"

Use router/luna or router/sol when a request must bypass automatic routing.

License

SwitchLM is distributed under the MIT License. See LICENSE.

Parts of the ChatGPT/Codex OAuth integration are based on or adapted from OmniRoute. See THIRD_PARTY_NOTICES.md for attribution and third-party license terms.

About

Route coding requests to the right model based on complexity. OpenAI-compatible proxy for Codex with automatic escalation, routing strategies, and manual overrides.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

SwitchLM

npm versionlicense

Local OpenAI-compatible routing proxy for Codex. SwitchLM exposes the Responses API and routes coding requests between Luna for simple work and Sol for heavier reasoning.

SwitchLM is published on npm as switchlm. The current release is 1.0.2.

Key benefits

  • Automatically routes simple tasks to Luna and heavier tasks to Sol using transparent deterministic heuristics.
  • Supports explicit model selection through router/luna and router/sol when automatic routing is not desired.
  • Connects to ChatGPT/Codex through OAuth, including local token storage and automatic access-token refresh.
  • Preserves OpenAI Responses API compatibility, including server-sent event streaming.
  • Exposes routing and token statistics through GET /stats and switchlm stats.
  • Runs locally with a small configuration and no database or additional classifier model.

Install

Install the published package globally:

npm install --global switchlm

Upgrade to the latest published version:

npm update --global switchlm

To run from source instead:

npm install
npm run build

Configure

Create the global user config at ~/.switchlm/config.json (%USERPROFILE%\.switchlm\config.json on Windows) so SwitchLM commands work from any directory. A project-level ./switchlm.config.json overrides the global config when both exist.

To move an existing project config on PowerShell:

New-Item-ItemType Directory -Force "$HOME\.switchlm"Move-Item .\switchlm.config.json "$HOME\.switchlm\config.json"

Configuration example:

{
"host": "127.0.0.1",
"port": 8787,
"bodyLimit": 16777216,
"routing": {
"solThreshold": 5
},
"providers": {
"luna": {
"type": "codex-chatgpt",
"responsesUrl": "https://chatgpt.com/backend-api/codex/responses",
"model": "gpt-5.6-luna",
"account": "default"
},
"sol": {
"type": "codex-chatgpt",
"responsesUrl": "https://chatgpt.com/backend-api/codex/responses",
"model": "gpt-5.6-sol",
"account": "default"
}
},
"logLevel": "info"
}

bodyLimit is the maximum request body size in bytes. The default is 16 MiB; increase it if Codex sends a larger repository context.

Authenticate once before starting SwitchLM:

switchlm login chatgpt

OAuth tokens are stored in ~/.switchlm/auth.json and refreshed automatically when possible.

OpenAI-compatible providers with API keys are also supported:

{
"providers": {
"luna": {
"baseUrl": "https://luna.example.com/v1",
"model": "luna-code",
"apiKeyEnv": "LUNA_API_KEY"
},
"sol": {
"baseUrl": "https://sol.example.com/v1",
"model": "sol-reasoning",
"apiKeyEnv": "SOL_API_KEY"
}
}
}

Provider entries without type are treated as openai-compatible. Set their credentials through the configured environment variables.

ChatGPT OAuth defaults:

authorizeUrl: https://auth.openai.com/oauth/authorize
tokenUrl: https://auth.openai.com/oauth/token
clientId: app_EMoamEEZ73f0CkXaXp7hrann
scopes: openid profile email offline_access
redirectUri: http://localhost:1455/auth/callback

Override them with env if needed:

set SWITCHLM_CHATGPT_AUTHORIZE_URL=...
set SWITCHLM_CHATGPT_TOKEN_URL=...
set SWITCHLM_CHATGPT_CLIENT_ID=...
set SWITCHLM_CHATGPT_SCOPES=openid profile

Run

switchlm start

Or from TypeScript during development:

npm run dev

Check health:

switchlm status

Show token usage:

npx switchlm stats

ChatGPT auth:

switchlm login chatgpt
switchlm auth status
switchlm logout chatgpt

API

Health:

curl http://127.0.0.1:8787/health

Responses:

curl http://127.0.0.1:8787/v1/responses ^
-H "content-type: application/json" ^
-d "{\"model\":\"router/auto\",\"input\":\"Fix this TypeScript error\"}"

Streaming:

curl http://127.0.0.1:8787/v1/responses ^
-H "content-type: application/json" ^
-d "{\"model\":\"router/auto\",\"input\":\"Fix this TypeScript error\",\"stream\":true}"

Virtual models:

  • router/auto routes by deterministic heuristics over user messages only; developer/system context and tool items are ignored.
  • router/luna always routes to Luna.
  • router/sol always routes to Sol.

Streaming requests are passed through as server-sent events.

The codex-chatgpt provider authenticates through the SwitchLM OAuth flow and uses the configured Codex Responses transport URL.

Token statistics:

curl http://127.0.0.1:8787/stats

The response contains routing and token totals for Luna, Sol, and both providers combined. routedRequests counts model selections, while measuredResponses counts completed responses with valid provider usage. The CLI shows their difference as missing. Statistics reset when SwitchLM restarts.

With logLevel: "info", each routing log includes the requested virtual model, selected target, score, and matching reasons without logging prompt contents.

The codex-chatgpt provider uses the configured Codex Responses transport URL. SwitchLM does not bundle provider-specific OAuth client credentials; set them explicitly through env.

Codex

Add the following provider to the user-level ~/.codex/config.toml:

model = "router/auto"model_provider = "switchlm"
[model_providers.switchlm]
name = "SwitchLM"base_url = "http://127.0.0.1:8787/v1"wire_api = "responses"

Use router/luna or router/sol when a request must bypass automatic routing.

License

SwitchLM is distributed under the MIT License. See LICENSE.

Parts of the ChatGPT/Codex OAuth integration are based on or adapted from OmniRoute. See THIRD_PARTY_NOTICES.md for attribution and third-party license terms.

About

Route coding requests to the right model based on complexity. OpenAI-compatible proxy for Codex with automatic escalation, routing strategies, and manual overrides.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

SwitchLM

npm versionlicense

Local OpenAI-compatible routing proxy for Codex. SwitchLM exposes the Responses API and routes coding requests between Luna for simple work and Sol for heavier reasoning.

SwitchLM is published on npm as switchlm. The current release is 1.0.2.

Key benefits

  • Automatically routes simple tasks to Luna and heavier tasks to Sol using transparent deterministic heuristics.
  • Supports explicit model selection through router/luna and router/sol when automatic routing is not desired.
  • Connects to ChatGPT/Codex through OAuth, including local token storage and automatic access-token refresh.
  • Preserves OpenAI Responses API compatibility, including server-sent event streaming.
  • Exposes routing and token statistics through GET /stats and switchlm stats.
  • Runs locally with a small configuration and no database or additional classifier model.

Install

Install the published package globally:

npm install --global switchlm

Upgrade to the latest published version:

npm update --global switchlm

To run from source instead:

npm install
npm run build

Configure

Create the global user config at ~/.switchlm/config.json (%USERPROFILE%\.switchlm\config.json on Windows) so SwitchLM commands work from any directory. A project-level ./switchlm.config.json overrides the global config when both exist.

To move an existing project config on PowerShell:

New-Item-ItemType Directory -Force "$HOME\.switchlm"Move-Item .\switchlm.config.json "$HOME\.switchlm\config.json"

Configuration example:

{
"host": "127.0.0.1",
"port": 8787,
"bodyLimit": 16777216,
"routing": {
"solThreshold": 5
},
"providers": {
"luna": {
"type": "codex-chatgpt",
"responsesUrl": "https://chatgpt.com/backend-api/codex/responses",
"model": "gpt-5.6-luna",
"account": "default"
},
"sol": {
"type": "codex-chatgpt",
"responsesUrl": "https://chatgpt.com/backend-api/codex/responses",
"model": "gpt-5.6-sol",
"account": "default"
}
},
"logLevel": "info"
}

bodyLimit is the maximum request body size in bytes. The default is 16 MiB; increase it if Codex sends a larger repository context.

Authenticate once before starting SwitchLM:

switchlm login chatgpt

OAuth tokens are stored in ~/.switchlm/auth.json and refreshed automatically when possible.

OpenAI-compatible providers with API keys are also supported:

{
"providers": {
"luna": {
"baseUrl": "https://luna.example.com/v1",
"model": "luna-code",
"apiKeyEnv": "LUNA_API_KEY"
},
"sol": {
"baseUrl": "https://sol.example.com/v1",
"model": "sol-reasoning",
"apiKeyEnv": "SOL_API_KEY"
}
}
}

Provider entries without type are treated as openai-compatible. Set their credentials through the configured environment variables.

ChatGPT OAuth defaults:

authorizeUrl: https://auth.openai.com/oauth/authorize
tokenUrl: https://auth.openai.com/oauth/token
clientId: app_EMoamEEZ73f0CkXaXp7hrann
scopes: openid profile email offline_access
redirectUri: http://localhost:1455/auth/callback

Override them with env if needed:

set SWITCHLM_CHATGPT_AUTHORIZE_URL=...
set SWITCHLM_CHATGPT_TOKEN_URL=...
set SWITCHLM_CHATGPT_CLIENT_ID=...
set SWITCHLM_CHATGPT_SCOPES=openid profile

Run

switchlm start

Or from TypeScript during development:

npm run dev

Check health:

switchlm status

Show token usage:

npx switchlm stats

ChatGPT auth:

switchlm login chatgpt
switchlm auth status
switchlm logout chatgpt

API

Health:

curl http://127.0.0.1:8787/health

Responses:

curl http://127.0.0.1:8787/v1/responses ^
-H "content-type: application/json" ^
-d "{\"model\":\"router/auto\",\"input\":\"Fix this TypeScript error\"}"

Streaming:

curl http://127.0.0.1:8787/v1/responses ^
-H "content-type: application/json" ^
-d "{\"model\":\"router/auto\",\"input\":\"Fix this TypeScript error\",\"stream\":true}"

Virtual models:

  • router/auto routes by deterministic heuristics over user messages only; developer/system context and tool items are ignored.
  • router/luna always routes to Luna.
  • router/sol always routes to Sol.

Streaming requests are passed through as server-sent events.

The codex-chatgpt provider authenticates through the SwitchLM OAuth flow and uses the configured Codex Responses transport URL.

Token statistics:

curl http://127.0.0.1:8787/stats

The response contains routing and token totals for Luna, Sol, and both providers combined. routedRequests counts model selections, while measuredResponses counts completed responses with valid provider usage. The CLI shows their difference as missing. Statistics reset when SwitchLM restarts.

With logLevel: "info", each routing log includes the requested virtual model, selected target, score, and matching reasons without logging prompt contents.

The codex-chatgpt provider uses the configured Codex Responses transport URL. SwitchLM does not bundle provider-specific OAuth client credentials; set them explicitly through env.

Codex

Add the following provider to the user-level ~/.codex/config.toml:

model = "router/auto"model_provider = "switchlm"
[model_providers.switchlm]
name = "SwitchLM"base_url = "http://127.0.0.1:8787/v1"wire_api = "responses"

Use router/luna or router/sol when a request must bypass automatic routing.

License

SwitchLM is distributed under the MIT License. See LICENSE.

Parts of the ChatGPT/Codex OAuth integration are based on or adapted from OmniRoute. See THIRD_PARTY_NOTICES.md for attribution and third-party license terms.

About

Route coding requests to the right model based on complexity. OpenAI-compatible proxy for Codex with automatic escalation, routing strategies, and manual overrides.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

SwitchLM

npm versionlicense

Local OpenAI-compatible routing proxy for Codex. SwitchLM exposes the Responses API and routes coding requests between Luna for simple work and Sol for heavier reasoning.

SwitchLM is published on npm as switchlm. The current release is 1.0.2.

Key benefits

  • Automatically routes simple tasks to Luna and heavier tasks to Sol using transparent deterministic heuristics.
  • Supports explicit model selection through router/luna and router/sol when automatic routing is not desired.
  • Connects to ChatGPT/Codex through OAuth, including local token storage and automatic access-token refresh.
  • Preserves OpenAI Responses API compatibility, including server-sent event streaming.
  • Exposes routing and token statistics through GET /stats and switchlm stats.
  • Runs locally with a small configuration and no database or additional classifier model.

Install

Install the published package globally:

npm install --global switchlm

Upgrade to the latest published version:

npm update --global switchlm

To run from source instead:

npm install
npm run build

Configure

Create the global user config at ~/.switchlm/config.json (%USERPROFILE%\.switchlm\config.json on Windows) so SwitchLM commands work from any directory. A project-level ./switchlm.config.json overrides the global config when both exist.

To move an existing project config on PowerShell:

New-Item-ItemType Directory -Force "$HOME\.switchlm"Move-Item .\switchlm.config.json "$HOME\.switchlm\config.json"

Configuration example:

{
"host": "127.0.0.1",
"port": 8787,
"bodyLimit": 16777216,
"routing": {
"solThreshold": 5
},
"providers": {
"luna": {
"type": "codex-chatgpt",
"responsesUrl": "https://chatgpt.com/backend-api/codex/responses",
"model": "gpt-5.6-luna",
"account": "default"
},
"sol": {
"type": "codex-chatgpt",
"responsesUrl": "https://chatgpt.com/backend-api/codex/responses",
"model": "gpt-5.6-sol",
"account": "default"
}
},
"logLevel": "info"
}

bodyLimit is the maximum request body size in bytes. The default is 16 MiB; increase it if Codex sends a larger repository context.

Authenticate once before starting SwitchLM:

switchlm login chatgpt

OAuth tokens are stored in ~/.switchlm/auth.json and refreshed automatically when possible.

OpenAI-compatible providers with API keys are also supported:

{
"providers": {
"luna": {
"baseUrl": "https://luna.example.com/v1",
"model": "luna-code",
"apiKeyEnv": "LUNA_API_KEY"
},
"sol": {
"baseUrl": "https://sol.example.com/v1",
"model": "sol-reasoning",
"apiKeyEnv": "SOL_API_KEY"
}
}
}

Provider entries without type are treated as openai-compatible. Set their credentials through the configured environment variables.

ChatGPT OAuth defaults:

authorizeUrl: https://auth.openai.com/oauth/authorize
tokenUrl: https://auth.openai.com/oauth/token
clientId: app_EMoamEEZ73f0CkXaXp7hrann
scopes: openid profile email offline_access
redirectUri: http://localhost:1455/auth/callback

Override them with env if needed:

set SWITCHLM_CHATGPT_AUTHORIZE_URL=...
set SWITCHLM_CHATGPT_TOKEN_URL=...
set SWITCHLM_CHATGPT_CLIENT_ID=...
set SWITCHLM_CHATGPT_SCOPES=openid profile

Run

switchlm start

Or from TypeScript during development:

npm run dev

Check health:

switchlm status

Show token usage:

npx switchlm stats

ChatGPT auth:

switchlm login chatgpt
switchlm auth status
switchlm logout chatgpt

API

Health:

curl http://127.0.0.1:8787/health

Responses:

curl http://127.0.0.1:8787/v1/responses ^
-H "content-type: application/json" ^
-d "{\"model\":\"router/auto\",\"input\":\"Fix this TypeScript error\"}"

Streaming:

curl http://127.0.0.1:8787/v1/responses ^
-H "content-type: application/json" ^
-d "{\"model\":\"router/auto\",\"input\":\"Fix this TypeScript error\",\"stream\":true}"

Virtual models:

  • router/auto routes by deterministic heuristics over user messages only; developer/system context and tool items are ignored.
  • router/luna always routes to Luna.
  • router/sol always routes to Sol.

Streaming requests are passed through as server-sent events.

The codex-chatgpt provider authenticates through the SwitchLM OAuth flow and uses the configured Codex Responses transport URL.

Token statistics:

curl http://127.0.0.1:8787/stats

The response contains routing and token totals for Luna, Sol, and both providers combined. routedRequests counts model selections, while measuredResponses counts completed responses with valid provider usage. The CLI shows their difference as missing. Statistics reset when SwitchLM restarts.

With logLevel: "info", each routing log includes the requested virtual model, selected target, score, and matching reasons without logging prompt contents.

The codex-chatgpt provider uses the configured Codex Responses transport URL. SwitchLM does not bundle provider-specific OAuth client credentials; set them explicitly through env.

Codex

Add the following provider to the user-level ~/.codex/config.toml:

model = "router/auto"model_provider = "switchlm"
[model_providers.switchlm]
name = "SwitchLM"base_url = "http://127.0.0.1:8787/v1"wire_api = "responses"

Use router/luna or router/sol when a request must bypass automatic routing.

License

SwitchLM is distributed under the MIT License. See LICENSE.

Parts of the ChatGPT/Codex OAuth integration are based on or adapted from OmniRoute. See THIRD_PARTY_NOTICES.md for attribution and third-party license terms.

About

Route coding requests to the right model based on complexity. OpenAI-compatible proxy for Codex with automatic escalation, routing strategies, and manual overrides.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

SwitchLM

npm versionlicense

Local OpenAI-compatible routing proxy for Codex. SwitchLM exposes the Responses API and routes coding requests between Luna for simple work and Sol for heavier reasoning.

SwitchLM is published on npm as switchlm. The current release is 1.0.2.

Key benefits

  • Automatically routes simple tasks to Luna and heavier tasks to Sol using transparent deterministic heuristics.
  • Supports explicit model selection through router/luna and router/sol when automatic routing is not desired.
  • Connects to ChatGPT/Codex through OAuth, including local token storage and automatic access-token refresh.
  • Preserves OpenAI Responses API compatibility, including server-sent event streaming.
  • Exposes routing and token statistics through GET /stats and switchlm stats.
  • Runs locally with a small configuration and no database or additional classifier model.

Install

Install the published package globally:

npm install --global switchlm

Upgrade to the latest published version:

npm update --global switchlm

To run from source instead:

npm install
npm run build

Configure

Create the global user config at ~/.switchlm/config.json (%USERPROFILE%\.switchlm\config.json on Windows) so SwitchLM commands work from any directory. A project-level ./switchlm.config.json overrides the global config when both exist.

To move an existing project config on PowerShell:

New-Item-ItemType Directory -Force "$HOME\.switchlm"Move-Item .\switchlm.config.json "$HOME\.switchlm\config.json"

Configuration example:

{
"host": "127.0.0.1",
"port": 8787,
"bodyLimit": 16777216,
"routing": {
"solThreshold": 5
},
"providers": {
"luna": {
"type": "codex-chatgpt",
"responsesUrl": "https://chatgpt.com/backend-api/codex/responses",
"model": "gpt-5.6-luna",
"account": "default"
},
"sol": {
"type": "codex-chatgpt",
"responsesUrl": "https://chatgpt.com/backend-api/codex/responses",
"model": "gpt-5.6-sol",
"account": "default"
}
},
"logLevel": "info"
}

bodyLimit is the maximum request body size in bytes. The default is 16 MiB; increase it if Codex sends a larger repository context.

Authenticate once before starting SwitchLM:

switchlm login chatgpt

OAuth tokens are stored in ~/.switchlm/auth.json and refreshed automatically when possible.

OpenAI-compatible providers with API keys are also supported:

{
"providers": {
"luna": {
"baseUrl": "https://luna.example.com/v1",
"model": "luna-code",
"apiKeyEnv": "LUNA_API_KEY"
},
"sol": {
"baseUrl": "https://sol.example.com/v1",
"model": "sol-reasoning",
"apiKeyEnv": "SOL_API_KEY"
}
}
}

Provider entries without type are treated as openai-compatible. Set their credentials through the configured environment variables.

ChatGPT OAuth defaults:

authorizeUrl: https://auth.openai.com/oauth/authorize
tokenUrl: https://auth.openai.com/oauth/token
clientId: app_EMoamEEZ73f0CkXaXp7hrann
scopes: openid profile email offline_access
redirectUri: http://localhost:1455/auth/callback

Override them with env if needed:

set SWITCHLM_CHATGPT_AUTHORIZE_URL=...
set SWITCHLM_CHATGPT_TOKEN_URL=...
set SWITCHLM_CHATGPT_CLIENT_ID=...
set SWITCHLM_CHATGPT_SCOPES=openid profile

Run

switchlm start

Or from TypeScript during development:

npm run dev

Check health:

switchlm status

Show token usage:

npx switchlm stats

ChatGPT auth:

switchlm login chatgpt
switchlm auth status
switchlm logout chatgpt

API

Health:

curl http://127.0.0.1:8787/health

Responses:

curl http://127.0.0.1:8787/v1/responses ^
-H "content-type: application/json" ^
-d "{\"model\":\"router/auto\",\"input\":\"Fix this TypeScript error\"}"

Streaming:

curl http://127.0.0.1:8787/v1/responses ^
-H "content-type: application/json" ^
-d "{\"model\":\"router/auto\",\"input\":\"Fix this TypeScript error\",\"stream\":true}"

Virtual models:

  • router/auto routes by deterministic heuristics over user messages only; developer/system context and tool items are ignored.
  • router/luna always routes to Luna.
  • router/sol always routes to Sol.

Streaming requests are passed through as server-sent events.

The codex-chatgpt provider authenticates through the SwitchLM OAuth flow and uses the configured Codex Responses transport URL.

Token statistics:

curl http://127.0.0.1:8787/stats

The response contains routing and token totals for Luna, Sol, and both providers combined. routedRequests counts model selections, while measuredResponses counts completed responses with valid provider usage. The CLI shows their difference as missing. Statistics reset when SwitchLM restarts.

With logLevel: "info", each routing log includes the requested virtual model, selected target, score, and matching reasons without logging prompt contents.

The codex-chatgpt provider uses the configured Codex Responses transport URL. SwitchLM does not bundle provider-specific OAuth client credentials; set them explicitly through env.

Codex

Add the following provider to the user-level ~/.codex/config.toml:

model = "router/auto"model_provider = "switchlm"
[model_providers.switchlm]
name = "SwitchLM"base_url = "http://127.0.0.1:8787/v1"wire_api = "responses"

Use router/luna or router/sol when a request must bypass automatic routing.

License

SwitchLM is distributed under the MIT License. See LICENSE.

Parts of the ChatGPT/Codex OAuth integration are based on or adapted from OmniRoute. See THIRD_PARTY_NOTICES.md for attribution and third-party license terms.

About

Route coding requests to the right model based on complexity. OpenAI-compatible proxy for Codex with automatic escalation, routing strategies, and manual overrides.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages