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

This commit is contained in:
ezl-keygraph
2026-04-28 21:52:13 +05:30
committed by GitHub
parent 03a3d764af
commit 6c8135d031
13 changed files with 909 additions and 203 deletions
+6 -3
View File
@@ -4,6 +4,9 @@
# Recommended output token configuration for larger tool outputs
CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000
# Adaptive thinking is enabled automatically on Opus 4.6/4.7. Set to false to disable.
# CLAUDE_ADAPTIVE_THINKING=false
# =============================================================================
# OPTION 1: Direct Anthropic
# =============================================================================
@@ -26,7 +29,7 @@ ANTHROPIC_API_KEY=your-api-key-here
# Optional for direct Anthropic and custom base URL modes. Required for Bedrock/Vertex.
# ANTHROPIC_SMALL_MODEL=... # Small tier (default: claude-haiku-4-5-20251001)
# ANTHROPIC_MEDIUM_MODEL=... # Medium tier (default: claude-sonnet-4-6)
# ANTHROPIC_LARGE_MODEL=... # Large tier (default: claude-opus-4-6)
# ANTHROPIC_LARGE_MODEL=... # Large tier (default: claude-opus-4-7)
# =============================================================================
# OPTION 3: AWS Bedrock
@@ -36,7 +39,7 @@ ANTHROPIC_API_KEY=your-api-key-here
# Example Bedrock model IDs for us-east-1:
# ANTHROPIC_SMALL_MODEL=us.anthropic.claude-haiku-4-5-20251001-v1:0
# ANTHROPIC_MEDIUM_MODEL=us.anthropic.claude-sonnet-4-6
# ANTHROPIC_LARGE_MODEL=us.anthropic.claude-opus-4-6
# ANTHROPIC_LARGE_MODEL=us.anthropic.claude-opus-4-7
# CLAUDE_CODE_USE_BEDROCK=1
# AWS_REGION=us-east-1
@@ -52,7 +55,7 @@ ANTHROPIC_API_KEY=your-api-key-here
# Example Vertex AI model IDs:
# ANTHROPIC_SMALL_MODEL=claude-haiku-4-5@20251001
# ANTHROPIC_MEDIUM_MODEL=claude-sonnet-4-6
# ANTHROPIC_LARGE_MODEL=claude-opus-4-6
# ANTHROPIC_LARGE_MODEL=claude-opus-4-7
# CLAUDE_CODE_USE_VERTEX=1
# CLOUD_ML_REGION=us-east5
+1 -1
View File
@@ -146,7 +146,7 @@ Durable workflow orchestration with crash recovery, queryable progress, intellig
### Supporting Systems
- **Configuration** — YAML configs in `apps/worker/configs/` with JSON Schema validation (`config-schema.json`). Supports auth settings, MFA/TOTP, and per-app testing parameters. Credential resolution — local mode: env vars → `./.env`; npx mode: env vars → `~/.shannon/config.toml` (via `shn setup`)
- **Prompts** — Per-phase templates in `apps/worker/prompts/` with variable substitution (`{{TARGET_URL}}`, `{{CONFIG_CONTEXT}}`). Shared partials in `apps/worker/prompts/shared/` via `apps/worker/src/services/prompt-manager.ts`
- **SDK Integration** — Uses `@anthropic-ai/claude-agent-sdk` with `maxTurns: 10_000` and `bypassPermissions` mode. Browser automation via `playwright-cli` with session isolation (`-s=<session>`). TOTP generation via `generate-totp` CLI tool. Login flow template at `apps/worker/prompts/shared/login-instructions.txt` supports form, SSO, API, and basic auth
- **SDK Integration** — Uses `@anthropic-ai/claude-agent-sdk` with `maxTurns: 10_000` and `bypassPermissions` mode. Adaptive thinking is enabled by default on Opus 4.6/4.7 (`supportsAdaptiveThinking` in `apps/worker/src/ai/models.ts`); disable per-scan via `CLAUDE_ADAPTIVE_THINKING=false` (env) or `core.adaptive_thinking = false` (npx TOML). Browser automation via `playwright-cli` with session isolation (`-s=<session>`). TOTP generation via `generate-totp` CLI tool. Login flow template at `apps/worker/prompts/shared/login-instructions.txt` supports form, SSO, API, and basic auth
- **Audit System** — Crash-safe append-only logging in `workspaces/{hostname}_{sessionId}/`. Tracks session metrics, per-agent logs, prompts, and deliverables. WorkflowLogger (`apps/worker/src/audit/workflow-logger.ts`) provides unified human-readable per-workflow logs, backed by LogStream (`apps/worker/src/audit/log-stream.ts`) shared stream primitive
- **Deliverables** — Saved to `deliverables/` in the target repo via the `save-deliverable` CLI script (`apps/worker/src/scripts/save-deliverable.ts`)
- **Workspaces & Resume** — Named workspaces via `-w <name>` or auto-named from URL+timestamp. Resume detects completed agents via `session.json`. `loadResumeState()` in `apps/worker/src/temporal/activities.ts` validates deliverable existence, restores git checkpoints, and cleans up incomplete deliverables. Workspace listing via `apps/worker/src/temporal/workspaces.ts`
+14 -7
View File
@@ -425,6 +425,13 @@ npx @keygraph/shannon start -u https://example.com -r /path/to/repo -c ./my-app-
If your application uses two-factor authentication, simply add the TOTP secret to your config file. The AI will automatically generate the required codes during testing.
#### Adaptive Thinking (Opus 4.6/4.7)
Claude decides when and how deeply to reason on Opus 4.6 and 4.7. Enabled by default whenever a tier resolves to one of these models.
- **npx mode** — `npx @keygraph/shannon setup` prompts you during the wizard.
- **Local mode** — set `CLAUDE_ADAPTIVE_THINKING=false` in `.env` (or as an exported env var) to disable.
#### Subscription Plan Rate Limits
Anthropic subscription plans reset usage on a **rolling 5-hour window**. The default retry strategy (30-min max backoff) will exhaust retries before the window resets. Add this to your config:
@@ -453,7 +460,7 @@ export AWS_REGION=us-east-1
export AWS_BEARER_TOKEN_BEDROCK=your-bearer-token
export ANTHROPIC_SMALL_MODEL=us.anthropic.claude-haiku-4-5-20251001-v1:0
export ANTHROPIC_MEDIUM_MODEL=us.anthropic.claude-sonnet-4-6
export ANTHROPIC_LARGE_MODEL=us.anthropic.claude-opus-4-6
export ANTHROPIC_LARGE_MODEL=us.anthropic.claude-opus-4-7
```
<details>
@@ -465,12 +472,12 @@ AWS_REGION=us-east-1
AWS_BEARER_TOKEN_BEDROCK=your-bearer-token
ANTHROPIC_SMALL_MODEL=us.anthropic.claude-haiku-4-5-20251001-v1:0
ANTHROPIC_MEDIUM_MODEL=us.anthropic.claude-sonnet-4-6
ANTHROPIC_LARGE_MODEL=us.anthropic.claude-opus-4-6
ANTHROPIC_LARGE_MODEL=us.anthropic.claude-opus-4-7
```
</details>
Shannon uses three model tiers: **small** (`claude-haiku-4-5-20251001`) for summarization, **medium** (`claude-sonnet-4-6`) for security analysis, and **large** (`claude-opus-4-6`) for deep reasoning. Set `ANTHROPIC_SMALL_MODEL`, `ANTHROPIC_MEDIUM_MODEL`, and `ANTHROPIC_LARGE_MODEL` to the Bedrock model IDs for your region.
Shannon uses three model tiers: **small** (`claude-haiku-4-5-20251001`) for summarization, **medium** (`claude-sonnet-4-6`) for security analysis, and **large** (`claude-opus-4-7`) for deep reasoning. Set `ANTHROPIC_SMALL_MODEL`, `ANTHROPIC_MEDIUM_MODEL`, and `ANTHROPIC_LARGE_MODEL` to the Bedrock model IDs for your region.
### Google Vertex AI
@@ -491,7 +498,7 @@ export ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project-id
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/your-sa-key.json
export ANTHROPIC_SMALL_MODEL=claude-haiku-4-5@20251001
export ANTHROPIC_MEDIUM_MODEL=claude-sonnet-4-6
export ANTHROPIC_LARGE_MODEL=claude-opus-4-6
export ANTHROPIC_LARGE_MODEL=claude-opus-4-7
```
<details>
@@ -504,7 +511,7 @@ ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project-id
GOOGLE_APPLICATION_CREDENTIALS=./credentials/google-sa-key.json
ANTHROPIC_SMALL_MODEL=claude-haiku-4-5@20251001
ANTHROPIC_MEDIUM_MODEL=claude-sonnet-4-6
ANTHROPIC_LARGE_MODEL=claude-opus-4-6
ANTHROPIC_LARGE_MODEL=claude-opus-4-7
```
</details>
@@ -531,7 +538,7 @@ export ANTHROPIC_AUTH_TOKEN=your-auth-token
# Optionally override model tiers (defaults are used if not set)
export ANTHROPIC_SMALL_MODEL=claude-haiku-4-5-20251001
export ANTHROPIC_MEDIUM_MODEL=claude-sonnet-4-6
export ANTHROPIC_LARGE_MODEL=claude-opus-4-6
export ANTHROPIC_LARGE_MODEL=claude-opus-4-7
```
<details>
@@ -542,7 +549,7 @@ ANTHROPIC_BASE_URL=https://your-proxy.example.com
ANTHROPIC_AUTH_TOKEN=your-auth-token
ANTHROPIC_SMALL_MODEL=claude-haiku-4-5-20251001
ANTHROPIC_MEDIUM_MODEL=claude-sonnet-4-6
ANTHROPIC_LARGE_MODEL=claude-opus-4-6
ANTHROPIC_LARGE_MODEL=claude-opus-4-7
```
</details>
+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 {
+836 -174
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -2,4 +2,4 @@ packages:
- "apps/*"
catalog:
"@anthropic-ai/claude-agent-sdk": ^0.2.38
"@anthropic-ai/claude-agent-sdk": ^0.2.114