@albinocrabs/o-switcher 0.1.0

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.
@@ -0,0 +1,72 @@
1
+ # Contributing to O-Switcher
2
+
3
+ Thank you for your interest in contributing!
4
+
5
+ ## Getting Started
6
+
7
+ ```bash
8
+ git clone https://github.com/<owner>/o-switcher.git
9
+ cd o-switcher
10
+ npm install
11
+ npm test # 351 tests, all must pass
12
+ npm run typecheck # zero TypeScript errors
13
+ ```
14
+
15
+ ## Development Workflow
16
+
17
+ 1. **Fork** the repository
18
+ 2. **Create a branch** from `main`: `git checkout -b feat/my-feature`
19
+ 3. **Write tests first** (TDD) — tests live next to source files (`*.test.ts`)
20
+ 4. **Implement** — make the tests pass
21
+ 5. **Verify** — `npm test && npm run typecheck`
22
+ 6. **Commit** — use [conventional commits](https://www.conventionalcommits.org/):
23
+ `feat(routing):`, `fix(config):`, `test(execution):`, `docs:`
24
+ 7. **Open a PR** against `main`
25
+
26
+ ## Project Structure
27
+
28
+ ```
29
+ src/
30
+ ├── config/ Config loading, Zod schema validation
31
+ ├── registry/ Target registry, health scoring, mode detection
32
+ ├── errors/ Error taxonomy (10 classes), dual-mode classifier
33
+ ├── retry/ Bounded retry with backoff, jitter, Retry-After
34
+ ├── routing/ Policy engine, circuit breaker, admission, failover
35
+ ├── execution/ Mode adapters, stream stitcher, audit collector
36
+ ├── operator/ Operator commands, config reload, plugin tools
37
+ ├── integration/ End-to-end integration tests
38
+ ├── audit/ Pino-based structured audit logger
39
+ ├── mode/ Deployment mode types and capabilities
40
+ └── spike/ Proof-of-concept explorations
41
+ ```
42
+
43
+ ## Code Conventions
44
+
45
+ - **TypeScript strict mode** — `noUncheckedIndexedAccess`, `verbatimModuleSyntax`
46
+ - **ESM** — `"type": "module"` in package.json
47
+ - **Factory functions** — `createXxx()` returning interfaces (not classes)
48
+ - **Pure functions** — routing/scoring logic has no I/O
49
+ - **No `let`** — use `const` only, restructure if needed
50
+ - **No `!!`** — use `Boolean()` for explicit coercion
51
+ - **Vitest** for testing — co-located `*.test.ts` files
52
+
53
+ ## Testing
54
+
55
+ ```bash
56
+ npm test # all tests
57
+ npx vitest run src/routing/ # specific module
58
+ npx vitest run --watch # watch mode
59
+ ```
60
+
61
+ ## Architecture
62
+
63
+ See [README.md](README.md) for the architecture diagram and module overview.
64
+
65
+ ## Reporting Issues
66
+
67
+ - Use GitHub Issues
68
+ - Include: steps to reproduce, expected vs actual behavior, Node.js version
69
+
70
+ ## License
71
+
72
+ By contributing, you agree that your contributions will be licensed under the MIT License.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 O-Switcher Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,361 @@
1
+ # O-Switcher
2
+
3
+ [English](#english) | [Русский](#русский)
4
+
5
+ ---
6
+
7
+ ## English
8
+
9
+ ### What is it?
10
+
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
20
+
21
+ Add to your `opencode.json`:
22
+
23
+ ```json
24
+ {
25
+ "plugin": [
26
+ "@apolenkov/o-switcher@latest"
27
+ ]
28
+ }
29
+ ```
30
+
31
+ Or for local development:
32
+
33
+ ```json
34
+ {
35
+ "plugin": [
36
+ "/path/to/o-switcher"
37
+ ]
38
+ }
39
+ ```
40
+
41
+ ### Configure
42
+
43
+ That's it. O-Switcher auto-discovers all configured providers from your OpenCode config. No target configuration needed.
44
+
45
+ Want to customize retry and timeout?
46
+
47
+ ```json
48
+ {
49
+ "plugin": ["@apolenkov/o-switcher@latest"],
50
+ "switcher": {
51
+ "retry": 3,
52
+ "timeout": 30000
53
+ }
54
+ }
55
+ ```
56
+
57
+ Both fields are optional with sensible defaults.
58
+
59
+ ### Examples
60
+
61
+ **Zero config — just add the plugin:**
62
+
63
+ ```json
64
+ {
65
+ "plugin": ["@apolenkov/o-switcher@latest"]
66
+ }
67
+ ```
68
+
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):**
72
+
73
+ ```json
74
+ {
75
+ "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" }
79
+ },
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
+ }
125
+ }
126
+ ```
127
+
128
+ Claude is primary (priority 2). If Claude fails 3 times — switches to GPT.
129
+
130
+ **Custom retry and failover limits:**
131
+
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
+ ```
145
+
146
+ ### How it works
147
+
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
159
+
160
+ - [Getting Started](docs/getting-started.md) — installation, configuration, troubleshooting
161
+ - [API Reference](docs/api-reference.md) — all config options, operator tools, error classes, log format
162
+ - [Examples](docs/examples.md) — multi-key, multi-provider, log analysis, circuit breaker tuning
163
+ - [Architecture](docs/architecture.md) — C4 diagrams, sequence flows, internals
164
+
165
+ ### Development
166
+
167
+ ```bash
168
+ git clone https://github.com/apolenkov/o-switcher.git
169
+ cd o-switcher
170
+ npm install
171
+ npm test # 396 tests
172
+ npm run typecheck # TypeScript strict
173
+ npm run build # ESM + CJS
174
+ ```
175
+
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`:
197
+
198
+ ```json
199
+ {
200
+ "plugin": [
201
+ "@apolenkov/o-switcher@latest"
202
+ ]
203
+ }
204
+ ```
205
+
206
+ Или для локальной разработки:
207
+
208
+ ```json
209
+ {
210
+ "plugin": [
211
+ "/путь/до/o-switcher"
212
+ ]
213
+ }
214
+ ```
215
+
216
+ ### Настройка
217
+
218
+ Все. O-Switcher автоматически находит все настроенные провайдеры из конфига OpenCode. Настраивать таргеты не нужно.
219
+
220
+ Хочешь задать свои retry и timeout?
221
+
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
+ ```
358
+
359
+ ### Лицензия
360
+
361
+ [MIT](LICENSE)