Skip to content

Commit a75a9b4

Browse files
Split Codex and Claude traits handling by model capabilities
- Add dedicated Claude traits picker with model-aware effort, thinking, and fast mode controls - Treat Claude Ultrathink as a prompt keyword instead of session effort - Normalize provider model options in composer flow and adapter, with tests for unsupported effort/thinking cases
1 parent f98679d commit a75a9b4

16 files changed

Lines changed: 1666 additions & 434 deletions

apps/server/src/provider/Layers/ClaudeAdapter.test.ts

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,104 @@ describe("ClaudeAdapterLive", () => {
275275
);
276276
});
277277

278+
it.effect("ignores unsupported max effort for Sonnet 4.6", () => {
279+
const harness = makeHarness();
280+
return Effect.gen(function* () {
281+
const adapter = yield* ClaudeAdapter;
282+
yield* adapter.startSession({
283+
threadId: THREAD_ID,
284+
provider: "claudeAgent",
285+
model: "claude-sonnet-4-6",
286+
runtimeMode: "full-access",
287+
modelOptions: {
288+
claudeAgent: {
289+
effort: "max",
290+
},
291+
},
292+
});
293+
294+
const createInput = harness.getLastCreateQueryInput();
295+
assert.equal(createInput?.options.effort, undefined);
296+
}).pipe(
297+
Effect.provideService(Random.Random, makeDeterministicRandomService()),
298+
Effect.provide(harness.layer),
299+
);
300+
});
301+
302+
it.effect("ignores adaptive effort for Haiku 4.5", () => {
303+
const harness = makeHarness();
304+
return Effect.gen(function* () {
305+
const adapter = yield* ClaudeAdapter;
306+
yield* adapter.startSession({
307+
threadId: THREAD_ID,
308+
provider: "claudeAgent",
309+
model: "claude-haiku-4-5",
310+
runtimeMode: "full-access",
311+
modelOptions: {
312+
claudeAgent: {
313+
effort: "high",
314+
},
315+
},
316+
});
317+
318+
const createInput = harness.getLastCreateQueryInput();
319+
assert.equal(createInput?.options.effort, undefined);
320+
}).pipe(
321+
Effect.provideService(Random.Random, makeDeterministicRandomService()),
322+
Effect.provide(harness.layer),
323+
);
324+
});
325+
326+
it.effect("forwards Claude thinking toggle into SDK settings for Haiku 4.5", () => {
327+
const harness = makeHarness();
328+
return Effect.gen(function* () {
329+
const adapter = yield* ClaudeAdapter;
330+
yield* adapter.startSession({
331+
threadId: THREAD_ID,
332+
provider: "claudeAgent",
333+
model: "claude-haiku-4-5",
334+
runtimeMode: "full-access",
335+
modelOptions: {
336+
claudeAgent: {
337+
thinking: false,
338+
},
339+
},
340+
});
341+
342+
const createInput = harness.getLastCreateQueryInput();
343+
assert.deepEqual(createInput?.options.settings, {
344+
alwaysThinkingEnabled: false,
345+
});
346+
}).pipe(
347+
Effect.provideService(Random.Random, makeDeterministicRandomService()),
348+
Effect.provide(harness.layer),
349+
);
350+
});
351+
352+
it.effect("ignores Claude thinking toggle for non-Haiku models", () => {
353+
const harness = makeHarness();
354+
return Effect.gen(function* () {
355+
const adapter = yield* ClaudeAdapter;
356+
yield* adapter.startSession({
357+
threadId: THREAD_ID,
358+
provider: "claudeAgent",
359+
model: "claude-sonnet-4-6",
360+
runtimeMode: "full-access",
361+
modelOptions: {
362+
claudeAgent: {
363+
thinking: false,
364+
},
365+
},
366+
});
367+
368+
const createInput = harness.getLastCreateQueryInput();
369+
assert.equal(createInput?.options.settings, undefined);
370+
}).pipe(
371+
Effect.provideService(Random.Random, makeDeterministicRandomService()),
372+
Effect.provide(harness.layer),
373+
);
374+
});
375+
278376
it.effect("forwards claude fast mode into SDK settings", () => {
279377
const harness = makeHarness();
280378
return Effect.gen(function* () {
@@ -325,13 +423,14 @@ describe("ClaudeAdapterLive", () => {
325423
);
326424
});
327425

328-
it.effect("maps ultrathink to max effort and prefixes the prompt", () => {
426+
it.effect("treats ultrathink as a prompt keyword instead of a session effort", () => {
329427
const harness = makeHarness();
330428
return Effect.gen(function* () {
331429
const adapter = yield* ClaudeAdapter;
332430
const session = yield* adapter.startSession({
333431
threadId: THREAD_ID,
334432
provider: "claudeAgent",
433+
model: "claude-sonnet-4-6",
335434
runtimeMode: "full-access",
336435
modelOptions: {
337436
claudeAgent: {
@@ -344,6 +443,7 @@ describe("ClaudeAdapterLive", () => {
344443
threadId: session.threadId,
345444
input: "Investigate the edge cases",
346445
attachments: [],
446+
model: "claude-sonnet-4-6",
347447
modelOptions: {
348448
claudeAgent: {
349449
effort: "ultrathink",
@@ -352,7 +452,7 @@ describe("ClaudeAdapterLive", () => {
352452
});
353453

354454
const createInput = harness.getLastCreateQueryInput();
355-
assert.equal(createInput?.options.effort, "max");
455+
assert.equal(createInput?.options.effort, undefined);
356456
const promptText = yield* Effect.promise(() => readFirstPromptText(createInput));
357457
assert.equal(promptText, "Ultrathink:\nInvestigate the edge cases");
358458
}).pipe(

apps/server/src/provider/Layers/ClaudeAdapter.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ import {
4040
import {
4141
applyClaudePromptEffortPrefix,
4242
getEffectiveClaudeCodeEffort,
43+
getReasoningEffortOptions,
44+
resolveReasoningEffortForProvider,
4345
supportsClaudeFastMode,
46+
supportsClaudeThinkingToggle,
47+
supportsClaudeUltrathinkKeyword,
4448
} from "@t3tools/shared/model";
4549
import { Cause, DateTime, Deferred, Effect, Layer, Queue, Random, Ref, Stream } from "effect";
4650

@@ -351,10 +355,18 @@ function buildUserMessage(input: ProviderSendTurnInput): SDKUserMessage {
351355
}
352356
}
353357

354-
const text = applyClaudePromptEffortPrefix(
355-
fragments.join("\n\n"),
358+
const requestedEffort = resolveReasoningEffortForProvider(
359+
"claudeAgent",
356360
input.modelOptions?.claudeAgent?.effort ?? null,
357361
);
362+
const supportedEffortOptions = getReasoningEffortOptions("claudeAgent", input.model);
363+
const promptEffort =
364+
requestedEffort === "ultrathink" && supportsClaudeUltrathinkKeyword(input.model)
365+
? "ultrathink"
366+
: requestedEffort && supportedEffortOptions.includes(requestedEffort)
367+
? requestedEffort
368+
: null;
369+
const text = applyClaudePromptEffortPrefix(fragments.join("\n\n"), promptEffort);
358370

359371
return {
360372
type: "user",
@@ -2197,13 +2209,30 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
21972209
);
21982210

21992211
const providerOptions = input.providerOptions?.claudeAgent;
2200-
const effort = input.modelOptions?.claudeAgent?.effort;
2212+
const requestedEffort = resolveReasoningEffortForProvider(
2213+
"claudeAgent",
2214+
input.modelOptions?.claudeAgent?.effort ?? null,
2215+
);
2216+
const supportedEffortOptions = getReasoningEffortOptions("claudeAgent", input.model);
2217+
const effort =
2218+
requestedEffort && supportedEffortOptions.includes(requestedEffort)
2219+
? requestedEffort
2220+
: null;
22012221
const fastMode =
22022222
input.modelOptions?.claudeAgent?.fastMode === true && supportsClaudeFastMode(input.model);
2223+
const thinking =
2224+
typeof input.modelOptions?.claudeAgent?.thinking === "boolean" &&
2225+
supportsClaudeThinkingToggle(input.model)
2226+
? input.modelOptions.claudeAgent.thinking
2227+
: undefined;
22032228
const effectiveEffort = getEffectiveClaudeCodeEffort(effort);
22042229
const permissionMode =
22052230
toPermissionMode(providerOptions?.permissionMode) ??
22062231
(input.runtimeMode === "full-access" ? "bypassPermissions" : undefined);
2232+
const settings = {
2233+
...(typeof thinking === "boolean" ? { alwaysThinkingEnabled: thinking } : {}),
2234+
...(fastMode ? { fastMode: true } : {}),
2235+
};
22072236

22082237
const queryOptions: ClaudeQueryOptions = {
22092238
...(input.cwd ? { cwd: input.cwd } : {}),
@@ -2219,7 +2248,7 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
22192248
...(providerOptions?.maxThinkingTokens !== undefined
22202249
? { maxThinkingTokens: providerOptions.maxThinkingTokens }
22212250
: {}),
2222-
...(fastMode ? { settings: { fastMode: true } } : {}),
2251+
...(Object.keys(settings).length > 0 ? { settings } : {}),
22232252
...(resumeState?.resume ? { resume: resumeState.resume } : {}),
22242253
...(resumeState?.resumeSessionAt ? { resumeSessionAt: resumeState.resumeSessionAt } : {}),
22252254
includePartialMessages: true,

0 commit comments

Comments
 (0)