saros-proxy 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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +620 -0
  3. package/dist/cli/opencode-config.d.ts +28 -0
  4. package/dist/cli/opencode-config.js +126 -0
  5. package/dist/cli/opencode-config.js.map +1 -0
  6. package/dist/cli/setup.d.ts +29 -0
  7. package/dist/cli/setup.js +669 -0
  8. package/dist/cli/setup.js.map +1 -0
  9. package/dist/cli/ui.d.ts +27 -0
  10. package/dist/cli/ui.js +116 -0
  11. package/dist/cli/ui.js.map +1 -0
  12. package/dist/config.d.ts +50 -0
  13. package/dist/config.js +341 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/constants.d.ts +25 -0
  16. package/dist/constants.js +162 -0
  17. package/dist/constants.js.map +1 -0
  18. package/dist/dashboard-parser.d.ts +47 -0
  19. package/dist/dashboard-parser.js +88 -0
  20. package/dist/dashboard-parser.js.map +1 -0
  21. package/dist/firefox-cookies.d.ts +42 -0
  22. package/dist/firefox-cookies.js +170 -0
  23. package/dist/firefox-cookies.js.map +1 -0
  24. package/dist/index.d.ts +11 -0
  25. package/dist/index.js +101 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/key-encryption.d.ts +21 -0
  28. package/dist/key-encryption.js +59 -0
  29. package/dist/key-encryption.js.map +1 -0
  30. package/dist/logger.d.ts +11 -0
  31. package/dist/logger.js +35 -0
  32. package/dist/logger.js.map +1 -0
  33. package/dist/proxy-logic.d.ts +108 -0
  34. package/dist/proxy-logic.js +353 -0
  35. package/dist/proxy-logic.js.map +1 -0
  36. package/dist/proxy.d.ts +35 -0
  37. package/dist/proxy.js +551 -0
  38. package/dist/proxy.js.map +1 -0
  39. package/dist/scraper.d.ts +70 -0
  40. package/dist/scraper.js +239 -0
  41. package/dist/scraper.js.map +1 -0
  42. package/dist/validation.d.ts +34 -0
  43. package/dist/validation.js +64 -0
  44. package/dist/validation.js.map +1 -0
  45. package/package.json +73 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Saros 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,620 @@
1
+ # Saros
2
+
3
+ [![CI](https://github.com/vitorvilaca3011/saros-proxy/actions/workflows/ci.yml/badge.svg)](https://github.com/vitorvilaca3011/saros-proxy/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/saros-proxy.svg)](https://www.npmjs.com/package/saros-proxy)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![Node.js Version](https://img.shields.io/badge/node-%3E%3D%2022.0.0-brightgreen.svg)](https://nodejs.org/)
7
+
8
+ Saros — like the eclipse cycle, this proxy predicts exhaustion and rotates accounts before they run out. Built for the OpenCode-Go API.
9
+
10
+ Manage multiple OpenCode-Go API keys behind a single endpoint. When one key is rate-limited, revoked, or returns server errors, the proxy automatically fails over to the next healthy key — with zero downtime.
11
+
12
+ ---
13
+
14
+ ## Contents
15
+
16
+ - [Features](#features)
17
+ - [Architecture](#architecture)
18
+ - [Installation](#installation)
19
+ - [Docker](#docker)
20
+ - [Quick Start](#quick-start)
21
+ - [Configuration Reference](#configuration-reference)
22
+ - [Usage Examples](#usage-examples)
23
+ - [OpenCode Integration](#opencode-integration)
24
+ - [Troubleshooting](#troubleshooting)
25
+ - [Development](#development)
26
+ - [LLM Quick Reference](#llm-quick-reference)
27
+ - [Security](#security)
28
+ - [License](#license)
29
+
30
+ ---
31
+
32
+ ## Features
33
+
34
+ - **Multi-key rotation** — Round-robin distribution across API keys.
35
+ - **Automatic failover** — Retry failed requests with the next available key.
36
+ - **Circuit breaker** — Disable failing keys after a configurable threshold, then lazily re-enable them after a cooldown period.
37
+ - **Streaming support** — SSE pass-through with mid-stream error detection.
38
+ - **Request timeout** — Configurable timeout with graceful 504 response.
39
+ - **Graceful shutdown** — Drains active requests before exiting.
40
+ - **Security hardening** — API key masking in logs, SSRF prevention (HTTPS-only upstream), configurable CORS, host binding validation.
41
+ - **Config validation** — CLI args, YAML file, and environment variables with sensible defaults.
42
+ - **Health endpoint** — `GET /health` returns key status, active requests, and circuit-breaker state.
43
+ - **No database** — Stateless proxy, zero external dependencies beyond Node.js.
44
+
45
+ ---
46
+
47
+ ## Architecture
48
+
49
+ ```
50
+ ┌──────────────────────────────────────────────────────┐
51
+ │ Client / OpenCode │
52
+ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
53
+ │ │ Request 1│ │ Request 2│ │ Request 3│ ... │
54
+ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
55
+ └───────┼──────────────┼──────────────┼────────────────┘
56
+ │ │ │
57
+ ▼ ▼ ▼
58
+ ┌──────────────────────────────────────────────────────┐
59
+ │ Saros (:3000) │
60
+ │ │
61
+ │ ┌─────────────┐ ┌────────────┐ ┌─────────────┐ │
62
+ │ │ Key Selector│──▶│ Circuit │──▶│ Request │ │
63
+ │ │ Round-robin │ │ Breaker │ │ Forwarder │ │
64
+ │ │ + Failover │ │ Threshold │ │ + Timeout │ │
65
+ │ └─────────────┘ │ + Cooldown │ └──────┬──────┘ │
66
+ │ └────────────┘ │ │
67
+ │ ┌─────────────────────────────────────────┘ │
68
+ │ │ ┌─────────────────────┐ │
69
+ │ └──▶ Streaming Detector │ SSE passthrough │
70
+ │ └─────────────────────┘ │
71
+ └──────────────────────┬───────────────────────────────┘
72
+ │
73
+ ┌────────────┼────────────┐
74
+ ▼ ▼ ▼
75
+ ┌──────────┐ ┌──────────┐ ┌──────────┐
76
+ │ Key A │ │ Key B │ │ Key C │
77
+ │ sk-abc… │ │ sk-def… │ │ sk-ghi… │
78
+ └────┬─────┘ └────┬─────┘ └────┬─────┘
79
+ │ │ │
80
+ ▼ ▼ ▼
81
+ ┌───────────────────────────────────────┐
82
+ │ OpenCode-Go API (upstream) │
83
+ │ https://opencode.ai │
84
+ └───────────────────────────────────────┘
85
+ ```
86
+
87
+ ### How Request Distribution Works
88
+
89
+ The proxy uses **two layers** to decide which API key handles each request:
90
+
91
+ #### Layer 1: Round-Robin (always active)
92
+ Every new request cycles to the next key in order:
93
+
94
+ ```
95
+ Request 1 → Key A
96
+ Request 2 → Key B
97
+ Request 3 → Key C
98
+ Request 4 → Key A (wraps around)
99
+ ```
100
+
101
+ This gives you **even distribution by default** — no manual odd/even logic needed. If you have 2 accounts, odd requests go to account 1 and even to account 2 automatically.
102
+
103
+ #### Layer 2: Usage-Based Gating (optional)
104
+ If you enable [dashboard scraping](#usage-based-key-selection), the proxy checks each account's quota usage before selecting a key:
105
+
106
+ - If an account's usage is **≥ threshold** (default 70%), that key is skipped
107
+ - If **all** keys are over threshold, the proxy falls back to the **lowest-usage** key
108
+ - If no usage data is available, round-robin continues unchanged
109
+
110
+ | Scenario | Behavior |
111
+ |---|---|
112
+ | Scraping disabled | Pure round-robin across all keys |
113
+ | Scraping enabled + data available | Round-robin, skipping over-quota accounts |
114
+ | Scraping enabled + no data | Falls back to pure round-robin |
115
+
116
+ #### Concurrent Request Safety
117
+ The proxy tracks which keys are **currently in use** by active requests. A key already handling a streaming request won't be assigned to another request until it completes. This prevents double-booking the same account under load.
118
+
119
+ ---
120
+
121
+ ## Installation
122
+
123
+ ### From npm (when published)
124
+
125
+ ```bash
126
+ npm install -g saros-proxy
127
+ ```
128
+
129
+ ### From source
130
+
131
+ ```bash
132
+ git clone https://github.com/vitorvilaca3011/saros-proxy.git
133
+ cd proxy
134
+ npm install
135
+ npm run build
136
+ ```
137
+
138
+ > **Requirements**: Node.js >= 22.0.0
139
+
140
+ ---
141
+
142
+ ## Docker
143
+
144
+ Run the proxy in a clean Linux container without installing Node.js locally.
145
+
146
+ ### Prerequisites
147
+
148
+ - [Docker](https://docs.docker.com/get-docker/) (with Compose v2)
149
+
150
+ ### Quick start
151
+
152
+ ```bash
153
+ # 1. Create config.yaml from the example (edit with your API keys)
154
+ cp config.example.yaml config.yaml
155
+
156
+ # 2. Build the image
157
+ npm run docker:build
158
+
159
+ # 3. Start the proxy
160
+ npm run docker:run
161
+ ```
162
+
163
+ The proxy is now available at `http://127.0.0.1:3000`.
164
+
165
+ ### Run tests in Docker
166
+
167
+ ```bash
168
+ # Run the full test suite inside a Linux container
169
+ npm run docker:test
170
+ ```
171
+
172
+ ### Manual Docker commands
173
+
174
+ ```bash
175
+ # Build
176
+ docker compose build proxy
177
+
178
+ # Run in foreground
179
+ docker compose up proxy
180
+
181
+ # Run in background (detached)
182
+ docker compose up -d proxy
183
+
184
+ # View logs
185
+ docker compose logs -f proxy
186
+
187
+ # Stop
188
+ docker compose down
189
+
190
+ # Run tests
191
+ docker compose run --rm test
192
+ ```
193
+
194
+ ### Configuration with Docker
195
+
196
+ Mount a custom `config.yaml` or use environment variables:
197
+
198
+ ```bash
199
+ # Using environment variables (no config.yaml needed)
200
+ docker compose run --rm -e OPENCODE_GO_KEYS="home:sk-xxx,work:sk-yyy" proxy
201
+
202
+ # Or edit docker-compose.yml to set env vars directly, then:
203
+ docker compose up proxy
204
+ ```
205
+
206
+ ---
207
+
208
+ ## Quick Start
209
+
210
+ ### Option A: Interactive Setup (Recommended)
211
+
212
+ ```bash
213
+ # Run the setup wizard — it will guide you through everything
214
+ npx saros-proxy setup
215
+
216
+ # Or if installed globally:
217
+ saros-proxy setup
218
+ ```
219
+
220
+ The wizard will:
221
+ 1. Ask for your proxy port (default: 3000)
222
+ 2. Ask for your API keys (name + key value)
223
+ 3. Generate `config.yaml` automatically
224
+ 4. Run a smoke test to verify everything works
225
+
226
+ ### Option B: Manual Setup
227
+
228
+ #### 1. Create a configuration file
229
+
230
+ ```yaml
231
+ # config.yaml
232
+ port: 3000
233
+ host: 127.0.0.1
234
+ upstreamBaseUrl: https://opencode.ai
235
+ keys:
236
+ - label: primary
237
+ key: sk-your-primary-key-here-12345678
238
+ - label: secondary
239
+ key: sk-your-secondary-key-here-87654321
240
+ circuitBreakerThreshold: 3
241
+ circuitBreakerCooldownMs: 60000
242
+ requestTimeoutMs: 30000
243
+ ```
244
+
245
+ #### 2. Run the proxy
246
+
247
+ ```bash
248
+ # Using tsx (development)
249
+ npx tsx src/index.ts
250
+
251
+ # Using built JavaScript
252
+ node dist/index.js
253
+ ```
254
+
255
+ #### 3. Test it
256
+
257
+ ```bash
258
+ curl http://127.0.0.1:3000/health
259
+
260
+ # Query models
261
+ curl http://127.0.0.1:3000/zen/go/v1/models
262
+
263
+ # Chat completion
264
+ curl http://127.0.0.1:3000/zen/go/v1/chat/completions \
265
+ -H "Content-Type: application/json" \
266
+ -d '{"model":"gpt-4","messages":[{"role":"user","content":"Hello"}]}'
267
+ ```
268
+
269
+ ---
270
+
271
+ ## Configuration Reference
272
+
273
+ All configuration options can be set via YAML file (`config.yaml`), environment variables, or CLI flags. Priority order: CLI > YAML > env vars > defaults.
274
+
275
+ ### YAML / Environment Variables
276
+
277
+ | Option | CLI Flag | Env Variable | Default | Description |
278
+ |---|---|---|---|---|
279
+ | `port` | `--port` | `PROXY_PORT` | `3000` | Listening port (1–65535) |
280
+ | `host` | `--host` | `PROXY_HOST` | `127.0.0.1` | Bind address |
281
+ | `upstreamBaseUrl` | — | `UPSTREAM_BASE_URL` | `https://opencode.ai` | Upstream API base URL (HTTPS only) |
282
+ | `circuitBreakerThreshold` | — | `CIRCUIT_BREAKER_THRESHOLD` | `3` | Consecutive failures before disabling a key (1–10) |
283
+ | `circuitBreakerCooldownMs` | — | `CIRCUIT_BREAKER_COOLDOWN_MS` | `60000` | Cooldown in ms before re-enabling a key (1000–3600000) |
284
+ | `requestTimeoutMs` | — | `REQUEST_TIMEOUT_MS` | `30000` | Upstream request timeout in ms (1000–300000) |
285
+ | `allowedOrigins` | — | — | `["http://localhost:*", "http://127.0.0.1:*"]` | CORS allowed origins (use `[]` to allow all) |
286
+ | `keys` | — | `OPENCODE_GO_KEYS` | — | Array of `{label, key}` objects or comma-separated env format |
287
+ | `config` | `--config` | — | `config.yaml` | Path to YAML config file |
288
+
289
+ ### API Key Format
290
+
291
+ Keys must start with `sk-` and be at least 20 characters.
292
+
293
+ **YAML:**
294
+ ```yaml
295
+ keys:
296
+ - label: my-account
297
+ key: sk-your-key-here...
298
+ ```
299
+
300
+ **Environment variable:**
301
+ ```bash
302
+ OPENCODE_GO_KEYS="account1:sk-xxx,account2:sk-yyy"
303
+ ```
304
+
305
+ ### API Key Encryption
306
+
307
+ API keys can be encrypted at rest using AES-256-GCM with scrypt key derivation. When encrypted, keys are stored as `enc:scrypt:...` in `config.yaml` and decrypted at startup using the `OPENCODE_GO_ENCRYPTION_KEY` environment variable.
308
+
309
+ **Setup with encryption:**
310
+ ```bash
311
+ npm run setup
312
+ # When prompted, choose to encrypt keys and enter a master key (min 16 characters)
313
+ ```
314
+
315
+ **Running with encrypted keys:**
316
+ ```bash
317
+ export OPENCODE_GO_ENCRYPTION_KEY="your-master-key"
318
+ npx tsx src/index.ts
319
+ ```
320
+
321
+ **Docker with encrypted keys:**
322
+ ```yaml
323
+ # docker-compose.yml
324
+ environment:
325
+ - OPENCODE_GO_ENCRYPTION_KEY=your-master-key
326
+ ```
327
+
328
+ **Notes:**
329
+ - Encrypted keys use AES-256-GCM with scrypt KDF (N=16384, r=8, p=1)
330
+ - Each key is encrypted with a random salt and IV
331
+ - Plaintext keys (starting with `sk-`) are still supported for backward compatibility
332
+ - The `OPENCODE_GO_KEYS` environment variable always uses plaintext keys (for CI/CD)
333
+ - If keys are encrypted but `OPENCODE_GO_ENCRYPTION_KEY` is not set, the proxy will fail to start with a clear error message
334
+
335
+ ### CLI Flags
336
+
337
+ ```bash
338
+ tsx src/index.ts --config my-config.yaml --port 4000 --host 0.0.0.0
339
+ ```
340
+
341
+ ---
342
+
343
+ ## Usage Examples
344
+
345
+ ### Basic: Run with YAML config
346
+
347
+ ```bash
348
+ # Create config.yaml (see Quick Start), then:
349
+ npx tsx src/index.ts
350
+ ```
351
+
352
+ ### Advanced: Run with environment variables only
353
+
354
+ ```bash
355
+ export PROXY_PORT=4000
356
+ export PROXY_HOST=127.0.0.1
357
+ export OPENCODE_GO_KEYS="home:sk-abc123...,work:sk-def456..."
358
+ export CIRCUIT_BREAKER_THRESHOLD=5
359
+ export CIRCUIT_BREAKER_COOLDOWN_MS=120000
360
+ export REQUEST_TIMEOUT_MS=45000
361
+ export LOG_LEVEL=debug
362
+ export NODE_ENV=development
363
+
364
+ npx tsx src/index.ts
365
+ ```
366
+
367
+ ### Custom config path
368
+
369
+ ```bash
370
+ npx tsx src/index.ts --config /etc/saros/config.yaml
371
+ ```
372
+
373
+ ### Run as a background service (Linux)
374
+
375
+ ```bash
376
+ nohup npx tsx src/index.ts > proxy.log 2>&1 &
377
+ ```
378
+
379
+ ---
380
+
381
+ ## OpenCode Integration
382
+
383
+ Configure OpenCode to route API requests through the proxy. Create or edit your OpenCode config file:
384
+
385
+ **Location:** `~/.config/opencode/opencode.json` (global) or `opencode.json` in your project root
386
+
387
+ ```jsonc
388
+ {
389
+ "$schema": "https://opencode.ai/config.json",
390
+ "provider": {
391
+ "saros-proxy": {
392
+ "npm": "@ai-sdk/openai-compatible",
393
+ "name": "Saros",
394
+ "options": {
395
+ "baseURL": "http://127.0.0.1:3000/zen/go/v1",
396
+ "apiKey": "not-used"
397
+ },
398
+ "models": {
399
+ "glm-5": { "name": "GLM-5" },
400
+ "kimi-k2.5": { "name": "Kimi K2.5" },
401
+ "qwen3.7-plus": { "name": "Qwen 3.7 Plus" }
402
+ }
403
+ }
404
+ }
405
+ }
406
+ ```
407
+
408
+ **Key points:**
409
+ - `baseURL` must end at `/v1` — OpenCode appends route paths internally
410
+ - `apiKey` can be any placeholder — the proxy handles real key selection
411
+ - Model IDs must match what the upstream API expects (e.g., `glm-5`, not `opencode-go/glm-5`)
412
+ - After editing the config, restart OpenCode
413
+
414
+ **Alternative: Environment variable substitution**
415
+
416
+ ```jsonc
417
+ {
418
+ "provider": {
419
+ "saros-proxy": {
420
+ "npm": "@ai-sdk/openai-compatible",
421
+ "name": "Saros",
422
+ "options": {
423
+ "baseURL": "{env:OPENCODE_PROXY_URL}",
424
+ "apiKey": "{env:OPENCODE_PROXY_KEY}"
425
+ }
426
+ }
427
+ }
428
+ }
429
+ ```
430
+
431
+ Then set: `export OPENCODE_PROXY_URL=http://127.0.0.1:3000/zen/go/v1`
432
+
433
+ **Using the proxy in OpenCode:**
434
+
435
+ Once configured, select the proxy provider in OpenCode's model picker, or set it as default:
436
+
437
+ ```jsonc
438
+ {
439
+ "provider": { /* ... as above ... */ },
440
+ "model": "saros-proxy/glm-5"
441
+ }
442
+ ```
443
+
444
+ ---
445
+
446
+ ## Troubleshooting
447
+
448
+ ### Check proxy health
449
+
450
+ ```bash
451
+ curl http://127.0.0.1:3000/health
452
+ ```
453
+
454
+ Returns JSON with key counts, active requests, and circuit-breaker state.
455
+
456
+ ### View logs
457
+
458
+ ```bash
459
+ # Development (pretty-printed, colorized)
460
+ NODE_ENV=development npx tsx src/index.ts
461
+
462
+ # Production (structured JSON)
463
+ NODE_ENV=production npx tsx src/index.ts
464
+ ```
465
+
466
+ ### Verify which key served a request
467
+
468
+ The proxy returns two debug headers on every successful response:
469
+
470
+ | Header | Example | Meaning |
471
+ |---|---|---|
472
+ | `X-Proxy-Key-Label` | `primary` | Which configured key was used |
473
+ | `X-Proxy-Request-Id` | `550e8400-e29b-41d4-a716-446655440000` | Unique request ID for log correlation |
474
+
475
+ ```bash
476
+ # Check which key handled your request
477
+ curl -s -D - http://127.0.0.1:3000/zen/go/v1/models | grep -i x-proxy
478
+ # X-Proxy-Key-Label: primary
479
+ # X-Proxy-Request-Id: 550e8400-e29b-41d4-a716-446655440000
480
+ ```
481
+
482
+ ### Common issues
483
+
484
+ | Symptom | Likely cause | Fix |
485
+ |---|---|---|
486
+ | `No valid API keys configured` | Missing or invalid keys | Check `keys` in config.yaml or `OPENCODE_GO_KEYS` env var |
487
+ | `ECONNREFUSED` on startup | Port already in use | Change `port` or kill the existing process |
488
+ | `504 Gateway Timeout` | Upstream request exceeded timeout | Increase `requestTimeoutMs` or check upstream latency |
489
+ | `All API keys are temporarily unavailable` | All keys disabled by circuit breaker | Wait for cooldown or check upstream status |
490
+ | `CORS violation` | Client origin not in `allowedOrigins` | Add the origin to `allowedOrigins` in config |
491
+
492
+ ---
493
+
494
+ ## Development
495
+
496
+ ### Run tests
497
+
498
+ ```bash
499
+ # Unit tests
500
+ npm test
501
+
502
+ # E2E tests (requires network)
503
+ npx vitest run test/e2e.test.ts
504
+
505
+ # Watch mode
506
+ npm test -- --watch
507
+ ```
508
+
509
+ ### Type check
510
+
511
+ ```bash
512
+ npm run lint
513
+ ```
514
+
515
+ ### Build
516
+
517
+ ```bash
518
+ npm run build
519
+ ```
520
+
521
+ Output goes to `dist/`.
522
+
523
+ ### Project structure
524
+
525
+ ```
526
+ src/
527
+ index.ts — Entry point, server startup, graceful shutdown
528
+ proxy.ts — Hono HTTP app, routing, streaming, failover
529
+ proxy-logic.ts — Pure logic: key selection, circuit breaker, error classification
530
+ proxy-logic.test.ts — Unit tests for proxy logic
531
+ config.ts — Configuration loading from YAML, env vars, CLI
532
+ logger.ts — Structured logging with Pino + key masking
533
+ cli/
534
+ setup.ts — Interactive setup wizard
535
+ test/
536
+ e2e.test.ts — End-to-end tests with mock upstream
537
+ ```
538
+
539
+ ### Contributing
540
+
541
+ 1. Fork the repository.
542
+ 2. Create a feature branch: `git checkout -b feat/my-feature`
543
+ 3. Commit changes: `git commit -am 'Add my feature'`
544
+ 4. Push: `git push origin feat/my-feature`
545
+ 5. Open a pull request.
546
+
547
+ ---
548
+
549
+ ## LLM Quick Reference
550
+
551
+ If you're an AI assistant helping a user install this proxy, use these exact commands:
552
+
553
+ **Prerequisites check:**
554
+ ```bash
555
+ node --version # Must be >= 22.0.0
556
+ ```
557
+
558
+ **Install from source:**
559
+ ```bash
560
+ git clone https://github.com/vitorvilaca3011/saros-proxy.git
561
+ cd proxy
562
+ npm install
563
+ npm run build
564
+ ```
565
+
566
+ **Configure:**
567
+ ```bash
568
+ npm run setup # Interactive wizard
569
+ # OR manually: cp config.example.yaml config.yaml && edit config.yaml
570
+ ```
571
+
572
+ **Run:**
573
+ ```bash
574
+ npm run dev # Development
575
+ node dist/index.js # Production
576
+ ```
577
+
578
+ **Verify:**
579
+ ```bash
580
+ curl http://127.0.0.1:3000/health
581
+ ```
582
+
583
+ **Docker quick start:**
584
+ ```bash
585
+ cp config.example.yaml config.yaml # Edit with your keys
586
+ npm run docker:build
587
+ npm run docker:run
588
+ ```
589
+
590
+ **Add to OpenCode config:**
591
+ ```bash
592
+ # Run setup wizard and choose "Update OpenCode config automatically"
593
+ npm run setup
594
+
595
+ # Or manually edit ~/.config/opencode/opencode.json and add:
596
+ {
597
+ "provider": {
598
+ "saros-proxy": {
599
+ "npm": "@ai-sdk/openai-compatible",
600
+ "name": "Saros",
601
+ "options": {
602
+ "baseURL": "http://127.0.0.1:3000/zen/go/v1",
603
+ "apiKey": "not-used"
604
+ }
605
+ }
606
+ }
607
+ }
608
+ ```
609
+
610
+ ---
611
+
612
+ ## Security
613
+
614
+ For vulnerability reporting, see [SECURITY.md](SECURITY.md).
615
+
616
+ ---
617
+
618
+ ## License
619
+
620
+ [MIT](LICENSE) © 2026 Saros Contributors
@@ -0,0 +1,28 @@
1
+ /**
2
+ * opencode-config.ts — OpenCode client configuration management.
3
+ *
4
+ * Handles reading, merging, and writing the user's opencode.json
5
+ * to add or update the proxy provider configuration.
6
+ */
7
+ /** Return the default opencode.json path for the current platform. */
8
+ export declare function getDefaultOpencodeConfigPath(): string;
9
+ export interface OpencodeConfigResult {
10
+ success: boolean;
11
+ path?: string;
12
+ created?: boolean;
13
+ error?: string;
14
+ }
15
+ /**
16
+ * Update or create the user's opencode.json with the proxy provider.
17
+ *
18
+ * @param port — The proxy port to use in baseURL
19
+ * @param options.configPath — Explicit path to opencode.json (optional)
20
+ * @returns Result object with success status and path
21
+ */
22
+ export declare function updateOpencodeConfig(port: number, options?: {
23
+ configPath?: string;
24
+ }): OpencodeConfigResult;
25
+ /**
26
+ * Generate a manual configuration snippet that users can paste.
27
+ */
28
+ export declare function generateManualConfigSnippet(port: number): string;