Skip to content
42 changes: 42 additions & 0 deletions apps/server/src/claudeModelOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { describe, expect, it } from "@effect/vitest";
import { ProviderInstanceId, type ModelSelection } from "@t3tools/contracts";

import { compileClaudeModelSelection } from "./claudeModelOptions.ts";
import {
BUNDLED_CLAUDE_MODEL_CATALOG,
scopeClaudeModelCatalog,
} from "./provider/ClaudeModelCatalog.ts";

const selection = (
model: string,
Expand Down Expand Up @@ -74,4 +78,42 @@ describe("compileClaudeModelSelection", () => {
.settings,
).toEqual({ alwaysThinkingEnabled: false });
});

it("defaults custom models to thinking on and 200k, with a reversible 1M choice", () => {
const catalog = scopeClaudeModelCatalog(BUNDLED_CLAUDE_MODEL_CATALOG, ["glm-flash"]);
for (const options of [[], [{ id: "contextWindow", value: "200k" }]]) {
expect(compileClaudeModelSelection(selection("glm-flash", options), catalog)).toMatchObject({
apiModelId: "glm-flash",
effort: "high",
settings: { alwaysThinkingEnabled: true },
});
}
expect(
compileClaudeModelSelection(
selection("glm-flash", [{ id: "contextWindow", value: "1m" }]),
catalog,
),
).toMatchObject({
apiModelId: "glm-flash[1m]",
settings: { alwaysThinkingEnabled: true },
});
});

it("compiles 1M, effort, and thinking for a bare custom model", () => {
const catalog = scopeClaudeModelCatalog(BUNDLED_CLAUDE_MODEL_CATALOG, ["glm-flash"]);
expect(
compileClaudeModelSelection(
selection("glm-flash", [
{ id: "contextWindow", value: "1m" },
{ id: "effort", value: "max" },
{ id: "thinking", value: false },
]),
catalog,
),
).toMatchObject({
apiModelId: "glm-flash[1m]",
effort: "max",
settings: { alwaysThinkingEnabled: false },
});
});
});
4 changes: 3 additions & 1 deletion apps/server/src/claudeModelOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
getModelSelectionBooleanOptionValue,
getModelSelectionStringOptionValue,
getProviderOptionDescriptors,
getProviderOptionCurrentValue,
resolvePromptInjectedEffort,
} from "@t3tools/shared/model";

Expand Down Expand Up @@ -39,7 +40,8 @@ export function compileClaudeModelSelection(
? (getModelSelectionBooleanOptionValue(selection, "fastMode") ?? false)
: undefined;
const thinking = supportsBoolean("thinking")
? getModelSelectionBooleanOptionValue(selection, "thinking")
? (getModelSelectionBooleanOptionValue(selection, "thinking") ??
getProviderOptionCurrentValue(descriptors.find((descriptor) => descriptor.id === "thinking")))
: undefined;
const settings = {
...(typeof thinking === "boolean" ? { alwaysThinkingEnabled: thinking } : {}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ import { WorktreeToolkit } from "../../mcp/toolkits/worktree/tools.ts";
import { ThreadToolkit } from "../../mcp/toolkits/thread/tools.ts";
import { OrchestratorToolkit } from "../../mcp/toolkits/orchestrator/tools.ts";
import { ClaudeExecutableFileCheck } from "../../provider/Drivers/ClaudeExecutable.ts";
import {
BUNDLED_CLAUDE_MODEL_CATALOG,
scopeClaudeModelCatalog,
} from "../../provider/ClaudeModelCatalog.ts";
import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
import type * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests";
Expand Down Expand Up @@ -126,6 +130,9 @@ const DEFAULT_CLAUDE_SETTINGS = Schema.decodeSync(ClaudeSettings)({});
const AUTO_COMPACT_CLAUDE_SETTINGS = Schema.decodeSync(ClaudeSettings)({
autoCompactWindow: "300000",
});
const BARE_CUSTOM_MODEL_CLAUDE_SETTINGS = Schema.decodeSync(ClaudeSettings)({
customModels: [{ slug: "gateway-bare", name: "Gateway Bare" }],
});
const CUSTOM_MODEL_CLAUDE_SETTINGS = Schema.decodeSync(ClaudeSettings)({
customModels: [
"opus",
Expand Down Expand Up @@ -1026,6 +1033,38 @@ describe("ClaudeAdapterV2 context usage", () => {
);
assert.strictEqual(usage.promptCacheExpiresAt, "2026-08-29T00:05:00.000Z");
});

it("defaults a bare custom slug and opens 1M through the turn path", () => {
const settings = BARE_CUSTOM_MODEL_CLAUDE_SETTINGS;
const bareSelection = {
instanceId: ProviderInstanceId.make(ClaudeAdapterV2.CLAUDE_PROVIDER),
model: "gateway-bare",
options: [
{ id: "effort", value: "max" },
{ id: "contextWindow", value: "1m" },
],
} satisfies ModelSelection;
const options = ClaudeAdapterV2.makeClaudeQueryOptions({
modelSelection: bareSelection,
nativeThreadId: "bare-model-thread",
resume: false,
cwd: "/workspace",
settings,
});
assert.equal(options.model, "gateway-bare[1m]");
assert.equal(options.effort, "max");
assert.equal(
ClaudeAdapterV2.claudeProviderTurnTokenUsage(
{ input_tokens: 1, output_tokens: 1 },
bareSelection,
"2026-08-29T00:00:00.000Z",
"",
undefined,
scopeClaudeModelCatalog(BUNDLED_CLAUDE_MODEL_CATALOG, settings.customModels),
).maxTokens,
1_000_000,
);
});
});

describe("ClaudeAdapterV2 session permissions", () => {
Expand Down Expand Up @@ -12630,7 +12669,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
model: "claude-custom-tuned",
options: [{ id: "effort", value: "brutal" }],
},
[`task-${SHADOW_TOOL_USE_ID}`]: { instanceId, model: "opus" },
[`task-${SHADOW_TOOL_USE_ID}`]: {
instanceId,
model: "opus",
options: [{ id: "effort", value: "high" }],
},
});
}).pipe(
Effect.provide(
Expand Down
41 changes: 29 additions & 12 deletions apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,11 @@ import {
export const CLAUDE_PROVIDER = ProviderDriverKind.make("claudeAgent");
export const CLAUDE_AGENT_SDK_QUERY_PROTOCOL = "claude-agent-sdk.query" as const;

function claudeContextWindow(modelSelection: ModelSelection): number | null {
return (
resolveClaudeCatalogContextWindowTokens(BUNDLED_CLAUDE_MODEL_CATALOG, modelSelection) ?? null
);
function claudeContextWindow(
catalog: ClaudeModelCatalog,
modelSelection: ModelSelection,
): number | null {
return resolveClaudeCatalogContextWindowTokens(catalog, modelSelection) ?? null;
}

interface ClaudeCacheCreation {
Expand Down Expand Up @@ -243,11 +244,12 @@ export function claudeProviderTurnTokenUsage(
updatedAt: string,
autoCompactWindow = "",
requestedAt?: DateTime.Utc,
catalog: ClaudeModelCatalog = BUNDLED_CLAUDE_MODEL_CATALOG,
) {
const inputTokens = claudeMessageInputTokens(usage);
const outputTokens = usage.output_tokens;
const promptCacheTtlMs = claudePromptCacheTtlMs(usage.cache_creation);
const maxTokens = claudeContextWindow(modelSelection);
const maxTokens = claudeContextWindow(catalog, modelSelection);
// The cache lifetime starts with the request, not when its response ends.
const promptCacheExpiresAt =
promptCacheTtlMs === undefined || requestedAt === undefined
Expand Down Expand Up @@ -1261,7 +1263,10 @@ export function makeClaudeQueryOptions(input: {
readonly allowDangerouslySkipPermissions?: boolean;
readonly appendSystemPromptFileText?: string;
}): ClaudeAgentSdkQueryOptions {
const compiledSelection = compileClaudeModelSelection(input.modelSelection);
const compiledSelection = compileClaudeModelSelection(
input.modelSelection,
scopeClaudeModelCatalog(BUNDLED_CLAUDE_MODEL_CATALOG, input.settings?.customModels ?? []),
);
const {
"permission-mode": launchArgPermissionMode,
"dangerously-skip-permissions": launchArgSkipPermissions,
Expand Down Expand Up @@ -4215,6 +4220,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
DateTime.formatIso(now),
adapterOptions.settings.autoCompactWindow,
context.streamUsage?.requestedAt,
subagentModelCatalog,
);
if (tokenUsage.inputTokens <= 0) return;
yield* emitProviderEvent({
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
Expand Down Expand Up @@ -5534,7 +5540,10 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
existingSubagent === undefined
? (input.effort ??
input.owner?.effort ??
compileClaudeModelSelection(input.context.input.modelSelection).effort)
compileClaudeModelSelection(
input.context.input.modelSelection,
subagentModelCatalog,
).effort)
: existingSubagent.effort;
const model = input.model ?? priorTask?.model ?? null;
const task = {
Expand Down Expand Up @@ -7691,7 +7700,10 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
? Math.round(postTokens)
: undefined;
if (afterTokenCount !== undefined) {
const maxTokens = claudeContextWindow(context.input.modelSelection);
const maxTokens = claudeContextWindow(
subagentModelCatalog,
context.input.modelSelection,
);
yield* emitProviderEvent({
type: "provider_turn.updated",
driver: CLAUDE_PROVIDER,
Expand Down Expand Up @@ -9043,7 +9055,10 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
: { allowedTools: queryPolicy.allowedTools }),
});
const queryPolicyKey = claudeEffectiveQueryPolicyKey(queryPolicy, mcpOverrides);
const compiledSelection = compileClaudeModelSelection(turnInput.modelSelection);
const compiledSelection = compileClaudeModelSelection(
turnInput.modelSelection,
subagentModelCatalog,
);
const resumeSessionAt = yield* getNativeConversationHeadId(turnInput.providerThread);
const existing = yield* Ref.get(queryContext);
// A continuation prompts nothing: it drains output the live process
Expand Down Expand Up @@ -9331,7 +9346,8 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
: yield* makeClaudeUserMessageWithAttachments({
text: applyClaudePromptEffortPrefix(
turnInput.message.text,
compileClaudeModelSelection(turnInput.modelSelection).promptEffort,
compileClaudeModelSelection(turnInput.modelSelection, subagentModelCatalog)
.promptEffort,
),
attachments: turnInput.message.attachments,
attachmentsDir,
Expand Down Expand Up @@ -9761,7 +9777,8 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
const userMessage = yield* makeClaudeUserMessageWithAttachments({
text: applyClaudePromptEffortPrefix(
turnInput.message.text,
compileClaudeModelSelection(currentTurn.input.modelSelection).promptEffort,
compileClaudeModelSelection(currentTurn.input.modelSelection, subagentModelCatalog)
.promptEffort,
),
attachments: turnInput.message.attachments,
priority: "now",
Expand Down Expand Up @@ -9896,7 +9913,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
providerSessionId: input.providerSessionId,
providerSession: session,
getModelContextWindow: (selection) =>
resolveClaudeCatalogContextWindowTokens(BUNDLED_CLAUDE_MODEL_CATALOG, selection),
resolveClaudeCatalogContextWindowTokens(subagentModelCatalog, selection),
events: Stream.fromEffectRepeat(Queue.take(events)),
backgroundTasksEndWithProcess: true,
hasPendingBackgroundWork: Effect.gen(function* () {
Expand Down
Loading
Loading