@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/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
- { request_id: requestId, target_id: activeTarget.target_id, health: activeTarget.health_score },
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
- { provider_id: providerId, attributed_target: target.target_id, profile_count: matchingTargets.length },
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
- export {
191
- plugin_default as default,
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.0",
4
- "description": "Routing and execution resilience layer for OpenCode",
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
- "routing",
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": "./dist/plugin.js",
26
- "require": "./dist/plugin.cjs",
27
- "types": "./dist/plugin.d.ts"
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": "./dist/index.js",
31
- "require": "./dist/index.cjs",
32
- "types": "./dist/index.d.ts"
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.