@albinocrabs/o-switcher 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,52 +1,77 @@
1
1
  # O-Switcher
2
2
 
3
- [English](#english) | [Русский](#русский)
3
+ [![npm version](https://img.shields.io/npm/v/@albinocrabs/o-switcher)](https://www.npmjs.com/package/@albinocrabs/o-switcher)
4
+ [![CI](https://github.com/apolenkov/o-switcher/actions/workflows/ci.yml/badge.svg)](https://github.com/apolenkov/o-switcher/actions/workflows/ci.yml)
5
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
4
6
 
5
- ---
7
+ Seamless OpenRouter profile rotation for [OpenCode](https://opencode.ai). Buy multiple cheap subscriptions — when one hits its quota or rate limit, O-Switcher silently switches to the next profile. No manual intervention needed.
6
8
 
7
- ## English
9
+ ## Why?
8
10
 
9
- ### What is it?
11
+ - OpenRouter profiles have quota limits and rate limits
12
+ - Without O-Switcher: quota runs out, you manually re-auth with another profile, lose context
13
+ - With O-Switcher: automatic rotation across profiles, work continues uninterrupted
14
+ - Buy 5 cheap subscriptions → use them as one seamless pool
10
15
 
11
- An [OpenCode](https://opencode.ai) plugin that automatically switches between LLM providers when one goes down. If Anthropic returns errors — your request silently goes to OpenAI. No manual intervention needed.
12
-
13
- ### Why?
14
-
15
- - Provider APIs go down, hit rate limits, or return errors
16
- - Without O-Switcher: you wait, manually switch providers, lose context
17
- - With O-Switcher: automatic retry, failover to backup provider, work continues
18
-
19
- ### Install
16
+ ## Quick Start
20
17
 
21
18
  Add to your `opencode.json`:
22
19
 
23
20
  ```json
24
21
  {
25
- "plugin": [
26
- "@apolenkov/o-switcher@latest"
27
- ]
22
+ "plugin": ["@albinocrabs/o-switcher@latest"]
28
23
  }
29
24
  ```
30
25
 
31
- Or for local development:
26
+ That's it. O-Switcher auto-discovers all your configured OpenCode providers.
32
27
 
33
- ```json
34
- {
35
- "plugin": [
36
- "/path/to/o-switcher"
37
- ]
38
- }
28
+ ## How It Works
29
+
30
+ ```mermaid
31
+ flowchart LR
32
+ subgraph pool["Subscription Pool"]
33
+ A["Profile A<br/>$5/mo"]
34
+ B["Profile B<br/>$5/mo"]
35
+ C["Profile C<br/>$5/mo"]
36
+ end
37
+
38
+ R([LLM Request]) --> SW{O-Switcher}
39
+ SW -->|healthy| A
40
+ A -.->|"quota hit ❌"| SW
41
+ SW -->|rotate| B
42
+ B -.->|"quota hit ❌"| SW
43
+ SW -->|rotate| C
44
+ C -->|"✅ success"| RES([Response])
45
+
46
+ style pool fill:#f0f9ff,stroke:#0284c7
47
+ style SW fill:#4a9eff,color:#fff
48
+ style RES fill:#6f6,color:#000
39
49
  ```
40
50
 
41
- ### Configure
51
+ 1. **Install the plugin** — add `@albinocrabs/o-switcher@latest` to your `opencode.json`
52
+ 2. **Auto-discovers your profiles** — O-Switcher reads all configured OpenRouter profiles at startup
53
+ 3. **Re-authenticate to add more** — run `opencode auth login openrouter` with another key, O-Switcher saves both profiles
54
+ 4. **Switcher rotates** — when one profile hits quota or rate limit, requests automatically route to the next
55
+
56
+ When a request fails, O-Switcher classifies the error and acts:
57
+
58
+ | Error | Action |
59
+ |-------|--------|
60
+ | Rate limited (429) | Waits and retries with backoff (up to 3 times) |
61
+ | Server error (5xx) | Retries with exponential backoff |
62
+ | Model unavailable | Immediately tries next target |
63
+ | Auth error (401) | Stops, marks target for re-authentication |
64
+ | All retries exhausted | Fails over to backup target (up to 2 failovers) |
65
+
66
+ All routing decisions are logged as structured NDJSON for debugging.
42
67
 
43
- That's it. O-Switcher auto-discovers all configured providers from your OpenCode config. No target configuration needed.
68
+ ## Configuration
44
69
 
45
- Want to customize retry and timeout?
70
+ Optional O-Switcher works with zero config. Customize retry and timeout if needed:
46
71
 
47
72
  ```json
48
73
  {
49
- "plugin": ["@apolenkov/o-switcher@latest"],
74
+ "plugin": ["@albinocrabs/o-switcher@latest"],
50
75
  "switcher": {
51
76
  "retry": 3,
52
77
  "timeout": 30000
@@ -54,308 +79,83 @@ Want to customize retry and timeout?
54
79
  }
55
80
  ```
56
81
 
57
- Both fields are optional with sensible defaults.
82
+ | Option | Default | Description |
83
+ |--------|---------|-------------|
84
+ | `retry` | `3` | Max retry attempts per request |
85
+ | `timeout` | `30000` | Request timeout in milliseconds |
58
86
 
59
- ### Examples
87
+ ## Examples
60
88
 
61
89
  **Zero config — just add the plugin:**
62
90
 
63
91
  ```json
64
92
  {
65
- "plugin": ["@apolenkov/o-switcher@latest"]
93
+ "plugin": ["@albinocrabs/o-switcher@latest"]
66
94
  }
67
95
  ```
68
96
 
69
- O-Switcher reads your configured providers (anthropic, openai, etc.) and creates targets automatically. You get: automatic retry on errors, circuit breaker protection, health monitoring.
70
-
71
- **Multiple API keys for one provider (rate limit distribution):**
97
+ **Multiple OpenRouter profiles (quota pool rotation):**
72
98
 
73
99
  ```json
74
100
  {
75
101
  "provider": {
76
- "openai-work": { "api": "openai", "apiKey": "sk-work-key-111" },
77
- "openai-personal": { "api": "openai", "apiKey": "sk-personal-222" },
78
- "openai-backup": { "api": "openai", "apiKey": "sk-backup-333" }
102
+ "openrouter-main": { "api": "openrouter", "apiKey": "sk-or-v1-aaa..." },
103
+ "openrouter-backup": { "api": "openrouter", "apiKey": "sk-or-v1-bbb..." },
104
+ "openrouter-spare": { "api": "openrouter", "apiKey": "sk-or-v1-ccc..." }
79
105
  },
80
- "plugin": ["@apolenkov/o-switcher@latest"]
81
- }
82
- ```
83
-
84
- O-Switcher sees 3 separate targets (all OpenAI). When `work` key hits rate limit — switches to `personal`, then `backup`. Same model, different keys.
85
-
86
- **Custom retry and timeout:**
87
-
88
- ```json
89
- {
90
- "switcher": {
91
- "retry": 5,
92
- "timeout": 60000
93
- }
94
- }
95
- ```
96
-
97
- ### Advanced: Manual Targets
98
-
99
- For full control, you can specify targets explicitly. When `targets` is present, auto-discovery is disabled.
100
-
101
- **Two providers — primary + backup:**
102
-
103
- ```json
104
- {
105
- "switcher": {
106
- "targets": [
107
- {
108
- "target_id": "claude",
109
- "provider_id": "anthropic",
110
- "capabilities": ["chat"],
111
- "enabled": true,
112
- "operator_priority": 2,
113
- "policy_tags": []
114
- },
115
- {
116
- "target_id": "gpt",
117
- "provider_id": "openai",
118
- "capabilities": ["chat"],
119
- "enabled": true,
120
- "operator_priority": 1,
121
- "policy_tags": []
122
- }
123
- ]
124
- }
106
+ "plugin": ["@albinocrabs/o-switcher@latest"]
125
107
  }
126
108
  ```
127
109
 
128
- Claude is primary (priority 2). If Claude fails 3 times — switches to GPT.
110
+ O-Switcher sees 3 profiles (all OpenRouter). When `main` hits quota — switches to `backup`, then `spare`. Same models, different subscriptions.
129
111
 
130
- **Custom retry and failover limits:**
112
+ ## Operator Commands
131
113
 
132
- ```json
133
- {
134
- "switcher": {
135
- "targets": [/* ... */],
136
- "retry_budget": 5,
137
- "failover_budget": 3,
138
- "backoff": {
139
- "base_ms": 500,
140
- "max_ms": 15000
141
- }
142
- }
143
- }
144
- ```
114
+ Control targets at runtime without code changes:
145
115
 
146
- ### How it works
116
+ | Command | Description |
117
+ |---------|-------------|
118
+ | `list` | Show all targets with health score, circuit breaker state, cooldown status |
119
+ | `pause <target>` | Temporarily stop routing to a target |
120
+ | `resume <target>` | Resume routing to a paused target |
121
+ | `drain <target>` | Stop new requests, let in-flight complete |
122
+ | `disable <target>` | Fully disable a target |
123
+ | `inspect <request_id>` | Show full request trace (attempts, failovers, segments) |
147
124
 
148
- 1. You send a request in OpenCode
149
- 2. O-Switcher picks the healthiest target
150
- 3. If the request fails:
151
- - **Rate limited** → waits and retries (up to 3 times)
152
- - **Server error** → retries with backoff
153
- - **Model unavailable** → immediately tries next target
154
- - **Auth error** → stops, marks target for re-authentication
155
- 4. If all retries fail → switches to backup target (up to 2 failovers)
156
- 5. All decisions are logged for debugging
157
-
158
- ### Docs
125
+ ## Documentation
159
126
 
160
127
  - [Getting Started](docs/getting-started.md) — installation, configuration, troubleshooting
161
128
  - [API Reference](docs/api-reference.md) — all config options, operator tools, error classes, log format
162
129
  - [Examples](docs/examples.md) — multi-key, multi-provider, log analysis, circuit breaker tuning
163
130
  - [Architecture](docs/architecture.md) — C4 diagrams, sequence flows, internals
164
131
 
165
- ### Development
132
+ ## Development
166
133
 
167
134
  ```bash
168
135
  git clone https://github.com/apolenkov/o-switcher.git
169
136
  cd o-switcher
170
137
  npm install
171
- npm test # 396 tests
138
+ npm test # 455 tests
172
139
  npm run typecheck # TypeScript strict
173
140
  npm run build # ESM + CJS
174
141
  ```
175
142
 
176
- ### License
177
-
178
- [MIT](LICENSE)
179
-
180
- ---
181
-
182
- ## Русский
183
-
184
- ### Что это?
185
-
186
- Плагин для [OpenCode](https://opencode.ai), который автоматически переключается между LLM-провайдерами, когда один падает. Если Anthropic возвращает ошибки — запрос тихо уходит в OpenAI. Никакого ручного вмешательства.
187
-
188
- ### Зачем?
189
-
190
- - API провайдеров падают, упираются в rate limit, возвращают ошибки
191
- - Без O-Switcher: ждёшь, вручную переключаешь провайдера, теряешь контекст
192
- - С O-Switcher: автоматический retry, failover на резервного провайдера, работа продолжается
193
-
194
- ### Установка
195
-
196
- Добавь в `opencode.json`:
143
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development guide.
197
144
 
198
- ```json
199
- {
200
- "plugin": [
201
- "@apolenkov/o-switcher@latest"
202
- ]
203
- }
204
- ```
145
+ ## Contributing
205
146
 
206
- Или для локальной разработки:
207
-
208
- ```json
209
- {
210
- "plugin": [
211
- "/путь/до/o-switcher"
212
- ]
213
- }
214
- ```
147
+ O-Switcher is open source and we welcome contributions! Whether it's bug reports, feature ideas, documentation improvements, or code — every contribution matters.
215
148
 
216
- ### Настройка
149
+ Check out our [Contributing Guide](CONTRIBUTING.md) to get started. Look for issues labeled [`good first issue`](https://github.com/apolenkov/o-switcher/labels/good%20first%20issue) — these are curated for new contributors.
217
150
 
218
- Все. O-Switcher автоматически находит все настроенные провайдеры из конфига OpenCode. Настраивать таргеты не нужно.
151
+ ## Roadmap
219
152
 
220
- Хочешь задать свои retry и timeout?
153
+ O-Switcher started as a plugin, but the goal is to contribute this as a core feature to [OpenCode](https://github.com/opencode-ai/opencode) itself — so that any provider (not just OpenRouter) can have multiple profiles that automatically substitute each other when problems occur on any of them.
221
154
 
222
- ```json
223
- {
224
- "plugin": ["@apolenkov/o-switcher@latest"],
225
- "switcher": {
226
- "retry": 3,
227
- "timeout": 30000
228
- }
229
- }
230
- ```
231
-
232
- Оба поля опциональны и имеют разумные дефолты.
233
-
234
- ### Примеры
235
-
236
- **Нулевой конфиг — просто добавь плагин:**
237
-
238
- ```json
239
- {
240
- "plugin": ["@apolenkov/o-switcher@latest"]
241
- }
242
- ```
243
-
244
- O-Switcher читает настроенных провайдеров (anthropic, openai и т.д.) и создает таргеты автоматически. Получаешь: автоматический retry, circuit breaker, мониторинг здоровья.
245
-
246
- **Несколько API ключей для одного провайдера (распределение rate limit):**
247
-
248
- ```json
249
- {
250
- "provider": {
251
- "openai-work": { "api": "openai", "apiKey": "sk-work-key-111" },
252
- "openai-personal": { "api": "openai", "apiKey": "sk-personal-222" },
253
- "openai-backup": { "api": "openai", "apiKey": "sk-backup-333" }
254
- },
255
- "plugin": ["@apolenkov/o-switcher@latest"]
256
- }
257
- ```
258
-
259
- O-Switcher видит 3 отдельных таргета (все OpenAI). Когда ключ `work` упирается в rate limit — переключается на `personal`, потом `backup`. Та же модель, разные ключи.
260
-
261
- Посмотреть настроенные провайдеры и ключи:
262
-
263
- ```bash
264
- opencode providers list # все провайдеры и авторизации
265
- opencode providers login # добавить новый ключ
266
- opencode providers logout # удалить ключ
267
- ```
268
-
269
- **Свои retry и timeout:**
270
-
271
- ```json
272
- {
273
- "switcher": {
274
- "retry": 5,
275
- "timeout": 60000
276
- }
277
- }
278
- ```
279
-
280
- ### Продвинутое: Ручная настройка таргетов
281
-
282
- Для полного контроля можно указать таргеты явно. Когда `targets` присутствует, автообнаружение отключается.
283
-
284
- **Два провайдера — основной + резервный:**
285
-
286
- ```json
287
- {
288
- "switcher": {
289
- "targets": [
290
- {
291
- "target_id": "claude",
292
- "provider_id": "anthropic",
293
- "capabilities": ["chat"],
294
- "enabled": true,
295
- "operator_priority": 2,
296
- "policy_tags": []
297
- },
298
- {
299
- "target_id": "gpt",
300
- "provider_id": "openai",
301
- "capabilities": ["chat"],
302
- "enabled": true,
303
- "operator_priority": 1,
304
- "policy_tags": []
305
- }
306
- ]
307
- }
308
- }
309
- ```
310
-
311
- Claude основной (приоритет 2). Если Claude упал 3 раза — переключается на GPT.
312
-
313
- **Свои лимиты retry и failover:**
314
-
315
- ```json
316
- {
317
- "switcher": {
318
- "targets": [/* ... */],
319
- "retry_budget": 5,
320
- "failover_budget": 3,
321
- "backoff": {
322
- "base_ms": 500,
323
- "max_ms": 15000
324
- }
325
- }
326
- }
327
- ```
328
-
329
- ### Как это работает
330
-
331
- 1. Отправляешь запрос в OpenCode
332
- 2. O-Switcher выбирает самый здоровый таргет
333
- 3. Если запрос упал:
334
- - **Rate limit** → ждёт и повторяет (до 3 раз)
335
- - **Ошибка сервера** → повтор с backoff
336
- - **Модель недоступна** → сразу пробует следующий таргет
337
- - **Ошибка авторизации** → останавливается, помечает таргет
338
- 4. Если все retry исчерпаны → переключение на резервный таргет (до 2 failover)
339
- 5. Все решения логируются для отладки
340
-
341
- ### Документация
342
-
343
- - [Getting Started](docs/getting-started.md) — установка, настройка, траблшутинг
344
- - [API Reference](docs/api-reference.md) — все опции конфига, операторские команды, классы ошибок, формат логов
345
- - [Примеры](docs/examples.md) — мульти-ключи, мульти-провайдеры, анализ логов, тюнинг circuit breaker
346
- - [Архитектура](docs/architecture.md) — C4 диаграммы, sequence-диаграммы, внутреннее устройство
347
-
348
- ### Разработка
349
-
350
- ```bash
351
- git clone https://github.com/apolenkov/o-switcher.git
352
- cd o-switcher
353
- npm install
354
- npm test # 359 тестов
355
- npm run typecheck # TypeScript strict
356
- npm run build # ESM + CJS
357
- ```
155
+ - **Now:** Plugin for OpenCode — works with OpenRouter profiles
156
+ - **Next:** PR to OpenCode — built-in multi-profile rotation for every provider
157
+ - **Vision:** Each provider in OpenCode config can have N profiles, auto-failover between them is transparent and zero-config
358
158
 
359
- ### Лицензия
159
+ ## License
360
160
 
361
- [MIT](LICENSE)
161
+ [Apache-2.0](LICENSE)