@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/CHANGELOG.md +18 -0
- package/LICENSE +199 -21
- package/README.md +88 -288
- package/dist/{chunk-BTDKGS7P.js → chunk-IKNWSNAS.js} +120 -307
- package/dist/chunk-VABBGKSR.cjs +1663 -0
- package/dist/index.cjs +583 -1927
- package/dist/index.js +348 -224
- package/dist/plugin.cjs +35 -1031
- package/dist/plugin.js +16 -34
- package/package.json +47 -11
- package/CONTRIBUTING.md +0 -72
- package/dist/chunk-BTDKGS7P.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/plugin.cjs.map +0 -1
- package/dist/plugin.js.map +0 -1
- package/docs/api-reference.md +0 -286
- package/docs/architecture.md +0 -511
- package/docs/examples.md +0 -190
- package/docs/getting-started.md +0 -316
- package/scripts/collect-errors.ts +0 -159
- package/scripts/corpus.jsonl +0 -5
package/dist/plugin.js
CHANGED
|
@@ -1,20 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createAuditLogger,
|
|
3
|
-
createAuthWatcher,
|
|
4
|
-
createCircuitBreaker,
|
|
5
|
-
createConcurrencyTracker,
|
|
6
|
-
createCooldownManager,
|
|
7
|
-
createLogSubscriber,
|
|
8
|
-
createProfileTools,
|
|
9
|
-
createRegistry,
|
|
10
|
-
createRequestTraceBuffer,
|
|
11
|
-
createRoutingEventBus,
|
|
12
|
-
discoverTargets,
|
|
13
|
-
discoverTargetsFromProfiles,
|
|
14
|
-
generateCorrelationId,
|
|
15
|
-
loadProfiles,
|
|
16
|
-
validateConfig
|
|
17
|
-
} from "./chunk-BTDKGS7P.js";
|
|
1
|
+
import { createAuditLogger, createProfileTools, generateCorrelationId, loadProfiles, discoverTargetsFromProfiles, discoverTargets, createAuthWatcher, validateConfig, createRegistry, createRoutingEventBus, createCircuitBreaker, createConcurrencyTracker, createCooldownManager, createRequestTraceBuffer, createLogSubscriber } from './chunk-IKNWSNAS.js';
|
|
18
2
|
|
|
19
3
|
// src/plugin.ts
|
|
20
4
|
var initializeSwitcher = (rawConfig) => {
|
|
@@ -97,10 +81,7 @@ var server = async (_input) => {
|
|
|
97
81
|
try {
|
|
98
82
|
const initialized = initializeSwitcher(rawConfig);
|
|
99
83
|
Object.assign(state, initialized);
|
|
100
|
-
logger.info(
|
|
101
|
-
{ targets: state.registry?.getAllTargets().length },
|
|
102
|
-
"O-Switcher initialized"
|
|
103
|
-
);
|
|
84
|
+
logger.info({ targets: state.registry?.getAllTargets().length }, "O-Switcher initialized");
|
|
104
85
|
state.authWatcher = createAuthWatcher({ logger });
|
|
105
86
|
await state.authWatcher.start();
|
|
106
87
|
logger.info("Auth watcher started");
|
|
@@ -120,9 +101,7 @@ var server = async (_input) => {
|
|
|
120
101
|
const targets = state.registry.getAllTargets();
|
|
121
102
|
const providerId = input.provider?.info?.id ?? input.provider?.info?.name ?? input.model?.providerID ?? void 0;
|
|
122
103
|
if (!providerId) return;
|
|
123
|
-
const matchingTargets = targets.filter(
|
|
124
|
-
(t) => t.provider_id === providerId && t.enabled
|
|
125
|
-
);
|
|
104
|
+
const matchingTargets = targets.filter((t) => t.provider_id === providerId && t.enabled);
|
|
126
105
|
if (matchingTargets.length === 0) return;
|
|
127
106
|
const activeTarget = matchingTargets.reduce(
|
|
128
107
|
(best, t) => t.health_score > best.health_score ? t : best
|
|
@@ -135,7 +114,11 @@ var server = async (_input) => {
|
|
|
135
114
|
output.maxOutputTokens = Math.min(output.maxOutputTokens, 4096);
|
|
136
115
|
}
|
|
137
116
|
state.logger?.info(
|
|
138
|
-
{
|
|
117
|
+
{
|
|
118
|
+
request_id: requestId,
|
|
119
|
+
target_id: activeTarget.target_id,
|
|
120
|
+
health: activeTarget.health_score
|
|
121
|
+
},
|
|
139
122
|
"Adjusted params for degraded target"
|
|
140
123
|
);
|
|
141
124
|
}
|
|
@@ -153,16 +136,18 @@ var server = async (_input) => {
|
|
|
153
136
|
if (error && "providerID" in error) {
|
|
154
137
|
const providerId = error.providerID;
|
|
155
138
|
if (providerId) {
|
|
156
|
-
const matchingTargets = state.registry.getAllTargets().filter(
|
|
157
|
-
(t) => t.provider_id === providerId
|
|
158
|
-
);
|
|
139
|
+
const matchingTargets = state.registry.getAllTargets().filter((t) => t.provider_id === providerId);
|
|
159
140
|
if (matchingTargets.length === 0) return;
|
|
160
141
|
const target = matchingTargets.length === 1 ? matchingTargets[0] : matchingTargets.reduce(
|
|
161
142
|
(worst, t) => t.health_score < worst.health_score ? t : worst
|
|
162
143
|
);
|
|
163
144
|
if (matchingTargets.length > 1) {
|
|
164
145
|
state.logger?.warn(
|
|
165
|
-
{
|
|
146
|
+
{
|
|
147
|
+
provider_id: providerId,
|
|
148
|
+
attributed_target: target.target_id,
|
|
149
|
+
profile_count: matchingTargets.length
|
|
150
|
+
},
|
|
166
151
|
"Multiple profiles for provider \u2014 error attributed to worst-health target (plugin-only mode limitation)"
|
|
167
152
|
);
|
|
168
153
|
}
|
|
@@ -187,8 +172,5 @@ var pluginModule = {
|
|
|
187
172
|
server
|
|
188
173
|
};
|
|
189
174
|
var plugin_default = pluginModule;
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
server
|
|
193
|
-
};
|
|
194
|
-
//# sourceMappingURL=plugin.js.map
|
|
175
|
+
|
|
176
|
+
export { plugin_default as default, server };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@albinocrabs/o-switcher",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Seamless OpenRouter profile rotation for OpenCode — buy multiple subscriptions, use as one pool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -10,33 +10,60 @@
|
|
|
10
10
|
},
|
|
11
11
|
"keywords": [
|
|
12
12
|
"opencode",
|
|
13
|
+
"openrouter",
|
|
13
14
|
"llm",
|
|
14
|
-
"
|
|
15
|
+
"profile-rotation",
|
|
16
|
+
"quota-pool",
|
|
15
17
|
"resilience",
|
|
16
|
-
"circuit-breaker",
|
|
17
18
|
"failover",
|
|
18
19
|
"retry"
|
|
19
20
|
],
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE",
|
|
25
|
+
"CHANGELOG.md"
|
|
26
|
+
],
|
|
20
27
|
"main": "dist/plugin.js",
|
|
21
28
|
"module": "dist/plugin.js",
|
|
22
29
|
"types": "dist/plugin.d.ts",
|
|
23
30
|
"exports": {
|
|
24
31
|
".": {
|
|
25
|
-
"import":
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
"import": {
|
|
33
|
+
"types": "./dist/plugin.d.ts",
|
|
34
|
+
"default": "./dist/plugin.js"
|
|
35
|
+
},
|
|
36
|
+
"require": {
|
|
37
|
+
"types": "./dist/plugin.d.cts",
|
|
38
|
+
"default": "./dist/plugin.cjs"
|
|
39
|
+
}
|
|
28
40
|
},
|
|
29
41
|
"./api": {
|
|
30
|
-
"import":
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
"import": {
|
|
43
|
+
"types": "./dist/index.d.ts",
|
|
44
|
+
"default": "./dist/index.js"
|
|
45
|
+
},
|
|
46
|
+
"require": {
|
|
47
|
+
"types": "./dist/index.d.cts",
|
|
48
|
+
"default": "./dist/index.cjs"
|
|
49
|
+
}
|
|
33
50
|
}
|
|
34
51
|
},
|
|
35
52
|
"scripts": {
|
|
36
53
|
"build": "tsup",
|
|
37
54
|
"test": "vitest run",
|
|
38
55
|
"test:watch": "vitest",
|
|
39
|
-
"typecheck": "tsc --noEmit"
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"lint": "eslint src/",
|
|
58
|
+
"lint:fix": "eslint src/ --fix",
|
|
59
|
+
"lint:pkg": "publint && attw --pack . --ignore-rules no-resolution",
|
|
60
|
+
"format:check": "prettier --check \"src/**/*.ts\" \"*.config.*\" \"*.json\" \".prettierrc\"",
|
|
61
|
+
"format": "prettier --write \"src/**/*.ts\" \"*.config.*\" \"*.json\" \".prettierrc\"",
|
|
62
|
+
"test:coverage": "vitest run --coverage",
|
|
63
|
+
"changeset": "changeset",
|
|
64
|
+
"version-packages": "changeset version",
|
|
65
|
+
"release": "npm run build && npm run lint:pkg && changeset publish",
|
|
66
|
+
"prepublishOnly": "npm run lint && npm run build && npm run lint:pkg"
|
|
40
67
|
},
|
|
41
68
|
"dependencies": {
|
|
42
69
|
"cockatiel": "^3.2.1",
|
|
@@ -46,12 +73,21 @@
|
|
|
46
73
|
"zod": "^4.3.6"
|
|
47
74
|
},
|
|
48
75
|
"devDependencies": {
|
|
76
|
+
"@arethetypeswrong/cli": "^0.18.2",
|
|
77
|
+
"@changesets/changelog-github": "^0.6.0",
|
|
78
|
+
"@changesets/cli": "^2.30.0",
|
|
79
|
+
"@eslint/js": "^10.0.1",
|
|
49
80
|
"@opencode-ai/plugin": "^1.4.3",
|
|
50
81
|
"@tsconfig/node20": "^20.1.9",
|
|
51
82
|
"@types/node": "^22",
|
|
83
|
+
"@vitest/coverage-v8": "^4.1.4",
|
|
84
|
+
"eslint": "^10.2.0",
|
|
52
85
|
"pino-pretty": "^13",
|
|
86
|
+
"prettier": "^3.8.2",
|
|
87
|
+
"publint": "^0.3.18",
|
|
53
88
|
"tsup": "^8.5.1",
|
|
54
89
|
"typescript": "^5.4.0",
|
|
90
|
+
"typescript-eslint": "^8.58.1",
|
|
55
91
|
"vitest": "^4.1.4"
|
|
56
92
|
},
|
|
57
93
|
"engines": {
|
package/CONTRIBUTING.md
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
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.
|