From f37d02b29216796233d4d2f586b91c1e244fbb90 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Tue, 10 Feb 2026 23:34:13 +0000 Subject: [PATCH] upgrade Claude to Opus 4.6 with effort levels (#256) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * upgrade Claude to Opus 4.6 with --effort max for --max mode - mini: haiku → sonnet - auto: opusplan → opus (Opus 4.6) - max: opus → opus + --effort max (Opus 4.6 max effort) - bump @anthropic-ai/claude-agent-sdk 0.2.7 → 0.2.39 Co-authored-by: Cursor * update action lockfile for claude-agent-sdk 0.2.39 Co-authored-by: Cursor * add tool_use_summary handler for SDK 0.2.39 Co-authored-by: Cursor * integrate gpt-5.3-codex with runtime model availability detection checks GET /v1/models at agent start to determine if gpt-5.3-codex is available for the API key, falling back to gpt-5.2-codex when it isn't. model resolution runs concurrently with CLI install for zero added latency. also bumps @openai/codex-sdk from 0.80.0 to 0.98.0. Co-authored-by: Cursor --------- Co-authored-by: Cursor --- agents/claude.ts | 27 ++++++++++++------- agents/codex.ts | 56 +++++++++++++++++++++++++++++++++------- entry | 67 +++++++++++++++++++++++++++++++++++++++--------- package.json | 4 +-- pnpm-lock.yaml | 20 +++++++-------- post | 4 +-- 6 files changed, 134 insertions(+), 44 deletions(-) diff --git a/agents/claude.ts b/agents/claude.ts index ab8e486..6a44db4 100644 --- a/agents/claude.ts +++ b/agents/claude.ts @@ -17,16 +17,18 @@ import { type AgentRunContext, agent } from "./shared.ts"; // model selection based on effort level // these are aliases that always resolve to the latest version const claudeEffortModels: Record = { - mini: "haiku", - auto: "opusplan", + mini: "sonnet", + auto: "opus", max: "opus", }; -// FUTURE: Consider using Anthropic's "effort" parameter (beta) with Opus. -// This would allow a single model with effort levels ("low", "medium", "high") controlling -// token spend across responses, tool calls, and thinking. Requires beta header "effort-2025-11-24". -// See: https://platform.claude.com/docs/en/build-with-claude/effort -// This approach could replace model selection if effort proves effective for controlling capability. +// Claude Code CLI --effort level per pullfrog effort +// null = use default (high). "max" is Opus 4.6 only. +const claudeEffortLevels: Record = { + mini: null, + auto: null, + max: "max", +}; /** * Build disallowedTools list from payload permissions. @@ -81,9 +83,10 @@ export const claude = agent({ // install CLI at start of run const cliPath = await installClaude(); - // select model based on effort level + // select model and effort level const model = claudeEffortModels[ctx.payload.effort]; - log.info(`» using model: ${model} (effort: ${ctx.payload.effort})`); + const effortLevel = claudeEffortLevels[ctx.payload.effort]; + log.info(`» using model: ${model}${effortLevel ? ` (effort: ${effortLevel})` : ""}`); // build disallowedTools based on tool permissions const disallowedTools = buildDisallowedTools(ctx); @@ -110,6 +113,11 @@ export const claude = agent({ "--verbose", ]; + // add --effort flag if specified (e.g. "max" for Opus 4.6) + if (effortLevel) { + args.push("--effort", effortLevel); + } + // add disallowed tools if any if (disallowedTools.length > 0) { args.push("--disallowedTools"); @@ -301,5 +309,6 @@ const messageHandlers: SDKMessageHandlers = { system: () => {}, stream_event: () => {}, tool_progress: () => {}, + tool_use_summary: () => {}, auth_status: () => {}, }; diff --git a/agents/codex.ts b/agents/codex.ts index 6f5e603..2ca3fbb 100644 --- a/agents/codex.ts +++ b/agents/codex.ts @@ -15,14 +15,52 @@ import { type AgentRunContext, agent } from "./shared.ts"; // configuration based on effort level // https://developers.openai.com/codex/models/ -// gpt-5.3-codex announced 2026-02-05 but not yet available in codex CLI type ModelReasoningEffort = "minimal" | "low" | "medium" | "high" | "xhigh"; type CodexEffortConfig = { model: string; reasoningEffort?: ModelReasoningEffort }; -const codexEffortConfig: Record = { - mini: { model: "gpt-5.1-codex-mini", reasoningEffort: "low" }, - auto: { model: "gpt-5.2-codex" }, - max: { model: "gpt-5.2-codex", reasoningEffort: "high" }, -}; + +// preferred model for auto/max — falls back to gpt-5.2-codex if API key lacks access +const PREFERRED_MODEL = "gpt-5.3-codex"; +const FALLBACK_MODEL = "gpt-5.2-codex"; + +function getCodexEffortConfig(model: string): Record { + return { + mini: { model: "gpt-5.1-codex-mini", reasoningEffort: "low" }, + auto: { model }, + max: { model, reasoningEffort: "high" }, + }; +} + +// check if a model is available for the given API key via GET /v1/models +async function isModelAvailable(ctx: { apiKey: string; model: string }): Promise { + try { + const response = await fetch("https://api.openai.com/v1/models", { + headers: { Authorization: `Bearer ${ctx.apiKey}` }, + signal: AbortSignal.timeout(10_000), + }); + if (!response.ok) { + log.warning( + `failed to list models (HTTP ${response.status}), falling back to ${FALLBACK_MODEL}` + ); + return false; + } + const body = (await response.json()) as { data: Array<{ id: string }> }; + return body.data.some((m) => m.id === ctx.model); + } catch (err) { + log.warning(`failed to list models: ${err}, falling back to ${FALLBACK_MODEL}`); + return false; + } +} + +// resolve the best available model for auto/max effort levels +async function resolveModel(apiKey: string): Promise { + const available = await isModelAvailable({ apiKey, model: PREFERRED_MODEL }); + if (available) { + log.info(`» ${PREFERRED_MODEL} is available for this API key`); + return PREFERRED_MODEL; + } + log.info(`» ${PREFERRED_MODEL} not available, using ${FALLBACK_MODEL}`); + return FALLBACK_MODEL; +} function writeCodexConfig(ctx: AgentRunContext): string { const codexDir = join(ctx.tmpdir, ".codex"); @@ -95,14 +133,14 @@ export const codex = agent({ throw new Error("OPENAI_API_KEY is required for codex agent"); } - // install CLI at start of run - const cliPath = await installCodex(); + // install CLI and resolve model concurrently + const [cliPath, model] = await Promise.all([installCodex(), resolveModel(apiKey)]); // write config file (creates ~/.codex/config.toml) const codexDir = writeCodexConfig(ctx); // get model and reasoning effort based on effort level - const effortConfig = codexEffortConfig[ctx.payload.effort]; + const effortConfig = getCodexEffortConfig(model)[ctx.payload.effort]; log.info(`» using model: ${effortConfig.model} (effort: ${ctx.payload.effort})`); if (effortConfig.reasoningEffort) { log.info(`» using modelReasoningEffort: ${effortConfig.reasoningEffort}`); diff --git a/entry b/entry index ec69f1f..562a9d6 100755 --- a/entry +++ b/entry @@ -144125,13 +144125,13 @@ var package_default = { }, dependencies: { "@actions/core": "^1.11.1", - "@anthropic-ai/claude-agent-sdk": "0.2.7", + "@anthropic-ai/claude-agent-sdk": "0.2.39", "@ark/fs": "0.53.0", "@ark/util": "0.53.0", "@octokit/plugin-throttling": "^11.0.3", "@octokit/rest": "^22.0.0", "@octokit/webhooks-types": "^7.6.1", - "@openai/codex-sdk": "0.80.0", + "@openai/codex-sdk": "0.98.0", "@opencode-ai/sdk": "^1.0.143", "@standard-schema/spec": "1.0.0", "@toon-format/toon": "^1.0.0", @@ -144442,10 +144442,15 @@ ${ctx.instructions.user}` : null, // agents/claude.ts var claudeEffortModels = { - mini: "haiku", - auto: "opusplan", + mini: "sonnet", + auto: "opus", max: "opus" }; +var claudeEffortLevels = { + mini: null, + auto: null, + max: "max" +}; function buildDisallowedTools(ctx) { const disallowed = []; if (ctx.payload.web === "disabled") disallowed.push("WebFetch"); @@ -144483,7 +144488,8 @@ var claude = agent({ run: async (ctx) => { const cliPath = await installClaude(); const model = claudeEffortModels[ctx.payload.effort]; - log.info(`\xBB using model: ${model} (effort: ${ctx.payload.effort})`); + const effortLevel = claudeEffortLevels[ctx.payload.effort]; + log.info(`\xBB using model: ${model}${effortLevel ? ` (effort: ${effortLevel})` : ""}`); const disallowedTools = buildDisallowedTools(ctx); if (disallowedTools.length > 0) { log.info(`\xBB disallowed tools: ${disallowedTools.join(", ")}`); @@ -144502,6 +144508,9 @@ var claude = agent({ "stream-json", "--verbose" ]; + if (effortLevel) { + args3.push("--effort", effortLevel); + } if (disallowedTools.length > 0) { args3.push("--disallowedTools"); args3.push(...disallowedTools); @@ -144646,6 +144655,8 @@ var messageHandlers = { }, tool_progress: () => { }, + tool_use_summary: () => { + }, auth_status: () => { } }; @@ -144653,11 +144664,43 @@ var messageHandlers = { // agents/codex.ts import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync8 } from "node:fs"; import { join as join10 } from "node:path"; -var codexEffortConfig = { - mini: { model: "gpt-5.1-codex-mini", reasoningEffort: "low" }, - auto: { model: "gpt-5.2-codex" }, - max: { model: "gpt-5.2-codex", reasoningEffort: "high" } -}; +var PREFERRED_MODEL = "gpt-5.3-codex"; +var FALLBACK_MODEL = "gpt-5.2-codex"; +function getCodexEffortConfig(model) { + return { + mini: { model: "gpt-5.1-codex-mini", reasoningEffort: "low" }, + auto: { model }, + max: { model, reasoningEffort: "high" } + }; +} +async function isModelAvailable(ctx) { + try { + const response = await fetch("https://api.openai.com/v1/models", { + headers: { Authorization: `Bearer ${ctx.apiKey}` }, + signal: AbortSignal.timeout(1e4) + }); + if (!response.ok) { + log.warning( + `failed to list models (HTTP ${response.status}), falling back to ${FALLBACK_MODEL}` + ); + return false; + } + const body = await response.json(); + return body.data.some((m) => m.id === ctx.model); + } catch (err) { + log.warning(`failed to list models: ${err}, falling back to ${FALLBACK_MODEL}`); + return false; + } +} +async function resolveModel(apiKey) { + const available = await isModelAvailable({ apiKey, model: PREFERRED_MODEL }); + if (available) { + log.info(`\xBB ${PREFERRED_MODEL} is available for this API key`); + return PREFERRED_MODEL; + } + log.info(`\xBB ${PREFERRED_MODEL} not available, using ${FALLBACK_MODEL}`); + return FALLBACK_MODEL; +} function writeCodexConfig(ctx) { const codexDir = join10(ctx.tmpdir, ".codex"); mkdirSync4(codexDir, { recursive: true }); @@ -144709,9 +144752,9 @@ var codex = agent({ if (!apiKey) { throw new Error("OPENAI_API_KEY is required for codex agent"); } - const cliPath = await installCodex(); + const [cliPath, model] = await Promise.all([installCodex(), resolveModel(apiKey)]); const codexDir = writeCodexConfig(ctx); - const effortConfig = codexEffortConfig[ctx.payload.effort]; + const effortConfig = getCodexEffortConfig(model)[ctx.payload.effort]; log.info(`\xBB using model: ${effortConfig.model} (effort: ${ctx.payload.effort})`); if (effortConfig.reasoningEffort) { log.info(`\xBB using modelReasoningEffort: ${effortConfig.reasoningEffort}`); diff --git a/package.json b/package.json index 6890eb4..c853296 100644 --- a/package.json +++ b/package.json @@ -25,13 +25,13 @@ }, "dependencies": { "@actions/core": "^1.11.1", - "@anthropic-ai/claude-agent-sdk": "0.2.7", + "@anthropic-ai/claude-agent-sdk": "0.2.39", "@ark/fs": "0.53.0", "@ark/util": "0.53.0", "@octokit/plugin-throttling": "^11.0.3", "@octokit/rest": "^22.0.0", "@octokit/webhooks-types": "^7.6.1", - "@openai/codex-sdk": "0.80.0", + "@openai/codex-sdk": "0.98.0", "@opencode-ai/sdk": "^1.0.143", "@standard-schema/spec": "1.0.0", "@toon-format/toon": "^1.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3341b68..713855a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^1.11.1 version: 1.11.1 '@anthropic-ai/claude-agent-sdk': - specifier: 0.2.7 - version: 0.2.7(zod@4.3.5) + specifier: 0.2.39 + version: 0.2.39(zod@4.3.5) '@ark/fs': specifier: 0.53.0 version: 0.53.0 @@ -30,8 +30,8 @@ importers: specifier: ^7.6.1 version: 7.6.1 '@openai/codex-sdk': - specifier: 0.80.0 - version: 0.80.0 + specifier: 0.98.0 + version: 0.98.0 '@opencode-ai/sdk': specifier: ^1.0.143 version: 1.0.143 @@ -111,8 +111,8 @@ packages: '@actions/io@1.1.3': resolution: {integrity: sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==} - '@anthropic-ai/claude-agent-sdk@0.2.7': - resolution: {integrity: sha512-I1/zcnLah74kZeRkj/1QnDaC6ItJ2m/Bftlm25uoaRkZx7i7SkcpqM9jGE/r2A8PMxnw5WpabP60Xgj99CrTuw==} + '@anthropic-ai/claude-agent-sdk@0.2.39': + resolution: {integrity: sha512-wR1TBH62X6E1YwRnWa+A2Eau7AfpTWtfpnwQXO3yRY31FtmzOjPkQb93hbF3AkT0WL7YF9mxBBwJKUa3ZEc5+A==} engines: {node: '>=18.0.0'} peerDependencies: zod: ^4.0.0 @@ -620,8 +620,8 @@ packages: '@octokit/webhooks-types@7.6.1': resolution: {integrity: sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw==} - '@openai/codex-sdk@0.80.0': - resolution: {integrity: sha512-4+/bZOSjJAPsuM6yceQoVbNUK7UTS03QMKxCrFClObxD1j6n5oh7OSJniyff2MmF0DI7yHnf9omzfFL7RoLWnQ==} + '@openai/codex-sdk@0.98.0': + resolution: {integrity: sha512-TbPgrBpuSNMJyOXys0HNsh6UoP5VIHu1fVh2KDdACi5XyB0vuPtzBZC+qOsxHz7WXEQPFlomPLyxS6JnE5Okmg==} engines: {node: '>=18'} '@opencode-ai/sdk@1.0.143': @@ -1664,7 +1664,7 @@ snapshots: '@actions/io@1.1.3': {} - '@anthropic-ai/claude-agent-sdk@0.2.7(zod@4.3.5)': + '@anthropic-ai/claude-agent-sdk@0.2.39(zod@4.3.5)': dependencies: zod: 4.3.5 optionalDependencies: @@ -2012,7 +2012,7 @@ snapshots: '@octokit/webhooks-types@7.6.1': {} - '@openai/codex-sdk@0.80.0': {} + '@openai/codex-sdk@0.98.0': {} '@opencode-ai/sdk@1.0.143': {} diff --git a/post b/post index a7cb0bd..5b03375 100755 --- a/post +++ b/post @@ -41281,13 +41281,13 @@ var package_default = { }, dependencies: { "@actions/core": "^1.11.1", - "@anthropic-ai/claude-agent-sdk": "0.2.7", + "@anthropic-ai/claude-agent-sdk": "0.2.39", "@ark/fs": "0.53.0", "@ark/util": "0.53.0", "@octokit/plugin-throttling": "^11.0.3", "@octokit/rest": "^22.0.0", "@octokit/webhooks-types": "^7.6.1", - "@openai/codex-sdk": "0.80.0", + "@openai/codex-sdk": "0.98.0", "@opencode-ai/sdk": "^1.0.143", "@standard-schema/spec": "1.0.0", "@toon-format/toon": "^1.0.0",