Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion apps/marketing/src/content/docs/guides/ai-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,19 @@ Requires the `claude` CLI installed and authenticated. Uses Claude Code's full s

**Models:**

- Sonnet 4.6 (default)
- Sonnet 5 (default)
- Fable 5
- Opus 5
- Opus 4.8
- Sonnet 4.6
- Opus 4.7
- Opus 4.6
- Haiku 4.5

Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6 are also offered in `(1M)` context
variants. The 5-series models use their 1M context window by default, so they
have no separate variant.

### Codex (via Codex SDK)

Requires the `codex` CLI installed and authenticated. The AI operates in a sandboxed read-only mode with the diff context injected as a system prompt prefix.
Expand Down
26 changes: 26 additions & 0 deletions packages/ai/ai.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,32 @@ describe("resolveSDKModel", () => {
expect(resolveSDKModel("claude-sonnet-4-6[1m]", bedrockEnv)).toBe(SONNET_ARN);
});

// Opus 5 is a bare alias like every other picker entry: it must NOT be
// mistaken for a cloud identifier (no `arn:` / `anthropic.` prefix), and the
// family matcher must still route it to the configured Opus ARN on
// Bedrock/Vertex. A matcher that keyed on "opus-4" would silently drop it.
test("maps the bare Opus 5 alias to the configured Opus ARN", () => {
expect(resolveSDKModel("claude-opus-5", bedrockEnv)).toBe(OPUS_ARN);
expect(
resolveSDKModel("claude-opus-5", {
CLAUDE_CODE_USE_VERTEX: "true",
ANTHROPIC_DEFAULT_OPUS_MODEL: OPUS_ARN,
}),
).toBe(OPUS_ARN);
});

test("off Bedrock/Vertex: returns the Opus 5 alias unchanged", () => {
expect(resolveSDKModel("claude-opus-5", {})).toBe("claude-opus-5");
});

// Fable has no ANTHROPIC_DEFAULT_*_MODEL env var of its own (Claude Code
// only defines OPUS/SONNET/HAIKU), so on Bedrock it falls through to
// ANTHROPIC_MODEL rather than matching a family. Pinned so the fallthrough
// stays deliberate rather than looking like a missed family branch.
test("falls back to ANTHROPIC_MODEL for the Fable alias on Bedrock", () => {
expect(resolveSDKModel("claude-fable-5", bedrockEnv)).toBe(OPUS_ARN);
});

test("passes through an identifier that is already an ARN", () => {
expect(resolveSDKModel(SONNET_ARN, bedrockEnv)).toBe(SONNET_ARN);
});
Expand Down
1 change: 1 addition & 0 deletions packages/ai/providers/claude-agent-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export class ClaudeAgentSDKProvider implements AIProvider {
};
readonly models = [
{ id: 'claude-fable-5', label: 'Fable 5' },
{ id: 'claude-opus-5', label: 'Opus 5' },
{ id: 'claude-opus-4-8', label: 'Opus 4.8' },
{ id: 'claude-opus-4-8[1m]', label: 'Opus 4.8 (1M)' },
{ id: 'claude-sonnet-5', label: 'Sonnet 5', default: true },
Expand Down
10 changes: 10 additions & 0 deletions packages/server/agent-review-message.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,16 @@ describe("getLocalDiffInstruction", () => {
});

describe("buildClaudeCommand", () => {
test("defaults to Opus 5 when the caller passes no model", () => {
const command = buildClaudeCommand("review").command;
expect(command[command.indexOf("--model") + 1]).toBe("claude-opus-5");
});

test("still honours an explicitly requested model", () => {
const command = buildClaudeCommand("review", "claude-sonnet-5").command;
expect(command[command.indexOf("--model") + 1]).toBe("claude-sonnet-5");
});

test("allows read-only JJ commands", () => {
const command = buildClaudeCommand("review").command;
const allowedTools = command[command.indexOf("--allowedTools") + 1];
Expand Down
2 changes: 1 addition & 1 deletion packages/server/claude-review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ export interface ClaudeCommandResult {
* Build the `claude -p` command. Prompt is passed via stdin, not as a
* positional arg — avoids quoting issues, argv limits, and variadic flag conflicts.
*/
export function buildClaudeCommand(prompt: string, model: string = "claude-opus-4-7", effort?: string): ClaudeCommandResult {
export function buildClaudeCommand(prompt: string, model: string = "claude-opus-5", effort?: string): ClaudeCommandResult {
const allowedTools = [
"Agent", "Read", "Glob", "Grep",
// GitHub CLI
Expand Down
29 changes: 28 additions & 1 deletion packages/ui/components/AgentsTab.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,35 @@
import { describe, expect, test } from "bun:test";
import { CODEX_MODELS, codexReasoningOptions } from "./AgentsTab";
import { CLAUDE_MODELS, CODEX_MODELS, TOUR_CLAUDE_MODELS, codexReasoningOptions } from "./AgentsTab";

const catalogEntry = (value: string) => CODEX_MODELS.find((m) => m.value === value);

describe("CLAUDE_MODELS catalog", () => {
test("offers the current 5-series flagships", () => {
for (const value of ["claude-fable-5", "claude-opus-5", "claude-sonnet-5"]) {
expect(CLAUDE_MODELS.some((m) => m.value === value)).toBe(true);
}
expect(CLAUDE_MODELS.find((m) => m.value === "claude-opus-5")?.label).toBe("Opus 5");
});

// The 5-series models are 1M-context by default, so they carry no `[1m]`
// variant — that suffix exists only to opt the older 4.x models into the
// larger window. Pinned so a future entry doesn't invent `claude-opus-5[1m]`.
test("5-series entries have no [1m] context variant", () => {
for (const { value } of CLAUDE_MODELS) {
if (/-5(\[|$)/.test(value)) expect(value).not.toContain("[1m]");
}
});

test("tour/guide catalog inherits every review model plus the latest aliases", () => {
for (const { value } of CLAUDE_MODELS) {
expect(TOUR_CLAUDE_MODELS.some((m) => m.value === value)).toBe(true);
}
for (const alias of ["sonnet", "opus", "fable"]) {
expect(TOUR_CLAUDE_MODELS.some((m) => m.value === alias)).toBe(true);
}
});
});

test("uses the canonical GPT-5.6 Sol model ID", () => {
expect(catalogEntry("gpt-5.6-sol")?.label).toBe("GPT-5.6 Sol");
expect(CODEX_MODELS.some(({ value }) => value === "gpt-5.6")).toBe(false);
Expand Down
1 change: 1 addition & 0 deletions packages/ui/components/AgentsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export type { AgentLaunchParams } from '../hooks/useAgentJobs';

export const CLAUDE_MODELS: Array<{ value: string; label: string }> = [
{ value: 'claude-fable-5', label: 'Fable 5' },
{ value: 'claude-opus-5', label: 'Opus 5' },
{ value: 'claude-opus-4-8', label: 'Opus 4.8' },
{ value: 'claude-opus-4-8[1m]', label: 'Opus 4.8 (1M)' },
{ value: 'claude-sonnet-5', label: 'Sonnet 5' },
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/hooks/useAgentSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const COOKIE_KEY = 'plannotator.agents';
// would clobber writes exactly like before.
const settingsListeners = new Set<(s: AgentSettingsState) => void>();

export const DEFAULT_CLAUDE_MODEL = 'claude-opus-4-7';
export const DEFAULT_CLAUDE_MODEL = 'claude-opus-5';
export const DEFAULT_CLAUDE_EFFORT = 'high';
// gpt-5.3-codex is deprecated (ChatGPT-account Codex rejects it outright) —
// default to the current flagship everywhere.
Expand Down