feat(ai): upgrade to Opus 4.7 with adaptive thinking (#325)

This commit is contained in:
ezl-keygraph authored and GitHub committed 2026-04-28 21:52:13 +05:30
1 parent 03a3d764af
commit 6c8135d031
13 files changed
+909 -203

No files matched your search

+24 -7
View File
@@ -32,7 +32,10 @@ export async function setup(): Promise<void> {
const config = await setupProvider(provider as Provider);
// 2. Save config
// 2. Adaptive thinking
await maybePromptAdaptiveThinking(config);
// 3. Save config
saveConfig(config);
const configPath = path.join(SHANNON_HOME, 'config.toml');
@@ -80,7 +83,7 @@ async function setupAnthropic(): Promise<ShannonConfig> {
'Do you want to change the default models?\n' +
' Small - claude-haiku-4-5-20251001\n' +
' Medium - claude-sonnet-4-6\n' +
' Large - claude-opus-4-6',
' Large - claude-opus-4-7',
initialValue: false,
});
if (p.isCancel(customizeModels)) return cancelAndExit();
@@ -102,7 +105,7 @@ async function setupAnthropic(): Promise<ShannonConfig> {
const large = await p.text({
message: 'Large model ID',
initialValue: 'claude-opus-4-6',
initialValue: 'claude-opus-4-7',
validate: required('Large model ID is required'),
});
if (p.isCancel(large)) return cancelAndExit();
@@ -140,7 +143,7 @@ async function setupCustomBaseUrl(): Promise<ShannonConfig> {
'Do you want to change the default models?\n' +
' Small - claude-haiku-4-5-20251001\n' +
' Medium - claude-sonnet-4-6\n' +
' Large - claude-opus-4-6',
' Large - claude-opus-4-7',
initialValue: false,
});
if (p.isCancel(customizeModels)) return cancelAndExit();
@@ -162,7 +165,7 @@ async function setupCustomBaseUrl(): Promise<ShannonConfig> {
const large = await p.text({
message: 'Large model ID',
initialValue: 'claude-opus-4-6',
initialValue: 'claude-opus-4-7',
validate: required('Large model ID is required'),
});
if (p.isCancel(large)) return cancelAndExit();
@@ -199,7 +202,7 @@ async function setupBedrock(): Promise<ShannonConfig> {
const large = await p.text({
message: 'Large model ID',
placeholder: 'us.anthropic.claude-opus-4-6',
placeholder: 'us.anthropic.claude-opus-4-7',
validate: required('Large model ID is required'),
});
if (p.isCancel(large)) return cancelAndExit();
@@ -262,7 +265,7 @@ async function setupVertex(): Promise<ShannonConfig> {
large: () =>
p.text({
message: 'Large model ID',
placeholder: 'claude-opus-4-6',
placeholder: 'claude-opus-4-7',
validate: required('Large model ID is required'),
}),
});
@@ -281,6 +284,20 @@ async function setupVertex(): Promise<ShannonConfig> {
// === Helpers ===
async function maybePromptAdaptiveThinking(config: ShannonConfig): Promise<void> {
const m = config.models;
const hasOpus47 = !m || [m.small, m.medium, m.large].some((v) => v && /opus-4-[67]/.test(v));
if (!hasOpus47) return;
const enable = await p.confirm({
message: 'Enable adaptive thinking on Opus 4.6/4.7? Claude decides when and how deeply to reason.',
initialValue: true,
});
if (p.isCancel(enable)) return cancelAndExit();
config.core = { ...config.core, adaptive_thinking: enable };
}
async function promptSecret(message: string): Promise<string> {
const value = await p.password({
message,
+10 -6
View File
@@ -18,12 +18,14 @@ interface ConfigMapping {
readonly env: string;
readonly toml: string;
readonly type: TOMLType;
readonly boolFormat?: 'numeric' | 'literal';
}
/** Maps every supported env var to its TOML path (section.key) and expected type. */
const CONFIG_MAP: readonly ConfigMapping[] = [
// Core
{ env: 'CLAUDE_CODE_MAX_OUTPUT_TOKENS', toml: 'core.max_tokens', type: 'number' },
{ env: 'CLAUDE_ADAPTIVE_THINKING', toml: 'core.adaptive_thinking', type: 'boolean', boolFormat: 'literal' },
// Anthropic
{ env: 'ANTHROPIC_API_KEY', toml: 'anthropic.api_key', type: 'string' },
@@ -56,9 +58,9 @@ type TOMLValue = string | number | boolean;
type TOMLSection = Record<string, TOMLValue>;
type TOMLConfig = Record<string, TOMLSection>;
/** Read a nested TOML value by dotted path (e.g. "anthropic.api_key"). */
function getTomlValue(config: TOMLConfig, path: string): string | undefined {
const [section, key] = path.split('.');
/** Read a nested TOML value for a given mapping. */
function getTomlValue(config: TOMLConfig, mapping: ConfigMapping): string | undefined {
const [section, key] = mapping.toml.split('.');
if (!section || !key) return undefined;
const sectionObj = config[section];
@@ -67,8 +69,10 @@ function getTomlValue(config: TOMLConfig, path: string): string | undefined {
const value = sectionObj[key];
if (value === undefined || value === null) return undefined;
// NOTE: env.ts checks bedrock/vertex via `=== '1'`, so booleans must map to "1"/"0"
if (typeof value === 'boolean') return value ? '1' : '0';
if (typeof value === 'boolean') {
if (mapping.boolFormat === 'literal') return value ? 'true' : 'false';
return value ? '1' : '0';
}
return String(value);
}
@@ -273,7 +277,7 @@ export function resolveConfig(): void {
for (const mapping of CONFIG_MAP) {
if (process.env[mapping.env]) continue;
const value = getTomlValue(toml, mapping.toml);
const value = getTomlValue(toml, mapping);
if (value) {
process.env[mapping.env] = value;
}
+1 -1
View File
@@ -8,7 +8,7 @@ import { getConfigFile } from '../home.js';
// === Types ===
export interface ShannonConfig {
core?: { max_tokens?: number };
core?: { max_tokens?: number; adaptive_thinking?: boolean };
anthropic?: { api_key?: string; oauth_token?: string };
custom_base_url?: { base_url?: string; auth_token?: string };
bedrock?: { use?: boolean; region?: string; token?: string };
+1
View File
@@ -26,6 +26,7 @@ const FORWARD_VARS = [
'ANTHROPIC_MEDIUM_MODEL',
'ANTHROPIC_LARGE_MODEL',
'CLAUDE_CODE_MAX_OUTPUT_TOKENS',
'CLAUDE_ADAPTIVE_THINKING',
] as const;
/**
+3 -1
View File
@@ -18,7 +18,7 @@ import { formatTimestamp } from '../utils/formatting.js';
import { Timer } from '../utils/metrics.js';
import { createAuditLogger } from './audit-logger.js';
import { dispatchMessage } from './message-handlers.js';
import { type ModelTier, resolveModel } from './models.js';
import { type ModelTier, resolveModel, supportsAdaptiveThinking } from './models.js';
import { detectExecutionContext, formatCompletionMessage, formatErrorOutput } from './output-formatters.js';
import { createProgressManager } from './progress-manager.js';
@@ -217,6 +217,7 @@ export async function runClaudePrompt(
// 4. Configure SDK options
// Model override from providerConfig takes precedence over env-based resolveModel
const model = providerConfig?.modelOverrides?.[modelTier] ?? resolveModel(modelTier);
const adaptiveThinking = supportsAdaptiveThinking(model) && process.env.CLAUDE_ADAPTIVE_THINKING !== 'false';
const options = {
model,
maxTurns: 10_000,
@@ -225,6 +226,7 @@ export async function runClaudePrompt(
allowDangerouslySkipPermissions: true,
settingSources: ['user'] as ('user' | 'project' | 'local')[],
env: sdkEnv,
...(adaptiveThinking && { thinking: { type: 'adaptive' as const } }),
...(outputFormat && { outputFormat }),
};
+4 -1
View File
@@ -39,7 +39,10 @@ function extractMessageContent(message: AssistantMessage): string {
const messageContent = message.message;
if (Array.isArray(messageContent.content)) {
return messageContent.content.map((c: ContentBlock) => c.text || JSON.stringify(c)).join('\n');
return messageContent.content
.filter((c: ContentBlock) => c.type !== 'thinking' && c.type !== 'redacted_thinking')
.map((c: ContentBlock) => c.text || JSON.stringify(c))
.join('\n');
}
return String(messageContent.content);
+6 -1
View File
@@ -21,7 +21,7 @@ export type ModelTier = 'small' | 'medium' | 'large';
const DEFAULT_MODELS: Readonly<Record<ModelTier, string>> = {
small: 'claude-haiku-4-5-20251001',
medium: 'claude-sonnet-4-6',
large: 'claude-opus-4-6',
large: 'claude-opus-4-7',
};
/** Resolve a model tier to a concrete model ID. */
@@ -35,3 +35,8 @@ export function resolveModel(tier: ModelTier = 'medium'): string {
return process.env.ANTHROPIC_MEDIUM_MODEL || DEFAULT_MODELS.medium;
}
}
/** Whether a model supports adaptive thinking. Opus 4.6 and 4.7 only. */
export function supportsAdaptiveThinking(model: string): boolean {
return /opus-4-[67]/.test(model);
}
+2
View File
@@ -52,6 +52,8 @@ export interface ToolResultData {
export interface ContentBlock {
type?: string;
text?: string;
thinking?: string;
data?: string;
}
export interface AssistantMessage {