Merge pull request #59 from kaitranntt/dev

feat(release): v5.7.0 - claude thinking support via antigravity
This commit is contained in:
Kai (Tam Nhu) Tran
2025-12-05 17:44:46 -05:00
committed by GitHub
10 changed files with 185 additions and 37 deletions
+7 -1
View File
@@ -1,6 +1,12 @@
---
name: ccs-delegation
description: Auto-activate CCS CLI delegation for deterministic tasks. Parses user input, auto-selects optimal profile (glm/kimi/custom) from ~/.ccs/config.json, enhances prompts with context, executes via `ccs {profile} -p "task"` or `ccs {profile}:continue`, and reports results. Trigger: "use ccs [task]" patterns, typo/test/refactor keywords. Excludes: complex architecture, security-critical code, performance optimization, breaking changes.
description: >-
Auto-activate CCS CLI delegation for deterministic tasks. Parses user input,
auto-selects optimal profile (glm/kimi/custom) from ~/.ccs/config.json,
enhances prompts with context, executes via `ccs {profile} -p "task"` or
`ccs {profile}:continue`, and reports results. Triggers on "use ccs [task]"
patterns, typo/test/refactor keywords. Excludes complex architecture,
security-critical code, performance optimization, breaking changes.
version: 3.0.0
---
+32
View File
@@ -0,0 +1,32 @@
name: CI
on:
pull_request:
branches: [main, dev]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build package
run: bun run build
- name: Validate (typecheck + lint + tests)
run: bun run validate
+1 -1
View File
@@ -1 +1 @@
5.7.0
5.7.0-dev.8
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@kaitranntt/ccs",
"version": "5.7.0",
"version": "5.7.0-dev.8",
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
"keywords": [
"cli",
+25 -6
View File
@@ -342,6 +342,8 @@ export function getAllAuthStatus(): AuthStatus[] {
/**
* Clear authentication for provider
* Only removes files belonging to the specified provider (by prefix or content)
* Does NOT remove the shared auth directory or other providers' files
*/
export function clearAuth(provider: CLIProxyProvider): boolean {
const tokenDir = getProviderTokenDir(provider);
@@ -350,16 +352,33 @@ export function clearAuth(provider: CLIProxyProvider): boolean {
return false;
}
// Remove all files in token directory
const validPrefixes = PROVIDER_AUTH_PREFIXES[provider] || [];
const files = fs.readdirSync(tokenDir);
let removedCount = 0;
// Only remove files that belong to this provider
for (const file of files) {
fs.unlinkSync(path.join(tokenDir, file));
const filePath = path.join(tokenDir, file);
const lowerFile = file.toLowerCase();
// Check by prefix first (fast path)
const matchesByPrefix = validPrefixes.some((prefix) => lowerFile.startsWith(prefix));
// If no prefix match, check by content (for Gemini tokens without prefix)
const matchesByContent = !matchesByPrefix && isTokenFileForProvider(filePath, provider);
if (matchesByPrefix || matchesByContent) {
try {
fs.unlinkSync(filePath);
removedCount++;
} catch {
// Failed to remove - skip
}
}
}
// Remove directory
fs.rmdirSync(tokenDir);
return true;
// DO NOT remove the shared auth directory - other providers may still have tokens
return removedCount > 0;
}
/**
+7 -12
View File
@@ -383,6 +383,7 @@ export class BinaryManager {
// Download archive
const archivePath = path.join(this.config.binPath, `cliproxy-archive.${platform.extension}`);
// Use single spinner and update text as we progress (avoids UI jumping)
const spinner = new ProgressIndicator(`Downloading CLIProxyAPI v${this.config.version}`);
spinner.start();
@@ -394,11 +395,8 @@ export class BinaryManager {
throw new Error(result.error || 'Download failed after retries');
}
spinner.succeed('Download complete');
// Verify checksum
const verifySpinner = new ProgressIndicator('Verifying checksum');
verifySpinner.start();
// Verify checksum (update spinner text instead of creating new one)
spinner.update('Verifying checksum');
const checksumResult = await this.verifyChecksum(
archivePath,
@@ -407,7 +405,7 @@ export class BinaryManager {
);
if (!checksumResult.valid) {
verifySpinner.fail('Checksum mismatch');
spinner.fail('Checksum mismatch');
fs.unlinkSync(archivePath);
throw new Error(
`Checksum mismatch for ${platform.binaryName}\n` +
@@ -417,15 +415,12 @@ export class BinaryManager {
);
}
verifySpinner.succeed('Checksum verified');
// Extract archive
const extractSpinner = new ProgressIndicator('Extracting binary');
extractSpinner.start();
// Extract archive (update spinner text)
spinner.update('Extracting binary');
await this.extractArchive(archivePath, platform.extension);
extractSpinner.succeed('Extraction complete');
spinner.succeed('CLIProxyAPI ready');
// Cleanup archive
fs.unlinkSync(archivePath);
-6
View File
@@ -50,22 +50,16 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
id: 'gemini-claude-opus-4-5-thinking',
name: 'Claude Opus 4.5 Thinking',
description: 'Most capable, extended thinking',
broken: true,
issueUrl: 'https://github.com/router-for-me/CLIProxyAPI/issues/415',
},
{
id: 'gemini-claude-sonnet-4-5-thinking',
name: 'Claude Sonnet 4.5 Thinking',
description: 'Balanced with extended thinking',
broken: true,
issueUrl: 'https://github.com/router-for-me/CLIProxyAPI/issues/415',
},
{
id: 'gemini-claude-sonnet-4-5',
name: 'Claude Sonnet 4.5',
description: 'Fast and capable',
broken: true,
issueUrl: 'https://github.com/router-for-me/CLIProxyAPI/issues/415',
},
{
id: 'gemini-3-pro-preview',
+71 -10
View File
@@ -13,6 +13,20 @@ import { getProviderSettingsPath, getClaudeEnvVars } from './config-generator';
import { CLIProxyProvider } from './types';
import { initUI, color, bold, dim, ok, info, header } from '../utils/ui';
/**
* Check if model is a Claude model routed via Antigravity
* Claude models require MAX_THINKING_TOKENS < 8192 for thinking to work
*/
function isClaudeModel(modelId: string): boolean {
return modelId.includes('claude');
}
/**
* Max thinking tokens for Claude models via Antigravity
* Must be < 8192 due to Google protocol conversion limitations
*/
const CLAUDE_MAX_THINKING_TOKENS = '8191';
/** CCS directory */
const CCS_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '', '.ccs');
@@ -117,18 +131,56 @@ export async function configureProviderModel(
defaultIndex: safeDefaultIdx,
});
// Get base env vars to preserve haiku model and base URL
// Get base env vars for defaults
const baseEnv = getClaudeEnvVars(provider);
// Build settings with selected model
const settings = {
env: {
...baseEnv,
ANTHROPIC_MODEL: selectedModel,
ANTHROPIC_DEFAULT_OPUS_MODEL: selectedModel,
ANTHROPIC_DEFAULT_SONNET_MODEL: selectedModel,
// Keep haiku as-is from base config (usually flash model)
},
// Read existing settings to preserve user customizations
let existingSettings: Record<string, unknown> = {};
let existingEnv: Record<string, string> = {};
if (fs.existsSync(settingsPath)) {
try {
existingSettings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
existingEnv = (existingSettings.env as Record<string, string>) || {};
} catch {
// Invalid JSON - start fresh
}
}
// Build settings with selective merge:
// - Preserve ALL user settings (top-level and env vars)
// - Only update CCS-controlled fields (model selection + thinking toggle for Claude)
const isClaude = isClaudeModel(selectedModel);
// CCS-controlled env vars (always override with our values)
const ccsControlledEnv: Record<string, string> = {
ANTHROPIC_BASE_URL: baseEnv.ANTHROPIC_BASE_URL || '',
ANTHROPIC_AUTH_TOKEN: baseEnv.ANTHROPIC_AUTH_TOKEN || '',
ANTHROPIC_MODEL: selectedModel,
ANTHROPIC_DEFAULT_OPUS_MODEL: selectedModel,
ANTHROPIC_DEFAULT_SONNET_MODEL: selectedModel,
ANTHROPIC_DEFAULT_HAIKU_MODEL: baseEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL || '',
};
// Claude models require MAX_THINKING_TOKENS < 8192 for thinking to work
if (isClaude) {
ccsControlledEnv.MAX_THINKING_TOKENS = CLAUDE_MAX_THINKING_TOKENS;
}
// Merge: user env vars (preserved) + CCS controlled (override)
const mergedEnv = {
...existingEnv,
...ccsControlledEnv,
};
// Remove MAX_THINKING_TOKENS when switching away from Claude model
if (!isClaude && mergedEnv.MAX_THINKING_TOKENS) {
delete mergedEnv.MAX_THINKING_TOKENS;
}
// Build final settings: preserve user top-level settings + update env
const settings: Record<string, unknown> = {
...existingSettings,
env: mergedEnv,
};
// Ensure CCS directory exists
@@ -146,6 +198,15 @@ export async function configureProviderModel(
console.error('');
console.error(ok(`Model set to: ${bold(displayName)}`));
console.error(dim(` Config saved: ${settingsPath}`));
// Show info for Claude models about thinking token limit
if (isClaude) {
console.error('');
console.error(
info(`MAX_THINKING_TOKENS set to ${CLAUDE_MAX_THINKING_TOKENS} (required < 8192)`)
);
console.error(dim(' Google protocol conversion requires this limit for thinking to work.'));
}
console.error('');
return true;
+34
View File
@@ -16,6 +16,7 @@ interface ParsedArgs {
timeout?: number;
resumeSession?: boolean;
sessionId?: string;
extraArgs?: string[]; // Passthrough args for Claude CLI
};
}
@@ -185,6 +186,39 @@ export class DelegationHandler {
options.timeout = parseInt(args[timeoutIndex + 1], 10);
}
// Collect extra args to pass through to Claude CLI
// CCS-handled flags with values (skip these and their values):
const ccsFlagsWithValue = new Set(['-p', '--prompt', '--timeout', '--permission-mode']);
const extraArgs: string[] = [];
const profile = this._extractProfile(args);
for (let i = 0; i < args.length; i++) {
const arg = args[i];
// Skip profile name (non-flag first arg)
if (arg === profile && !arg.startsWith('-')) continue;
// Skip CCS-handled flags and their values
if (ccsFlagsWithValue.has(arg)) {
i++; // Skip next arg (the value)
continue;
}
// Collect flags and their values as passthrough
if (arg.startsWith('-')) {
extraArgs.push(arg);
// If next arg exists and doesn't start with '-', it's likely a value
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
extraArgs.push(args[i + 1]);
i++; // Skip the value we just added
}
}
}
if (extraArgs.length > 0) {
options.extraArgs = extraArgs;
}
return options;
}
+7
View File
@@ -45,6 +45,7 @@ interface ExecutionOptions {
resumeSession?: boolean;
sessionId?: string;
maxRetries?: number;
extraArgs?: string[]; // Passthrough args for Claude CLI
}
interface ExecutionResult {
@@ -112,6 +113,7 @@ export class HeadlessExecutor {
permissionMode = 'acceptEdits',
resumeSession = false,
sessionId = null,
extraArgs = [],
} = options;
// Validate permission mode
@@ -210,6 +212,11 @@ export class HeadlessExecutor {
// Note: No max-turns limit - using time-based limits instead (default 10min timeout)
// Passthrough extra args (from Claude CLI flags like --agent, --system-prompt-file, etc.)
if (extraArgs.length > 0) {
args.push(...extraArgs);
}
// Debug log args
if (process.env.CCS_DEBUG) {
console.error(`[i] Claude CLI args: ${args.join(' ')}`);