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
78 changes: 78 additions & 0 deletions apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,84 @@ describe("ClaudeAdapterV2 Auto-accept edits", () => {
);
});

describe("ClaudeAdapterV2 custom model names", () => {
it.effect("sends a custom model that shares a built-in alias under its own name", () =>
Effect.scoped(
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const opened: Array<ClaudeAdapterV2.ClaudeAgentSdkQueryOptions> = [];
const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
settings: CUSTOM_MODEL_CLAUDE_SETTINGS,
environment: {},
attachmentsDir: yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-claude-custom-",
}),
fileSystem,
path: yield* Path.Path,
crypto: yield* Crypto.Crypto,
idAllocator: yield* IdAllocator.IdAllocatorV2,
queryRunner: {
allocateSessionId: Effect.succeed("native-thread-claude-custom"),
open: (input) =>
Effect.sync(() => {
opened.push(input.options);
return {
messages: Stream.never,
offer: () => Effect.void,
setModel: () => Effect.void,
applyFlagSettings: () => Effect.void,
setPermissionMode: () => Effect.void,
backgroundTasks: () => Effect.succeed(false),
stopTask: () => Effect.void,
interrupt: Effect.void,
close: Effect.void,
};
}),
forkSession: () => Effect.die("unused"),
subagentLaunchToolUseId: () => Effect.succeed(null),
assertComplete: Effect.void,
},
});
// "opus" is a built-in alias, and a proxy may serve its own model of that name.
const modelSelection = { ...CLAUDE_TEST_MODEL_SELECTION, model: "opus", options: [] };
const threadId = ThreadId.make("thread-claude-custom");
const runtime = yield* adapter.openSession({
threadId,
providerSessionId: ProviderSessionId.make("provider-session-claude-custom"),
modelSelection,
runtimePolicy: CLAUDE_TEST_RUNTIME_POLICY,
});
const providerThread = yield* runtime.ensureThread({
threadId,
modelSelection,
runtimePolicy: CLAUDE_TEST_RUNTIME_POLICY,
});
yield* runtime
.startTurn(
makeClaudeTestTurnInput({
threadId,
providerThread,
now: yield* DateTime.now,
attemptId: RunAttemptId.make("attempt-claude-custom"),
text: "Hello",
attachments: [],
modelSelection,
}),
)
.pipe(Effect.exit);

assert.lengthOf(opened, 1);
assert.equal(opened[0]!.model, "opus");
}).pipe(
Effect.provide(
Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
),
),
),
);
});

describe("ClaudeAdapterV2 append-system-prompt-file launch argument", () => {
const startTurnWithLaunchArgs = Effect.fnUntraced(function* (launchArgs: string, cwd: string) {
const fileSystem = yield* FileSystem.FileSystem;
Expand Down
28 changes: 18 additions & 10 deletions apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1424,8 +1424,9 @@ export function makeClaudeQueryOptions(input: {
readonly appendSystemPromptFileText?: string;
// Flag settings, because a user's settings.json env beats the process env in Claude Code.
readonly toolSearchOverride?: "true";
readonly modelCatalog?: ClaudeModelCatalog;
}): ClaudeAgentSdkQueryOptions {
const compiledSelection = compileClaudeModelSelection(input.modelSelection);
const compiledSelection = compileClaudeModelSelection(input.modelSelection, input.modelCatalog);
const {
"permission-mode": launchArgPermissionMode,
"dangerously-skip-permissions": launchArgSkipPermissions,
Expand Down Expand Up @@ -4242,7 +4243,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
const serverSettings = yield* Effect.serviceOption(ServerSettings.ServerSettingsService);
const { attachmentsDir, fileSystem, path, crypto, idAllocator, queryRunner } = adapterOptions;
const subagentModelCatalog = scopeClaudeModelCatalog(
const scopedModelCatalog = scopeClaudeModelCatalog(
BUNDLED_CLAUDE_MODEL_CATALOG,
adapterOptions.settings.customModels,
);
Expand Down Expand Up @@ -6081,7 +6082,8 @@ 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, scopedModelCatalog)
.effort)
: existingSubagent.effort;
const model = input.model ?? priorTask?.model ?? null;
const task = {
Expand Down Expand Up @@ -6119,7 +6121,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
...(existingSubagent === undefined || existingSubagent.task.modelSelection !== undefined
? {
modelSelection: claudeSubagentModelSelection(
subagentModelCatalog,
scopedModelCatalog,
input.context.input.modelSelection.instanceId,
model || input.context.input.modelSelection.model,
effort,
Expand Down Expand Up @@ -8424,7 +8426,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
const model =
snapshotModel.length === 0 || snapshotModel === "<synthetic>"
? undefined
: canonicalClaudeSubagentModel(subagentModelCatalog, snapshotModel);
: canonicalClaudeSubagentModel(scopedModelCatalog, snapshotModel);
if (parentToolUseId !== null && model !== undefined) {
const subagent = yield* resolveSubagentByToolUseId(context, parentToolUseId);
if (subagent === undefined) {
Expand Down Expand Up @@ -8708,7 +8710,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
const nativeToolInput = claudeNativeToolInputFromUnknown(toolUse.input);
if (toolUse.name === "Agent") {
rememberClaudeSubagentLaunch(
subagentModelCatalog,
scopedModelCatalog,
context,
pendingSubagentLaunchesByToolUseId,
toolUse.id,
Expand Down Expand Up @@ -9280,7 +9282,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
const heldForEcho = context.heldRootFrames.length > 0;
if (toolName === "Agent") {
rememberClaudeSubagentLaunch(
subagentModelCatalog,
scopedModelCatalog,
context,
pendingSubagentLaunchesByToolUseId,
nativeRequestId,
Expand Down Expand Up @@ -9631,7 +9633,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,
scopedModelCatalog,
);
const resumeSessionAt = yield* getNativeConversationHeadId(turnInput.providerThread);
const existing = yield* Ref.get(queryContext);
// Continuations drain existing output without prompting; retain the live process regardless of selection.
Expand Down Expand Up @@ -9759,6 +9764,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
supportedDialogKinds: ["resume_return"],
...(appendSystemPromptFileText === undefined ? {} : { appendSystemPromptFileText }),
...(toolSearchOverride === undefined ? {} : { toolSearchOverride }),
modelCatalog: scopedModelCatalog,
});
const querySession = yield* queryRunner
.open({
Expand Down Expand Up @@ -9927,7 +9933,8 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
: yield* makeClaudeUserMessageWithAttachments({
text: applyClaudePromptEffortPrefix(
turnInput.message.text,
compileClaudeModelSelection(turnInput.modelSelection).promptEffort,
compileClaudeModelSelection(turnInput.modelSelection, scopedModelCatalog)
.promptEffort,
),
attachments: turnInput.message.attachments,
attachmentsDir,
Expand Down Expand Up @@ -10380,7 +10387,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, scopedModelCatalog)
.promptEffort,
),
attachments: turnInput.message.attachments,
priority: "now",
Expand Down
Loading