- Thinking config: 24 tests for applyThinkingSuffix, detectTierFromModel,
getThinkingValueForTier, and applyThinkingConfig with compositeTierThinking
- Fallback: 42 tests for isProviderError, detectFailedTier, applyFallback,
and PROVIDER_ERROR_PATTERNS
- Variant service: 9 tests for updateCompositeVariant,
saveCompositeVariantUnified, and listVariantsFromConfig with composite types
* fix(cliproxy): migrate deprecated gemini-claude-* model names to upstream claude-* names (#515)
* fix(cliproxy): migrate deprecated gemini-claude-* model names to upstream claude-* names
CLIProxyAPI registry no longer recognizes the gemini-claude-* prefix convention.
Model names in catalog, base config, and user settings are migrated to upstream
claude-* names. Auto-migration in env-builder rewrites existing user settings on
load and persists the change.
Closes#513
* fix: address code review feedback — sync UI layer and add migration tests
- Sync UI isNativeGeminiModel() with backend (remove gemini-claude- exclusion)
- Update UI model catalog agy entries from gemini-claude-* to claude-*
- Update CI/CD workflow and code-reviewer default model names
- Add unit tests for migrateDeprecatedModelNames() logic
* fix(hooks): isolate image type check before error-prone processing (#514)
* fix(hooks): isolate image type check before error-prone processing
Restructure processHook() into two phases so non-image Read calls
never see hook error messages. Phase 1 defensively checks tool name
and file extension, exiting 0 silently on any failure. Phase 2 only
runs for confirmed image/PDF files where errors are relevant.
Closes#511
* fix(hooks): sync image analyzer hook file on every profile launch
Add installImageAnalyzerHook() call to cliproxy executor, matching
the existing installWebSearchHook() pattern. This ensures the .cjs
file in ~/.ccs/hooks/ gets refreshed from the npm package on every
launch, so users receive hook updates after npm update.
* chore(release): 7.41.0-dev.1 [skip ci]
* fix(cliproxy): add fork:true for Claude model aliases in config generator (#523)
Config generator now outputs fork:true for Claude model alias entries,
ensuring both upstream (claude-*) and aliased (gemini-claude-*) model
names appear in /v1/models listings. Also preserves fork flag when
parsing user-added aliases during config regeneration.
Bumps config version to v7 to trigger regeneration on next ccs doctor.
Closes#522
* chore(release): 7.41.0-dev.2 [skip ci]
* feat(cliproxy): add account safety guards to prevent Google account bans (#516)
* feat(cliproxy): add account safety guards to prevent Google account bans
Implements cross-provider isolation to prevent Google from flagging
concurrent OAuth usage across different client IDs (ref: #509, #512).
Three pillars:
1. Auto-pause enforcement at session launch — conflicting accounts in
other Google OAuth providers are paused so CLIProxyAPI can't use them,
restored on session exit with crash recovery via auto-paused.json
2. Ban/disable detection — error responses matching Google ban patterns
auto-pause the affected account to prevent further damage
3. Cross-provider conflict warnings during OAuth registration
Key design decisions:
- PID-based session tracking for crash recovery (dead PID = restore)
- Timestamp comparison prevents restoring ban-paused accounts on exit
- Schema validation on auto-paused.json prevents corrupted state
- Falls back to warn-only when another session is managing isolation
* fix(cliproxy): address code review feedback (attempt 1/5)
- Re-read auto-paused.json before write in enforceProviderIsolation to
reduce concurrent write race window
- Use actual email from registry for display instead of raw accountId
- Export maskEmail for testability
- Add 27 unit tests covering ban detection, email masking,
cross-provider duplicate detection, enforcement lifecycle,
crash recovery, and timestamp-guarded restore
* fix(cliproxy): address remaining review feedback (attempt 2/5)
- Add handleBanDetection test verifying account pause on ban error
- Add warnCrossProviderDuplicates tests (true/false/non-Google)
- Document PID reuse limitation in isPidAlive JSDoc comment
* chore(release): 7.41.0-dev.3 [skip ci]
* feat(cliproxy): runtime quota monitoring during active sessions (#529)
* feat(cliproxy): add runtime quota monitoring during active sessions
Adds adaptive background quota polling to detect and respond to quota
exhaustion during active CLIProxy sessions. Prevents rate-limit-driven
account bans by auto-cooling exhausted accounts and switching defaults.
- Adaptive polling: 300s normal, 60s at 20% threshold, stops at 0%
- Stderr warnings at 20%, boxed exhaustion alerts at 0%
- Cooldown + default switch on exhaustion (existing patterns)
- Configurable via quota_management.runtime_monitor in config.yaml
- Timer.unref() prevents blocking process exit
- monitorStopped guard for in-flight poll safety
Closes#524
* fix: address code review feedback (attempt 1/5)
- M1: Round quotaPercent display with Math.round() to avoid ugly floats
- M2: Rename exhaust_threshold -> exhaustion_threshold for consistency
with existing auto.exhaustion_threshold config field
- M3: Replace async not.toThrow() with direct await assertion pattern
* fix: address code review feedback (attempt 2/5)
- Remove .claude/agent-memory/ from tracking and add to .gitignore
- Unify cooldown_minutes default to 5 (was 10 in runtime_monitor, 5 in auto)
- Add threshold validation in startQuotaMonitor (warn > exhaustion)
- Document intentional post-switch monitoring gap in code comment
* chore(release): 7.41.0-dev.4 [skip ci]
* fix(cliproxy): mask email in ban detection and fix JSDoc default
- Use maskEmail() in handleBanDetection output for consistency
- Fix cooldown_minutes JSDoc: default is 5, not 10
* chore(release): 7.41.0-dev.5 [skip ci]
* fix(cliproxy): address all review feedback (Low + informational)
- Add sync constraint comment on process.exit handler (executor)
- Add TOCTOU race acceptability comment (account-safety)
- Mask email in handleQuotaExhaustion reason string
- Use realistic exhaustion_threshold (5) in test configs
* chore(release): 7.41.0-dev.6 [skip ci]
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Replace permissive JSON Schema Draft-07 whitelist with strict
Gemini-compatible field set (22 fields). The "examples" field
in Claude Code tool schemas caused 400 errors from Gemini API.
Also strips other unsupported fields: $ref, $defs, oneOf, allOf,
additionalProperties, const, if/then/else, etc.
Safe change — sanitizer only runs for CLIProxy profiles (Gemini,
Codex, Antigravity), never for direct Anthropic API requests.
Closes#155
Track hasReceivedMessageDelta and hasReceivedMessageStop in both
streaming paths (pipe-through and SSE-processing). Conditionally
omit these events from buildSyntheticErrorResponse() when upstream
already sent them, preventing protocol violations.
Closes#491
* fix(cliproxy): disable 1M extended context for gemini-claude-opus-4-6-thinking
Antigravity backend only supports 256k context window for this model,
not 1M as previously declared. Set extendedContext: false with TODO
comment for easy re-enable when backend adds support.
Closes#490
* fix(cliproxy): strip [1m] suffix at runtime for models without extended context
Users with existing [1m] in saved agy.settings.json will now have it
automatically stripped at runtime when the model no longer supports
extended context. Prevents misleading context window claims.
* fix(cliproxy): guard against empty upstream SSE responses in agy profile
When CLIProxyAPIPlus drops unsigned thinking blocks during sub-agent
execution, the response stream can contain no content_block_start or
message_delta events. This causes Claude Code CLI to crash with
"No assistant message found".
Add empty response detection in the tool sanitization proxy's streaming
handler. Both the pipe-through and SSE-processing paths now track
whether meaningful content was received. If upstream sent data but no
content blocks on a 200 OK, a synthetic minimal valid SSE response is
injected to prevent the client crash and surface a clear error message.
Closes#350
* fix(cliproxy): improve empty response detection and add tests
Address code review findings:
- Remove message_delta from content detection (lifecycle event, not
content); only content_block_start indicates actual content
- Add try-catch in end handlers to handle client disconnects gracefully
- Add 3 integration tests: empty stream injection, normal stream
passthrough, 4xx/5xx non-injection
* fix(cliproxy): avoid duplicate message_start in synthetic response
Track whether upstream already sent a message_start event. When
injecting the synthetic error response, omit message_start if upstream
already sent one, preventing duplicate events in the SSE stream.
Addresses PR review feedback from ccs-reviewer[bot].
* test(cliproxy): add SSE processing path and real failure mode tests
Address code review observations:
- Add test exercising SSE processing path (sanitized tool names) for
empty response detection, ensuring both code paths are covered
- Add test mirroring real failure mode where upstream sends only
message_start then ends abruptly (no message_delta/message_stop)
- Document duplicate message_start assumption with inline comment
* fix(version): show active config path instead of deprecated config.json
The version command was using deprecated getConfigPath() which always
returned config.json path. Now uses getActiveConfigPath() which shows
config.yaml in unified mode or config.json in legacy mode.
* chore(release): 7.37.1-dev.1 [skip ci]
* fix(ui): use native dynamic import to fix Node 24 ESM/CJS interop
TypeScript compiles import() to require() when targeting CommonJS,
which breaks ESM packages like ora on Node 24. Use new Function()
to create native dynamic import at runtime, bypassing TS transform.
Closes#472
* chore(release): 7.37.1-dev.2 [skip ci]
* fix(env): strip ANTHROPIC_* from account/default profiles
Account and default profiles inherit process.env which may contain
stale ANTHROPIC_BASE_URL from prior CLIProxy sessions. This causes
ConnectionRefused errors when Claude tries to hit an unavailable proxy.
Settings-based profiles already handle this by explicitly injecting
their own ANTHROPIC_* values. This fix applies the same protection
to account/default profiles by stripping ANTHROPIC_* before spawn.
Closes#474
* test(env): add unit tests for stripAnthropicEnv
Address code review feedback from PR #475. Tests cover:
- Removing all ANTHROPIC_* keys
- Preserving non-ANTHROPIC keys
- Empty object handling
- Undefined value preservation
- Case sensitivity (only uppercase ANTHROPIC_)
- All ANTHROPIC_ prefixed variants stripped
* chore(release): 7.37.1-dev.3 [skip ci]
* feat(cliproxy): add extended context support for 1M token window
Add --1m and --no-1m flags to enable/disable 1M token context window.
Uses Claude Code's [1m] suffix mechanism.
Behavior:
- Gemini models: auto-enabled by default
- Claude models: opt-in with --1m flag
- New extendedContext field in model catalog
Also adds Claude Opus 4.6 to model catalog with extended context support.
Closes#103
* feat(ui): add extended context toggle in dashboard model config
- Add ExtendedContextToggle component for 1M token context window
- Add Claude Opus 4.6 (claude-opus-4-6-20260203) to model catalogs
- Mark Gemini and Claude models with extendedContext: true
- Toggle only appears when selected model supports extended context
- Auto-enabled info for native Gemini, opt-in info for Claude
Part of extended context feature implementation for issue #103.
* fix: address code review findings and CI failure
- Fix CI error: add missing 'provider' prop to ModelConfigSection
- Fix case sensitivity in applyExtendedContextSuffix
- Fix whitespace handling in stripModelSuffixes
- Handle --1m=value and --no-1m=value CLI patterns
- Add warning when --1m used on unsupported model
- Sync agy catalog: add extendedContext to gemini-3-pro-preview
- Extract isNativeGeminiModel to shared utility (DRY)
- Add 21 unit tests for extended-context-config
* fix(catalog): rename claude-opus-4-6-20260203 to claude-opus-4-6
* fix(ui): wire extended context toggle through component tree
- Add extendedContextEnabled and toggleExtendedContext to useProviderEditor hook
- Store setting as CCS_EXTENDED_CONTEXT env var in provider settings
- Pass props through provider-editor → model-config-tab → model-config-section
- Update UseProviderEditorReturn type with new properties
* fix(ui): apply [1m] suffix directly to model strings in settings
- Toggle now applies/strips [1m] suffix to all ANTHROPIC_*MODEL env vars
- Extended context detected by checking if any model has [1m] suffix
- Remove legacy CCS_EXTENDED_CONTEXT flag approach
- Add suffix utilities: applyExtendedContextSuffix, stripExtendedContextSuffix
- Raw Configuration now shows actual model values with [1m] suffix
* fix(qwen): update model catalog with correct context windows and tier mappings
- Update context window specs from official Alibaba docs:
- Qwen3 Coder Plus: 1M context (was 32K)
- Qwen3 Max: 256K context (flagship)
- Qwen3 Coder Flash: fast code generation
- Fix preset mappings for Claude tier equivalence:
- Opus → qwen3-max (flagship 256K)
- Sonnet → qwen3-coder-plus (balanced 1M)
- Haiku → qwen3-coder-flash (fast)
- Update provider descriptions to reflect 256K-1M context range
- Add all 7 Qwen models from CLIProxyAPI: qwen3-coder-plus, qwen3-max,
qwen3-max-preview, qwen3-235b, qwen3-vl-plus, qwen3-coder-flash, qwen3-32b
Closes#478
* fix(ui): only apply [1m] suffix to ANTHROPIC_MODEL, fix toggle refresh
- Only ANTHROPIC_MODEL gets [1m] suffix, not tier mappings
- Strip [1m] when looking up model in catalog to prevent toggle disappearing
- Fix odd page refresh when toggling extended context
* chore(release): 7.37.1-dev.4 [skip ci]
* fix(ui): remove duplicate import in use-provider-editor
* chore: address PR review feedback - sync comment and unused import
* chore(release): 7.37.1-dev.5 [skip ci]
* feat(cliproxy): add Opus 4.6 to Antigravity model catalog (#482)
* feat(cliproxy): add Opus 4.6 to Antigravity model catalog
- Add gemini-claude-opus-4-6-thinking as new default agy model
- Update preset mappings to route opus tier to Opus 4.6
- Bump CLIProxy fallback versions to v6.8.2
- Keep Opus 4.5 as previous flagship option
* fix(cliproxy): include oauth-model-alias in config generation
Root cause: CLIProxy config.yaml was missing Opus 4.6 alias because:
1. CLIProxyPlus startup migration is disabled (intentional)
2. CCS config generator never wrote oauth-model-alias section
3. Existing users with outdated aliases got 502 on Opus 4.6
Fix:
- Add DEFAULT_ANTIGRAVITY_ALIASES to config generator
- Generate oauth-model-alias section in config.yaml template
- Preserve claude-api-key and custom aliases during regeneration
- Bump config version to v6 to trigger auto-regeneration
- Update model catalog tests for new model count
* fix(cliproxy): preserve YAML indentation in extractYamlSection
- Replace .trim() with regex to strip only leading/trailing newlines,
preserving 2-space indent on claude-api-key children
- Skip standalone comments at col 0 in section boundary detection
- Update stale test description (4 → 5 models)
* chore(release): 7.37.1-dev.6 [skip ci]
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Skip saving registry when no new accounts discovered (prevents empty
accounts.json creation for invalid files)
- Skip merging empty provider sections to prevent empty provider entries
- Update test module cache clearing to include new modular submodules
(accounts/registry, accounts/index) for proper test isolation
- Test proxy lifecycle (start/stop)
- Test request sanitization (duplicates, truncation, passthrough)
- Test response restoration (buffered and SSE streaming)
- Test error handling (invalid JSON, upstream errors)
- Test multiple tools with mapping tracking
- Mock upstream server for isolated testing
- change AccountTier from binary (free/paid) to granular (ultra/pro/free/unknown)
- update mapTierString() to parse API tier strings correctly
- remove faulty inferTierFromModels() fallback - tier comes only from API
- update default tier_priority config to ['ultra', 'pro', 'free']
- update model catalog tier types and display logic
Closes#387
- Add 31 unit tests for fetchLocalSyncStatus, fetchProfiles, and performLocalSync
- Cover success, error, timeout, and network failure scenarios
- Remove unused --force flag from sync handler (YAGNI)
- Fix "Remote" → "Local" in sync-dialog.tsx and CLI help text
- Add resetWatcherState() export for test cleanup
- Add unit tests for profile-mapper, local-config-sync, auto-sync-watcher
- Remove unused ModelAlias and AliasesResponse types from hooks
- Validate authToken with trim() to reject whitespace-only values
- Show broken model warning for both remote and local modes
- Add context-aware messaging for remote proxy users
- Add comprehensive test coverage for authToken edge cases:
- Whitespace-only strings
- Null values
- Tabs/newlines
- Tokens with leading/trailing whitespace
Co-authored-by: Shun Kakinoki <shunkakinoki@gmail.com>
When --proxy-host and --proxy-auth-token are provided, the remote proxy
handles authentication via its own OAuth sessions. This change skips:
- Local OAuth check/trigger
- Preflight quota check (managed by remote server)
- Model configuration prompts (configured on remote server)
- Broken model warnings (model selection is remote)
This enables headless CI/CD usage without requiring pre-cached
CCS_SESSIONS when a remote proxy with auth token is available.
- Add centralized mock infrastructure in tests/mocks/:
- mock-fetch.ts: Bun native fetch interception
- mock-http-server.ts: Fake server responses (no ports)
- fixtures/responses.ts: Preset response constants
- types.ts: Mock type definitions
- Refactor https-tunnel-proxy.test.ts:
- Remove TEST_CERT constant (~50 lines)
- Remove real HTTPS server creation
- Use invalid hosts for error path testing
- 26/26 tests pass in ~430ms
- Refactor remote-token-uploader.test.ts:
- Replace 7 real HTTP servers with mockFetch
- Reduce from 422 to 289 lines
- 18/18 tests pass in ~120ms
- Add global test timeout (10s) in bunfig.toml
Test suite: 10+ min → 14 seconds
- Fix config-generator test assertion for parseUserApiKeys
- Update model-catalog tests to expect codex provider
- Remove outdated codex unsupported test from model-config
Add cliproxy.backend config field to choose between CLIProxyAPI (original)
and CLIProxyAPIPlus backends. Includes CLI --backend flag, Dashboard toggle,
and provider validation to block Kiro/ghcp on original backend.
The previous test relied on client socket receiving close event from
server-side destroy, which is timing-dependent and flaky in CI.
New approach:
- Verify stop() behavior directly via getPort() returning null
- Test what we control (server state) not what we observe (client state)
- Remove timing-dependent assertions
- Faster and more reliable (83ms vs 1000ms+ timeout)
- Fix connection tracking test: wait for 'close' event instead of only
checking socket.destroyed (more reliable in CI environments)
- Increase timeout from 500ms to 1000ms for CI latency
- Add proper socket cleanup after test
- Merge dev changes: add projectId parameter to registerAccount()
- Fix timeout test to use HTTPS server (tunnel uses https.request)
- Fix timeout event handling - call reject directly instead of relying on destroy error event
- Fix misleading comment in remote-token-uploader (Authorization header, not X-Management-Key)
- Remove unused imports in test files
Built [OnSteroids](https://onsteroids.ai)
Co-Authored-By: OnSteroids <built@onsteroids.ai>
* feat(cliproxy): add HTTPS tunnel for remote proxy mode
Claude Code doesn't support HTTPS in ANTHROPIC_BASE_URL directly (undici
limitation). This adds an HTTP→HTTPS tunnel proxy for remote CLIProxyAPI
connections.
Changes:
- Add HttpsTunnelProxy: local HTTP server tunneling to remote HTTPS
- Add CodexReasoningProxy HTTPS support and path prefix stripping
- Remote mode now uses root paths (/v1/messages) not provider-prefixed
- Add remote token uploader for syncing OAuth tokens to remote server
- Auto-upload tokens after OAuth auth when remote mode is enabled
Flow for remote HTTPS:
Claude CLI → CodexReasoningProxy → HttpsTunnel → Remote HTTPS
Built with [OnSteroids](https://onsteroids.ai)
Co-Authored-By: OnSteroids <built@onsteroids.ai>
* fix: address PR review issues for HTTPS tunnel proxy
- Add connection tracking with activeConnections Set for proper cleanup
- Add port validation after start() to reject port 0
- Add Authorization header fallback injection in buildForwardHeaders()
- Handle client disconnect (premature close) and request errors
- Improve error handling in uploadTokenToRemoteAsync (log instead of silent catch)
- Add comprehensive tests for HttpsTunnelProxy (97% coverage)
- Add integration tests for remote-token-uploader
- Add stripPathPrefix unit tests for remote mode
Built with [OnSteroids](https://onsteroids.ai)
Co-Authored-By: OnSteroids <built@onsteroids.ai>
* fix: bump Node.js engine requirement to >=18.0.0
FormData and Blob APIs used in remote-token-uploader require Node.js 18+.
Addresses coderabbit review comment about Node.js engine compatibility.
Built [OnSteroids](https://onsteroids.ai)
Co-Authored-By: OnSteroids <built@onsteroids.ai>
* fix: address remaining PR suggestions
- Add verbose parameter to registerAccountFromToken for proper propagation
- Improve stripPathPrefix with path normalization (double slashes, leading slash)
- Add edge case tests for path normalization
Built [OnSteroids](https://onsteroids.ai)
Co-Authored-By: OnSteroids <built@onsteroids.ai>
* fix: add timeout to flaky npm CLI tests
Tests 'handles empty arguments gracefully' and 'handles very long argument'
were missing timeout option in execSync, causing occasional timeouts when
bun test's 5000ms limit was reached before CLI completed.
Built [OnSteroids](https://onsteroids.ai)
Co-Authored-By: OnSteroids <built@onsteroids.ai>
* fix: address new PR review feedback
- Add path segment boundary check to prevent partial matches (/codex vs /codextra)
- Add hostname validation in HttpsTunnelProxy constructor
- Add race condition protection with 'starting' flag in start()
- Sanitize error messages (detailed only in verbose mode)
- Update comments for clarity (regex behavior)
- Add comprehensive tests for all edge cases
Built [OnSteroids](https://onsteroids.ai)
Co-Authored-By: OnSteroids <built@onsteroids.ai>
---------
Co-authored-by: OnSteroids <built@onsteroids.ai>
- test budget bounds and constants
- test off/auto/level values handling
- test budget-type and level-type models
- test edge cases and unknown models
Refs #307
- Fix path traversal in trace() via safe dir validation
- Add RFC 7230 hop-by-hop header filtering
- Fix isRecord() to exclude arrays
- Add req.destroy() on oversized body rejection
- Add missing ANTHROPIC_BASE_URL warning for Codex
- Use cwd() fallback when HOME undefined
- Add 4 edge case unit tests
Kiro/GHCP OAuth tokens have empty email field, causing all accounts to
use accountId='default' and overwrite each other. This fix:
- Add PROVIDERS_WITHOUT_EMAIL constant for kiro/ghcp identification
- Require nickname for kiro/ghcp during registration (CLI + web UI)
- Use nickname as accountId instead of email for these providers
- Enforce nickname uniqueness to prevent collisions
- Update discoverExistingAccounts() to generate unique IDs (kiro-1, etc.)
Closes#258, #267
- Test kiro/ghcp provider type mapping
- Test email extraction from filename fallback
- Test multiple accounts per provider
- Test edge cases (invalid JSON, missing type, unknown provider)
- Export validatePort() from config-generator with full edge case handling
- Add YAML port validation in proxy-config-resolver before use
- Add final port validation in cliproxy-executor after config merge
- Add default port parameter and validation to proxy-detector
- Add 38 new tests covering all port validation edge cases
Fixes undefined port bug when upgrading from 7.11.1 to 7.12.0
Update tests to use renamed function after refactoring getConfigPath
to getCliproxyConfigPath in cliproxy/config-generator.ts
Files updated:
- tests/unit/cliproxy/config-generator-port.test.js
- tests/unit/cliproxy/config-generator.test.js
Add comprehensive test suite mapping to PR #184 test plan:
- Create multiple variants with unique ports (8318-8417)
- Verify port persistence in config and separate config-{port}.yaml
- Run multiple variants concurrently with isolated sessions
- Verify list shows port column for each variant
- Remove variant cleans config, session, and port-specific files
25 new tests covering edge cases:
- Port exhaustion (100 variant max)
- Port reuse after deletion
- Legacy variants without port field
- Stale session cleanup for dead PIDs
- Default port special handling (sessions.json)