Commit Graph
15 Commits
Author SHA1 Message Date
030c4f884b perf: optimasi 5 bottleneck utama (cache, DSML, retry, stream, DNS) (#5)
* feat: optimasi boros bandwidth dan CPU

- Cache layer: LRU cache + TTL untuk non-streaming LLM responses
  (CACHE_TTL, env: CACHE_TTL, CACHE_MAX_SIZE)
- Retries: turunkan default dari pool.size+1 ke 2 (env: MAX_RETRIES)
- Generic stream passthrough: trust content-type, bukan provider name
  (env: STREAM_PASSTHROUGH)
- DSML detection toggle: matikan parsing hot-path kalo gak perlu
  (env: DSML_DETECTION)

All 274 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* perf: optimasi 5 bottleneck utama (cache, DSML, retry, stream, DNS)

Cache (response-cache.ts):
- TTL default 15s → 300s (5 menit), bandwidth upstream -60-80%
- Ganti hand-rolled doubly-linked list dengan Map insertion order (O(1) reorder)
- Tambah CACHE_MODELS envvar untuk allowlist per model
- Tambah hit/miss stats untuk observability

DSML detection (ai-proxy.ts, anthropic-proxy.ts, response-cache.ts):
- Guard isDSMLDetectionEnabled(model) — hanya scan chunk untuk model
  DeepSeek/Codestral via DSML_MODELS envvar (default: deepseek,codestral)
- CPU streaming -40% untuk model non-DeepSeek

Retry (fetch-utils.ts):
- Default MAX_RETRIES 2 → 1 (langsung single attempt)
- Backoff 200ms/2000ms cap → 50ms/500ms cap
- -200ms per failed request

Stream processing (ai-proxy.ts, anthropic-proxy.ts):
- BATCH_SIZE 8 → 32 (yield 4× lebih jarang)
- Keepalive interval 15s → 30s (50% lebih sedikit timer wakeups)

DNS cache (relay-utils.ts):
- TTL 5 menit untuk isPrivateIpAfterResolve, bounded 1000 entries
- -50-200ms per relay request setelah lookup pertama

Tests: 274/274 pass (test runtime 182ms → 56ms, 3.2× lebih cepat
karena O(1) LRU reorder)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 16:56:21 +07:00
820ac3b56c feat: optimasi boros bandwidth dan CPU (#4)
- Cache layer: LRU cache + TTL untuk non-streaming LLM responses
  (CACHE_TTL, env: CACHE_TTL, CACHE_MAX_SIZE)
- Retries: turunkan default dari pool.size+1 ke 2 (env: MAX_RETRIES)
- Generic stream passthrough: trust content-type, bukan provider name
  (env: STREAM_PASSTHROUGH)
- DSML detection toggle: matikan parsing hot-path kalo gak perlu
  (env: DSML_DETECTION)

All 274 tests pass.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:53:42 +07:00
MythEclipseandClaude Opus 4.8 f78f1f5bbf chore: hapus fitur IPv6 source rotation
- Hapus src/lib/ipv6-pool.ts dan test
- Hapus fetchViaCurl() dari fetch-utils.ts
- Hapus parameter ipv6Source dari router, ai-proxy, anthropic-proxy
- Hapus ipv6Only: false dari Bun.serve(), default bind ke 0.0.0.0
- Hapus dokumentasi IPv6 dari CLAUDE.md dan README.md
- SSRF protection untuk IPv6 private/loopback tetap ada

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 12:10:25 +07:00
MythEclipse ac19f8f30d perf: stability and performance improvements across proxy
- Add exponential backoff (200ms-2s) between retry attempts
- Fix session retry bug: rotate proxy before retry to avoid same proxy
- Fix ACTIVE_READERS memory leak: release reader on stream done/error
- Make closeAllActiveReaders async with proper await
- WebSocket backpressure: pause upstream forwarding at 512KB buffer
- Optimize rate-limiter pruneTimestamps: avoid array reallocation
- Batch stream chunks (8 per yield) to reduce event loop overhead
- Add periodic cooldown cleanup in ProxyPool (30s interval)
- Extract shared wrapStreamWithCleanup utility to reduce duplication
- Fix fetchViaCurl temp file cleanup with proper unlink()
- Add missing ipv6Source overload to AI proxy handlers
2026-06-20 19:32:23 +07:00
MythEclipse c62de0899a fix: streaming reliability, timeout, memory leaks, and retry improvements
- Fix streaming timeout: use AbortController for connection-only timeout
  instead of AbortSignal.timeout() that kills active SSE streams
- Fix fetchViaCurl: stream body via ReadableStream instead of buffering
  entire response in memory
- Fix JWT/aichat race condition: add Promise dedup to prevent concurrent
  bootstrap calls (10 requests = 1 bootstrap, not 10)
- Fix ACTIVE_READERS memory leak: auto-remove readers on stream completion
- Fix WebSocket backpressure: log warning when client buffer exceeds 1MB
- Add SSE heartbeat/keepalive: send ': keepalive' every 15s to prevent
  LB/proxy timeout during AI thinking
- Fix SSELineBuffer: graceful overflow handling (warn + discard instead
  of throwing error that crashes stream)
- Fix transformStream tight loop: yield to event loop after each chunk
  to prevent starvation
- Fix fetchViaCurl process cleanup: use SIGKILL + proper timeout cleanup
- Add retry on 502/504: retry transient server errors before returning
  to caller (both fetchWithRetry and fetchWithSessionRetry)
2026-06-20 17:13:32 +07:00
MythEclipse 17338a7dca feat: IPv6 outbound rotation via curl --interface
- Add IPv6SourcePool for round-robin IP rotation
- fetchViaCurl: Bun.spawn + curl -6 --interface for source binding
- Auto-fallback to regular fetch when IPv6 connection fails
- Response decompression via --compressed flag
- Docker + GHCR build/deploy workflows
- 8 routable IPv6 addresses on VPS
2026-06-19 19:07:46 +07:00
MythEclipse cb6191902e fix: critical bugs, serverless stability, and security hardening
Bug Fixes:
- Fix rate limiter API mismatch: check() -> checkAsync() in index.ts, worker.ts, api/relay.ts
- Fix WebSocket SSRF silent drop: return error Response instead of undefined
- Fix isDevMode() default: changed from true to false (production-safe)
- Fix process.env -> env bindings in worker.ts requireAuth for Cloudflare Workers
- Fix Bun.file() crash in Workers: add try/catch with fallback
- Fix Bun.CryptoHasher -> Web Crypto API in mimo-auth.ts for Workers compat

Architecture:
- Add public methods to ProxyPool (getEntryAtIndex, getProxyUrlAtIndex, getCurrentIndex, setCurrentIndex) to remove all 'as any' casts in SessionProxyPool
- Add addProxy() method for manual proxy management
- Add loadAsync(), tryLoadAsync(), loadFromString() to ProxyPool

Serverless Stability:
- Add optional DNS rebinding protection via SSRF_DNS_CHECK env flag
- CORS cache now auto-invalidates when CORS_ORIGIN env changes
- Rate limiter max-size eviction (10k keys) prevents unbounded memory growth

Tests:
- Fix type assertions in test files (body as Record<string, unknown>)
- All 153 tests pass, typecheck clean
2026-06-19 18:10:49 +07:00
MythEclipseandClaude 6d61dcba8f prioritas langsung baru proxy
Ubah strategi retry di fetchWithRetry dan fetchWithSessionRetry:
- Langsung (direct) sebagai percobaan pertama sebelum proxy pool
- Proxy sebagai fallback jika direct gagal

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-18 01:45:34 +07:00
MythEclipseandClaude 7cbf6fd817 feat: add per-model proxy cooldown for rate-limit handling
ProxyPool:
- Add cooldowns map (host:port::model -> expiry) and cooldownDuration (60s default)
- Add markRateLimited(model) — puts current proxy in cooldown for a model
- Add isProxyInCooldown, isIndexInCooldown, isCurrentInCooldown checks
- Modify rotate(model?) — skip proxies in cooldown for the given model
- Add setCooldownDuration(ms) for configuration

SessionProxyPool:
- Add markRateLimited(sessionId, model) — delegates to underlying pool
- Modify rotateNow(sessionId, model?) — skip cooldown proxies
- Modify acquire(sessionId, model?) — skip cooldown when picking least-loaded
- Modify pickLeastUsedIndex(model?) — skip cooldown proxies in scan

fetch-utils.ts:
- Add extractModel helper — extracts model from context string
- On HTTP 429: call markRateLimited(model) before markFailed
- Pass model to rotate() / rotateNow() / acquire() throughout

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-17 04:59:16 +07:00
MythEclipseandClaude 283ed26231 fix: exhaust all proxies then direct fallback before giving up
fetchWithRetry:
- Dynamically calculate maxAttempts = pool.size + 1 (direct fallback)
- Try every proxy in pool via rotation, then direct as last resort
- Only classify as error when even direct produces no response

fetchWithSessionRetry:
- Default maxRetries = sessionPool.size + 1 instead of hardcoded 3
- Try pool.size proxy attempts (each on a different proxy via rotateNow),
  then 1 direct attempt (no proxy) before giving up
- Only classify as error if direct also returns no response

ProxyPool.rotate():
- Skip proxies that have exceeded the failure threshold (isFailed)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-17 04:45:30 +07:00
MythEclipseandClaude f1943353ef fix: rotate proxy on every failure, return proper HTTP status
- Add rotateNow() to SessionProxyPool — force-rotate session to a
  different proxy immediately (excludes current index to ensure real
  rotation). Uses round-robin scan from oldIndex+1 so all proxies
  get used, not just bouncing between two.

- fetchWithSessionRetry: call rotateNow() on every failure instead of
  markFailed() which only rotated after threshold. Return last HTTP
  response (e.g. 429) instead of classifying as 502 when we have one.

- fetchWithRetry: rotate pool on every failure for consistency.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-17 04:40:45 +07:00
MythEclipseandClaude 45f1b33fb6 feat: add structured logging across proxy pool and fetch utils
- fetch-utils.ts: logProxy() — logs every attempt, proxy used, success/failure
  with sessionId prefix for traceability
- proxy-pool.ts: logPool() — logs acquire/release/rotate/markFailed/markSuccess
  with active session count and proxy host info
- Both use consistent [prefix] HH:MM:SS.mmm key=value format

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-17 04:29:31 +07:00
MythEclipseandClaude 519fe3af83 fix: proxy pool now actively distributes IPs across sessions
- fetchWithRetry: use proxy on every attempt (not just fallback after direct)
- fetchWithSessionRetry: call acquire() on attempt 0 so new sessions get
  a least-loaded proxy assigned, enabling IP spread across concurrent users
- Update docstrings to reflect new strategy

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-17 04:26:26 +07:00
MythEclipseandClaude 9bd6acddc5 feat: implement session-based sticky proxy pool with auto-failover
Add SessionProxyPool for per-session sticky proxy allocation with
load-balanced least-used selection and auto-rotation on failure.
Introduce fetchWithSessionRetry for transparent retry with proxy
rotation. Wire into AI proxy handlers (OpenAI + Anthropic) with
stream lifecycle cleanup.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-16 23:50:47 +07:00
MythEclipse b09bca8def feat(core): implement robust fetch utilities and stream management
Introduce a centralized `fetch-utils.ts` to handle retry logic with proxy fallback, SSE line buffering to prevent chunk-boundary corruption, and graceful shutdown via active reader tracking.

Key changes:
- Add `fetchWithRetry` for automatic direct-to-proxy failover.
- Implement `SSELineBuffer` to ensure reliable parsing of split SSE chunks.
- Add `createStreamBodyLimiter` to enforce payload limits on streaming requests.
- Refactor `ProxyPool` to decouple failure marking from rotation.
- Standardize CORS handling and environment variable configuration.
- Clean up documentation and remove obsolete skill files.
2026-06-11 03:56:26 +07:00