Consolidate 5 duplicate boolean tracking variables into a lifecycle
object with an update() method, reducing event detection from 4
locations to centralized calls. Add pause/drain/resume backpressure
to the passthrough path to prevent unbounded memory buffering with
slow clients.
Add message_start/message_delta/message_stop detection to the
remaining-buffer block in the SSE processing path for defensive
completeness. Addresses code review feedback on PR #493.
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
CLIProxyAPIPlus already handles background token refresh for kiro (every
1 min via background_refresh.go) and other providers. CCS was incorrectly
returning "not implemented" errors for these providers, causing noisy
worker logs and preventing proper token expiry tracking.
Changes:
- Classify providers into CCS-managed (gemini), CLIProxy-delegated
(codex, agy, kiro, ghcp, qwen, iflow), and not-implemented (claude)
- Delegated providers return success with delegated flag instead of error
- Skip refresh_token requirement for delegated providers in expiry checker
- Filter delegated providers from background worker refresh loop
Closes#487
* 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
- Add promptOAuthModeChoice() for headless OAuth mode selection
- Add --port-forward flag to force port-forwarding mode
- Add conflict detection for --paste-callback + --port-forward
- Handle edge cases: Ctrl+C, invalid input, non-TTY stdin
- Update help docs with new flag
Closes#461
CLIProxyAPI installation and updates now respect http_proxy, https_proxy,
and all_proxy environment variables when making network requests.
Uses https-proxy-agent and http-proxy-agent packages to route HTTP/HTTPS
requests through the configured proxy server.
Closes#266
Replace process.env.HOME with os.homedir() when expanding custom
settings paths. On Windows, process.env.HOME is undefined, causing
tilde expansion to produce invalid Unix-style paths like
'/.ccs/variant.settings.json'.
Closes#445
Changes ImageAnalysisConfig from providers array to provider_models
mapping for granular vision model control per CLIProxy provider.
Breaking change: config.yaml image_analysis section now uses
provider_models instead of providers/model fields.
Provider-to-model mappings:
- agy → gemini-2.5-flash
- gemini → gemini-2.5-flash
- codex → gpt-5.1-codex-mini
- kiro → kiro-claude-haiku-4-5
- ghcp → claude-haiku-4.5
- claude → claude-haiku-4-5-20251001
Hook checks CCS_CURRENT_PROVIDER against provider_models and skips
if no vision model configured for that provider.
Add PreToolUse hook that intercepts Read tool calls on image files
(.png, .jpg, .webp, etc.) and blocks them with helpful message.
This prevents context exhaustion when image generation skills
produce multiple files and the agent tries to read them (each
image can consume 100K+ tokens).
Configuration:
- Enable via config.yaml: hooks.block_image_read.enabled: true
- Or env var: CCS_BLOCK_IMAGE_READ=1
Hook integration:
- lib/hooks/block-image-read.cjs - the hook script
- src/utils/hooks/image-read-block-hook-env.ts - config loader
- Integrated into all spawn locations (ccs.ts, shell-executor,
cliproxy-executor)
Closes#426
Instead of showing "Quota access forbidden" generic error, now shows:
- Antigravity: "403 Forbidden - No Gemini Code Assist access"
- Codex: "403 Forbidden - No quota API access"
Keeps success=false with isForbidden flag so UI can show distinct
"403" badge (similar to Antigravity-Manager) rather than conflating
with 0% exhausted state.
403 ≠ 0% exhausted - they are semantically different:
- 403: Account lacks API access entirely
- 0%: Account has access but quota is used up (shows reset time)
- Add --settings flag to Claude CLI spawn for CLIProxy profiles
- Use getProviderSettingsPath() for hardcoded profiles (agy, gemini, codex, qwen)
- Use cfg.customSettingsPath for CLIProxy variants (user-defined providers)
- Block user --settings flag to prevent duplicate flags
- Enables WebSearch hooks to load for all CLIProxy profiles
Closes#412
- Add proactive refresh: refresh token 5min before expiry (not just after)
- Add retry on 401: if API returns 401, refresh token and retry once
- Extract fetchWithAuthData helper to reduce code duplication
- Update UI to show better error messages (no more "via CLI" text)
- Falls back gracefully if proactive refresh fails but token not yet expired
- Add in-memory quota cache with 2-minute TTL to reduce external API calls
- Add needsReauth flag to CodexQuotaResult and GeminiCliQuotaResult types
- Update quota routes to use caching (codex, gemini, and generic routes)
- Add "Reauth needed" indicator in account-item.tsx with CLI command hint
- Add "Reauth needed" indicator in account-card.tsx for flow visualization
- Cache successful results only (don't cache expired tokens needing reauth)
- add dedicated /quota/codex/:accountId and /quota/gemini/:accountId endpoints
- reorder routes to place specific before generic for correct Express matching
- fix Gemini auth file detection to support new naming format
- handle nested token structure in Gemini auth files
- Move require('os') to top-level import
- Add debug logging for log dir creation failures
- Add hash collision detection and warning in ToolNameMapper
- Clarify proxy chain order comments in cliproxy-executor
- Add hash collision risk note to sanitizer docs (6-char MD5 = ~16M combos)
- Fix VALID_CHARS_REGEX comment to include '/' character
- Use getCcsDir() instead of os.homedir() for log path (CLAUDE.md compliance)
- Add JSDoc for isRecord type guard helper
- Add file-based logging to ~/.ccs/logs/tool-sanitization-proxy.log
- Console output only when CCS_DEBUG=1 (prevents polluting Claude CLI output)
- Fallback to temp directory if logs dir creation fails
- Respects CCS_HOME for test isolation
Fixes token mismatch where Gemini auth succeeds but token immediately
expires. The issue was that isAuthenticated() checked CLIProxy auth dir
(~/.ccs/cliproxy/auth/) but ensureGeminiTokenValid() only read from
~/.gemini/oauth_creds.json.
Changes:
- Add readCliproxyGeminiCreds() to read from CLIProxy auth dir first
- Add mapCliproxyToGeminiCreds() to convert CLIProxyAPI format to internal
- Track token source path for correct write-back on refresh
- Add writeCliproxyGeminiCreds() to preserve email/project_id fields
- Fall back to ~/.gemini/oauth_creds.json for backward compatibility
Closes#368
- Fix regex escape in auth-utils.ts: `\\.` not `\\\\.` (dot matcher)
- Refactor quota-fetcher.ts to import from auth-utils.ts (DRY)
- Remove unused UnifiedQuotaResult type (YAGNI)
- Add maintenance comment to Gemini model groups
Addresses code review feedback on PR #395
Add quota fetching for Codex (ChatGPT) and Gemini CLI providers with
unified CLI display. Extends `ccs cliproxy quota` with --provider flag.
New files:
- quota-types.ts: Shared type definitions for multi-provider quota
- quota-fetcher-codex.ts: Fetches from ChatGPT backend API
- quota-fetcher-gemini-cli.ts: Fetches from Google Cloud Code API
Features:
- Parallel quota fetching from all providers
- --provider flag to filter (agy|codex|gemini|all)
- Rate limit windows for Codex (primary/secondary/code review)
- Bucket grouping for Gemini CLI (Flash/Pro series)
- Reset time display with proper formatting
Edge case handling:
- 429 rate limit specific error messages
- Clamp percentages/fractions to valid ranges
- Null/negative reset time guards
- Empty --provider= value validation
- Updated claude-opus-4-5-20250220 → claude-opus-4-5-20251101
- Updated claude-sonnet-4-5-20250514 → claude-sonnet-4-5-20250929
- Updated claude-haiku-4-5-20250514 → claude-haiku-4-5-20251001
- Updated default model to claude-sonnet-4-5-20250929
- Updated all preset mappings to use new model IDs
Closes#392
- paidTier.id contains actual subscription: g1-ultra-tier or g1-pro-tier
- allowedTiers[isDefault] returns standard-tier for all accounts (not useful)
- mapTierString now correctly parses g1-ultra-tier and g1-pro-tier formats
- 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 timeout warning in restartAutoSyncWatcher when sync exceeds 10s
- Improve YAML key detection with regex pattern for safer parsing
- Replace --force help with --verbose (no confirmation prompt exists)
- Add force flag parsing for future use
- Fix auto_sync JSDoc to say (default: true) matching actual default
- Add unlink event handler for profile deletion sync
- Add debug logging for sync failures in api-command