mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-03 05:19:21 +00:00
feat(thinking): complete thinking UX/DX for all providers
- Fix codex off-mode bug: add disableEffort to CodexReasoningProxy so mode=off actually skips reasoning injection - Add CCS_THINKING env var (priority: flag > env > config) - Add startup feedback: [i] Thinking: <level> (<source>) - Add ccs config thinking CLI subcommand with --mode, --override, --tier, --provider-override, --clear-override - Dashboard: add manual override selector when mode=manual - Dashboard: add provider overrides collapsible section - Dashboard: update info box with CCS_THINKING env var docs - Update help-command.ts with CCS_THINKING and config thinking Closes #583
This commit is contained in:
1 parent
7efbac6c1a
commit
c48ed2ea7f
7 files changed
+515
-12
No files matched your search
@@ -25,6 +25,8 @@ export interface CodexReasoningProxyConfig {
|
||||
* Example: '/api/provider/codex' will transform '/api/provider/codex/v1/messages' to '/v1/messages'
|
||||
*/
|
||||
stripPathPrefix?: string;
|
||||
/** When true, skip reasoning effort injection entirely (thinking mode: off) */
|
||||
disableEffort?: boolean;
|
||||
}
|
||||
|
||||
const EXTENDED_CONTEXT_SUFFIX_REGEX = /\[1m\]$/i;
|
||||
@@ -147,7 +149,12 @@ export class CodexReasoningProxy {
|
||||
private readonly config: Required<
|
||||
Pick<
|
||||
CodexReasoningProxyConfig,
|
||||
'upstreamBaseUrl' | 'verbose' | 'timeoutMs' | 'defaultEffort' | 'traceFilePath'
|
||||
| 'upstreamBaseUrl'
|
||||
| 'verbose'
|
||||
| 'timeoutMs'
|
||||
| 'defaultEffort'
|
||||
| 'traceFilePath'
|
||||
| 'disableEffort'
|
||||
>
|
||||
> &
|
||||
Pick<CodexReasoningProxyConfig, 'modelMap' | 'stripPathPrefix'>;
|
||||
@@ -170,6 +177,7 @@ export class CodexReasoningProxy {
|
||||
defaultEffort: config.defaultEffort ?? 'medium',
|
||||
traceFilePath: config.traceFilePath ?? '',
|
||||
stripPathPrefix: config.stripPathPrefix,
|
||||
disableEffort: config.disableEffort ?? false,
|
||||
};
|
||||
this.modelEffort = buildCodexModelEffortMap(this.config.modelMap, this.config.defaultEffort);
|
||||
}
|
||||
@@ -330,6 +338,20 @@ export class CodexReasoningProxy {
|
||||
? stripExtendedContextSuffix(originalModel)
|
||||
: null;
|
||||
|
||||
// When effort is disabled (thinking mode: off), strip model suffix but don't inject reasoning
|
||||
if (this.config.disableEffort) {
|
||||
const suffixParsed = normalizedRequestModel
|
||||
? parseModelEffortSuffix(normalizedRequestModel)
|
||||
: null;
|
||||
const upstreamModel = suffixParsed?.upstreamModel ?? normalizedRequestModel;
|
||||
const forwarded =
|
||||
upstreamModel && isRecord(parsed) ? { ...parsed, model: upstreamModel } : parsed;
|
||||
|
||||
this.log(`[disabled] model=${originalModel ?? 'null'} -> passthrough (no reasoning)`);
|
||||
await this.forwardJson(req, res, fullUpstreamUrl, forwarded);
|
||||
return;
|
||||
}
|
||||
|
||||
// Support "model aliases" like `gpt-5.2-codex-xhigh` by translating to:
|
||||
// - upstream model: `gpt-5.2-codex`
|
||||
// - reasoning.effort: `xhigh`
|
||||
|
||||
@@ -49,7 +49,7 @@ import {
|
||||
installWebSearchHook,
|
||||
displayWebSearchStatus,
|
||||
} from '../../utils/websearch-manager';
|
||||
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
|
||||
import { loadOrCreateUnifiedConfig, getThinkingConfig } from '../../config/unified-config-loader';
|
||||
import { installImageAnalyzerHook } from '../../utils/hooks';
|
||||
import { HttpsTunnelProxy } from '../https-tunnel-proxy';
|
||||
import { isKiroAuthMethod, KiroAuthMethod, normalizeKiroAuthMethod } from '../auth/auth-types';
|
||||
@@ -354,7 +354,20 @@ export async function execClaudeWithCLIProxy(
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const thinkingOverride = thinkingParse.value;
|
||||
// Priority: CLI flag > CCS_THINKING env var > config.yaml
|
||||
let thinkingOverride = thinkingParse.value;
|
||||
let thinkingSource: 'flag' | 'env' | 'config' | undefined =
|
||||
thinkingOverride !== undefined ? 'flag' : undefined;
|
||||
|
||||
if (thinkingOverride === undefined && process.env.CCS_THINKING) {
|
||||
const envVal = process.env.CCS_THINKING.trim();
|
||||
if (envVal) {
|
||||
// Parse same as CLI: integer string → number, else string
|
||||
thinkingOverride = /^-?\d+$/.test(envVal) ? Number.parseInt(envVal, 10) : envVal;
|
||||
thinkingSource = 'env';
|
||||
}
|
||||
}
|
||||
|
||||
if (thinkingParse.duplicateDisplays.length > 0) {
|
||||
console.warn(
|
||||
`[!] Multiple reasoning flags detected. Using first occurrence: ${thinkingParse.sourceDisplay}`
|
||||
@@ -804,10 +817,18 @@ export async function execClaudeWithCLIProxy(
|
||||
process.env.CCS_CODEX_REASONING_TRACE === '1' ||
|
||||
process.env.CCS_CODEX_REASONING_TRACE === 'true';
|
||||
const stripPathPrefix = useRemoteProxy ? '/api/provider/codex' : undefined;
|
||||
const thinkingCfg = getThinkingConfig();
|
||||
const codexThinkingOff =
|
||||
(thinkingCfg.mode === 'off' && thinkingOverride === undefined) ||
|
||||
thinkingOverride === 'off' ||
|
||||
(thinkingOverride === undefined &&
|
||||
thinkingCfg.mode === 'manual' &&
|
||||
thinkingCfg.override === 'off');
|
||||
codexReasoningProxy = new CodexReasoningProxy({
|
||||
upstreamBaseUrl: postSanitizationBaseUrl,
|
||||
verbose,
|
||||
defaultEffort: 'medium',
|
||||
disableEffort: codexThinkingOff,
|
||||
traceFilePath: traceEnabled ? path.join(getCcsDir(), 'codex-reasoning-proxy.log') : '',
|
||||
modelMap: {
|
||||
defaultModel: initialEnvVars.ANTHROPIC_MODEL,
|
||||
@@ -875,6 +896,33 @@ export async function execClaudeWithCLIProxy(
|
||||
const webSearchEnv = getWebSearchHookEnv();
|
||||
logEnvironment(env, webSearchEnv, verbose);
|
||||
|
||||
// 11b. Print thinking status feedback (TTY only, non-piped sessions)
|
||||
if (process.stderr.isTTY) {
|
||||
const thinkingCfgStatus = getThinkingConfig();
|
||||
let thinkingLabel: string;
|
||||
let sourceLabel: string;
|
||||
|
||||
if (thinkingOverride === 'off' || thinkingCfgStatus.mode === 'off') {
|
||||
thinkingLabel = 'off';
|
||||
sourceLabel =
|
||||
thinkingSource === 'flag' ? 'flag' : thinkingSource === 'env' ? 'env' : 'config';
|
||||
} else if (thinkingSource === 'flag') {
|
||||
thinkingLabel = String(thinkingOverride);
|
||||
sourceLabel = `flag: ${thinkingParse.sourceDisplay}`;
|
||||
} else if (thinkingSource === 'env') {
|
||||
thinkingLabel = String(thinkingOverride);
|
||||
sourceLabel = 'env: CCS_THINKING';
|
||||
} else if (thinkingCfgStatus.mode === 'manual' && thinkingCfgStatus.override !== undefined) {
|
||||
thinkingLabel = String(thinkingCfgStatus.override);
|
||||
sourceLabel = 'config: manual';
|
||||
} else {
|
||||
thinkingLabel = thinkingCfgStatus.mode === 'auto' ? 'auto' : 'default';
|
||||
sourceLabel = 'config: auto';
|
||||
}
|
||||
|
||||
console.error(`[i] Thinking: ${thinkingLabel} (${sourceLabel})`);
|
||||
}
|
||||
|
||||
// 12. Filter CCS-specific flags before passing to Claude CLI
|
||||
const ccsFlags = [
|
||||
'--auth',
|
||||
|
||||
Reference in new issue
Block a user