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
1 change: 1 addition & 0 deletions apps/server/src/mcp/toolkits/subagent/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ const buildModelSources = (runtime: SubagentRuntime) =>
driverKind: providerInstance.driverKind,
models: snapshot.models.map((providerModel) => ({
slug: providerModel.slug,
isCustom: providerModel.isCustom,
optionDescriptors: providerModel.capabilities?.optionDescriptors,
defaultOptions: buildProviderOptionSelectionsFromDescriptors(
providerModel.capabilities?.optionDescriptors,
Expand Down
4 changes: 2 additions & 2 deletions apps/server/src/mcp/toolkits/subagent/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export const ScheduleCreateInput = Schema.Struct({
intervalSeconds: Schema.optionalKey(Schema.Int),
cronExpr: Schema.optionalKey(Schema.String),
timezone: Schema.optionalKey(Schema.String),
// Optional plain model name (e.g. "claude-opus-4-8" or "gpt-5.4"); the
// Optional plain model name (e.g. "claude-opus-5" or "gpt-5.4"); the
// provider/harness is inferred from the live model lists, so the caller never
// guesses a harness/instance id. Omit to inherit the thread's current model.
model: Schema.optionalKey(TrimmedNonEmptyString),
Expand Down Expand Up @@ -196,7 +196,7 @@ export const SubagentsTool = Tool.make("t3_subagents", {

export const ScheduleCreateTool = Tool.make("t3_schedule_create", {
description:
"Schedule a recurring prompt to be sent to a thread (defaults to the calling thread). Provide exactly one of intervalSeconds (fixed interval) or cronExpr (a cron expression, validated on create); timezone defaults to UTC and the busy policy always defaults to skip. The same thread is reused on every trigger. To pin the model each run uses, pass `model` as a plain model name (e.g. 'claude-opus-4-8' or 'gpt-5.4'); the provider/harness is inferred automatically, so you never guess a harness/instance id. Pin a model on the thread's own provider (like the interactive model picker) — pinning a different provider than the thread's active session errors at run time, so prefer a dedicated thread for a cross-provider schedule. Omit `model` to inherit the thread's current model on each run.",
"Schedule a recurring prompt to be sent to a thread (defaults to the calling thread). Provide exactly one of intervalSeconds (fixed interval) or cronExpr (a cron expression, validated on create); timezone defaults to UTC and the busy policy always defaults to skip. The same thread is reused on every trigger. To pin the model each run uses, pass `model` as a plain model name (e.g. 'claude-opus-5' or 'gpt-5.4'); the provider/harness is inferred automatically, so you never guess a harness/instance id. Pin a model on the thread's own provider (like the interactive model picker) — pinning a different provider than the thread's active session errors at run time, so prefer a dedicated thread for a cross-provider schedule. Omit `model` to inherit the thread's current model on each run.",
parameters: ScheduleCreateInput,
success: ScheduleEntry,
failure: ThreadStartToolError,
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/mcp/toolkits/thread/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@ const makeActiveThreadStartRuntime = Effect.fn("ThreadToolkit.makeActiveRuntime"
driverKind: providerInstance.driverKind,
models: snapshot.models.map((providerModel) => ({
slug: providerModel.slug,
isCustom: providerModel.isCustom,
optionDescriptors: providerModel.capabilities?.optionDescriptors,
defaultOptions: buildProviderOptionSelectionsFromDescriptors(
providerModel.capabilities?.optionDescriptors,
Expand Down Expand Up @@ -728,6 +729,7 @@ const makeActiveThreadStartRuntime = Effect.fn("ThreadToolkit.makeActiveRuntime"
driverKind: providerInstance.driverKind,
models: snapshot.models.map((providerModel) => ({
slug: providerModel.slug,
isCustom: providerModel.isCustom,
optionDescriptors: providerModel.capabilities?.optionDescriptors,
defaultOptions: buildProviderOptionSelectionsFromDescriptors(
providerModel.capabilities?.optionDescriptors,
Expand Down
12 changes: 12 additions & 0 deletions apps/server/src/provider/Drivers/ClaudeDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import * as Crypto from "effect/Crypto";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import * as Ref from "effect/Ref";
import * as Schema from "effect/Schema";
import { HttpClient } from "effect/unstable/http";
import { ChildProcessSpawner } from "effect/unstable/process";
Expand All @@ -32,6 +33,7 @@ import {
checkClaudeProviderStatus,
makePendingClaudeProvider,
probeClaudeCapabilities,
reconcileKnownClaudeModelsAfterVersionProbe,
} from "../Layers/ClaudeProvider.ts";
import { ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts";
import { makeManagedServerProvider } from "../makeManagedServerProvider.ts";
Expand Down Expand Up @@ -160,13 +162,23 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv> = {
),
});
const capabilitiesCacheKey = yield* makeClaudeCapabilitiesCacheKey(effectiveConfig, cwd);
const knownVersionedModels = yield* Ref.make<ServerProvider["models"]>([]);

const checkProvider = checkClaudeProviderStatus(
effectiveConfig,
() => Cache.get(capabilitiesProbeCache, capabilitiesCacheKey),
processEnv,
).pipe(
Effect.map(stampIdentity),
Effect.flatMap((nextSnapshot) =>
Ref.modify(knownVersionedModels, (knownModels) => {
const reconciled = reconcileKnownClaudeModelsAfterVersionProbe(
knownModels,
nextSnapshot,
);
return [reconciled.snapshot, reconciled.knownModels];
}),
),
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
Effect.provideService(Path.Path, path),
);
Expand Down
29 changes: 26 additions & 3 deletions apps/server/src/provider/Layers/ClaudeAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,29 @@ describe("ClaudeAdapterLive", () => {
);
});

it.effect("preserves xhigh effort for Claude Opus 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-opus-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 Expand Up @@ -1435,7 +1458,7 @@ describe("ClaudeAdapterLive", () => {
const adapter = yield* ClaudeAdapter;
const requestedModel = createModelSelection(
ProviderInstanceId.make("claudeAgent"),
"claude-fable-5",
"claude-opus-5",
);
const runtimeEventsFiber = yield* Stream.takeUntil(
adapter.streamEvents,
Expand Down Expand Up @@ -1615,7 +1638,7 @@ describe("ClaudeAdapterLive", () => {
parent_tool_use_id: null,
message: {
id: "assistant-message-effective-model-alias",
model: "claude-opus-4-8-20260718",
model: "claude-opus-5-20260718",
content: [{ type: "text", text: "Alias answer" }],
},
} as unknown as SDKMessage);
Expand All @@ -1628,7 +1651,7 @@ describe("ClaudeAdapterLive", () => {
session_id: "sdk-session-effective-model-alias",
uuid: "result-effective-model-alias",
modelUsage: {
"claude-opus-4-8-20260718": {
"claude-opus-5-20260718": {
contextWindow: 1_000_000,
maxOutputTokens: 128_000,
},
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/provider/Layers/ClaudeAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ function selectedClaudeContextWindow(
modelSelection: ModelSelection | undefined,
): number | undefined {
switch (modelSelection?.model) {
case "claude-opus-5":
case "claude-opus-4-8":
case "claude-opus-4-7":
// Always 1M at the API; these models expose no contextWindow option.
Expand Down
Loading
Loading