mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 14:19:56 +00:00
Merge pull request #59 from kaitranntt/dev
feat(release): v5.7.0 - claude thinking support via antigravity
This commit is contained in:
@@ -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
|
||||
---
|
||||
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(' ')}`);
|
||||
|
||||
Reference in New Issue
Block a user