Skip to content
Closed
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
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
jobs:
check:
name: Check
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout
Expand Down Expand Up @@ -42,7 +42,7 @@ jobs:

test:
name: Test
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout
Expand All @@ -63,7 +63,7 @@ jobs:

mobile_native_static_analysis:
name: Mobile Native Static Analysis
runs-on: blacksmith-12vcpu-macos-26
runs-on: macos-26
timeout-minutes: 10
steps:
- name: Checkout
Expand All @@ -84,7 +84,7 @@ jobs:

release_smoke:
name: Release Smoke
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy-relay.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ concurrency:
jobs:
deploy_relay:
name: Deploy production relay
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 15
environment:
name: production
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/mobile-eas-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
preview:
name: EAS Preview
if: contains(github.event.pull_request.labels.*.name, '🚀 Mobile Continuous Deployment')
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
permissions:
contents: read
pull-requests: write
Expand Down
26 changes: 13 additions & 13 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
check_changes:
name: Check for changes since last nightly
if: github.event_name == 'schedule'
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
outputs:
has_changes: ${{ steps.check.outputs.has_changes }}
steps:
Expand Down Expand Up @@ -66,7 +66,7 @@ jobs:
if: |
!failure() && !cancelled() &&
(github.event_name != 'schedule' || needs.check_changes.outputs.has_changes == 'true')
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 10
outputs:
release_channel: ${{ steps.release_meta.outputs.release_channel }}
Expand Down Expand Up @@ -170,7 +170,7 @@ jobs:
name: Resolve T3 Connect public config
needs: preflight
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' }}
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 5
environment:
name: production
Expand Down Expand Up @@ -262,7 +262,7 @@ jobs:
name: Build WSL node-pty (linux-x64)
needs: [preflight]
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' }}
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Checkout
Expand Down Expand Up @@ -322,22 +322,22 @@ jobs:
matrix:
include:
- label: macOS arm64
runner: blacksmith-12vcpu-macos-26
runner: macos-26
platform: mac
target: dmg
arch: arm64
- label: macOS x64
runner: blacksmith-12vcpu-macos-26
runner: macos-26
platform: mac
target: dmg
arch: x64
- label: Linux x64
runner: blacksmith-32vcpu-ubuntu-2404
runner: ubuntu-24.04
platform: linux
target: AppImage
arch: x64
- label: Windows x64
runner: blacksmith-32vcpu-windows-2025
runner: windows-2025
platform: win
target: nsis
arch: x64
Expand Down Expand Up @@ -609,7 +609,7 @@ jobs:
name: Publish CLI to npm
needs: [preflight, relay_public_config, build]
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.build.result == 'success' }}
runs-on: ubuntu-24.04 # blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
Expand Down Expand Up @@ -666,7 +666,7 @@ jobs:
name: Publish GitHub Release
needs: [preflight, build, publish_cli]
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.build.result == 'success' && needs.publish_cli.result == 'success' }}
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- id: app_token
Expand Down Expand Up @@ -783,7 +783,7 @@ jobs:
name: Deploy hosted web app
needs: [preflight, relay_public_config, release]
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.release.result == 'success' }}
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 10
env:
T3CODE_CLERK_PUBLISHABLE_KEY: ${{ needs.relay_public_config.outputs.clerk_publishable_key }}
Expand Down Expand Up @@ -895,7 +895,7 @@ jobs:
name: Finalize release
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.release.result == 'success' && needs.preflight.outputs.release_channel == 'stable' }}
needs: [preflight, release]
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- id: app_token
Expand Down Expand Up @@ -976,7 +976,7 @@ jobs:
needs.deploy_web.result == 'success' &&
(needs.finalize.result == 'success' || needs.finalize.result == 'skipped')
needs: [preflight, relay_public_config, release, deploy_web, finalize]
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout
Expand Down
23 changes: 23 additions & 0 deletions apps/server/src/provider/Layers/ClaudeAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,29 @@ describe("ClaudeAdapterLive", () => {
);
});

it.effect("preserves xhigh effort for Claude Sonnet 5", () => {
const harness = makeHarness();
return Effect.gen(function* () {
const adapter = yield* ClaudeAdapter;
yield* adapter.startSession({
threadId: THREAD_ID,
provider: ProviderDriverKind.make("claudeAgent"),
modelSelection: createModelSelection(
ProviderInstanceId.make("claudeAgent"),
"claude-sonnet-5",
[{ id: "effort", value: "xhigh" }],
),
runtimeMode: "full-access",
});

const createInput = harness.getLastCreateQueryInput();
assert.equal(createInput?.options.effort, "xhigh");
}).pipe(
Effect.provideService(Random.Random, makeDeterministicRandomService()),
Effect.provide(harness.layer),
);
});

it.effect("falls back to default effort when unsupported max is requested for Sonnet 4.6", () => {
const harness = makeHarness();
return Effect.gen(function* () {
Expand Down
65 changes: 58 additions & 7 deletions apps/server/src/provider/Layers/ClaudeProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const CLAUDE_PRESENTATION = {
displayName: "Claude",
showInteractionModeToggle: true,
} as const;
const MINIMUM_CLAUDE_SONNET_5_VERSION = "2.1.197";
const MINIMUM_CLAUDE_FABLE_5_VERSION = "2.1.169";
const MINIMUM_CLAUDE_OPUS_4_8_VERSION = "2.1.154";
const MINIMUM_CLAUDE_OPUS_4_7_VERSION = "2.1.111";
Expand Down Expand Up @@ -193,6 +194,37 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
],
}),
},
{
slug: "claude-sonnet-5",
name: "Claude Sonnet 5",
isCustom: false,
capabilities: createModelCapabilities({
optionDescriptors: [
buildSelectOptionDescriptor({
id: "effort",
label: "Reasoning",
options: [
{ value: "low", label: "Low" },
{ value: "medium", label: "Medium" },
{ value: "high", label: "High", isDefault: true },
{ value: "xhigh", label: "Extra High" },
{ value: "max", label: "Max" },
{ value: "ultracode", label: "Ultracode" },
{ value: "ultrathink", label: "Ultrathink" },
],
promptInjectedValues: ["ultrathink"],
}),
buildSelectOptionDescriptor({
id: "contextWindow",
label: "Context Window",
options: [
{ value: "200k", label: "200k" },
{ value: "1m", label: "1M", isDefault: true },
],
}),
],
}),
},
{
slug: "claude-sonnet-4-6",
name: "Claude Sonnet 4.6",
Expand Down Expand Up @@ -237,6 +269,10 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
},
];

function supportsClaudeSonnet5(version: string | null | undefined): boolean {
return version ? compareSemverVersions(version, MINIMUM_CLAUDE_SONNET_5_VERSION) >= 0 : false;
}

function supportsClaudeFable5(version: string | null | undefined): boolean {
return version ? compareSemverVersions(version, MINIMUM_CLAUDE_FABLE_5_VERSION) >= 0 : false;
}
Expand All @@ -253,6 +289,9 @@ function getBuiltInClaudeModelsForVersion(
version: string | null | undefined,
): ReadonlyArray<ServerProviderModel> {
return BUILT_IN_MODELS.filter((model) => {
if (model.slug === "claude-sonnet-5") {
return supportsClaudeSonnet5(version);
}
if (model.slug === "claude-fable-5") {
return supportsClaudeFable5(version);
}
Expand All @@ -266,6 +305,11 @@ function getBuiltInClaudeModelsForVersion(
});
}

function formatClaudeSonnet5UpgradeMessage(version: string | null): string {
const versionLabel = version ? `v${version}` : "the installed version";
return `Claude Code ${versionLabel} is too old for Claude Sonnet 5. Upgrade to v${MINIMUM_CLAUDE_SONNET_5_VERSION} or newer to access it.`;
}

function formatClaudeFable5UpgradeMessage(version: string | null): string {
const versionLabel = version ? `v${version}` : "the installed version";
return `Claude Code ${versionLabel} is too old for Claude Fable 5. Upgrade to v${MINIMUM_CLAUDE_FABLE_5_VERSION} or newer to access it.`;
Expand Down Expand Up @@ -322,7 +366,12 @@ export function normalizeClaudeCliEffort(
if (effort === "ultracode") {
return "xhigh";
}
if (effort === "xhigh" && model !== "claude-fable-5" && model !== "claude-opus-4-8") {
if (
effort === "xhigh" &&
model !== "claude-fable-5" &&
model !== "claude-opus-4-8" &&
model !== "claude-sonnet-5"
) {
return "max";
}
if (effort === "max" && model === "claude-sonnet-4-6") {
Expand Down Expand Up @@ -726,13 +775,15 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
claudeSettings.customModels,
DEFAULT_CLAUDE_MODEL_CAPABILITIES,
);
const versionUpgradeMessage = supportsClaudeFable5(parsedVersion)
const versionUpgradeMessage = supportsClaudeSonnet5(parsedVersion)
? undefined
: supportsClaudeOpus48(parsedVersion)
? formatClaudeFable5UpgradeMessage(parsedVersion)
: supportsClaudeOpus47(parsedVersion)
? formatClaudeOpus48UpgradeMessage(parsedVersion)
: formatClaudeOpus47UpgradeMessage(parsedVersion);
: supportsClaudeFable5(parsedVersion)
? formatClaudeSonnet5UpgradeMessage(parsedVersion)
: supportsClaudeOpus48(parsedVersion)
? formatClaudeFable5UpgradeMessage(parsedVersion)
: supportsClaudeOpus47(parsedVersion)
? formatClaudeOpus48UpgradeMessage(parsedVersion)
: formatClaudeOpus47UpgradeMessage(parsedVersion);

const capabilities = resolveCapabilities
? yield* resolveCapabilities(claudeSettings).pipe(Effect.orElseSucceed(() => undefined))
Expand Down
57 changes: 57 additions & 0 deletions apps/server/src/provider/Layers/ProviderRegistry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1548,6 +1548,63 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te
),
);

it.effect("includes Claude Sonnet 5 on supported Claude Code versions", () =>
Effect.gen(function* () {
const status = yield* checkClaudeProviderStatus(
defaultClaudeSettings,
claudeCapabilities(),
);
const sonnet5 = status.models.find((model) => model.slug === "claude-sonnet-5");
assert.strictEqual(sonnet5?.name, "Claude Sonnet 5");
assert.strictEqual(status.message, undefined);
}).pipe(
Effect.provide(
mockSpawnerLayer((args) => {
const joined = args.join(" ");
if (joined === "--version") return { stdout: "2.1.197\n", stderr: "", code: 0 };
if (joined === "auth status")
return {
stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n',
stderr: "",
code: 0,
};
throw new Error(`Unexpected args: ${joined}`);
}),
),
),
);

it.effect("hides Claude Sonnet 5 on older Claude Code versions", () =>
Effect.gen(function* () {
const status = yield* checkClaudeProviderStatus(
defaultClaudeSettings,
claudeCapabilities(),
);
assert.strictEqual(
status.models.some((model) => model.slug === "claude-sonnet-5"),
false,
);
assert.strictEqual(
status.message,
"Claude Code v2.1.196 is too old for Claude Sonnet 5. Upgrade to v2.1.197 or newer to access it.",
);
}).pipe(
Effect.provide(
mockSpawnerLayer((args) => {
const joined = args.join(" ");
if (joined === "--version") return { stdout: "2.1.196\n", stderr: "", code: 0 };
if (joined === "auth status")
return {
stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n',
stderr: "",
code: 0,
};
throw new Error(`Unexpected args: ${joined}`);
}),
),
),
);

it.effect(
"includes Claude Opus 4.7 with xhigh as the default effort on supported versions",
() =>
Expand Down
Loading
Loading