Skip to content

Commit a1db1fb

Browse files
Merge branch 'main' into feature/default-branch-pr-guard
2 parents feeb577 + d8a485e commit a1db1fb

33 files changed

+978
-193
lines changed

apps/server/src/git/Layers/ClaudeTextGeneration.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Effect, Layer, Option, Schema, Stream } from "effect";
1111
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
1212

1313
import { ClaudeModelSelection } from "@t3tools/contracts";
14+
import { resolveApiModelId } from "@t3tools/shared/model";
1415
import { sanitizeBranchFragment, sanitizeFeatureBranchName } from "@t3tools/shared/git";
1516

1617
import { TextGenerationError } from "../Errors.ts";
@@ -103,7 +104,7 @@ const makeClaudeTextGeneration = Effect.gen(function* () {
103104
"--json-schema",
104105
jsonSchemaStr,
105106
"--model",
106-
modelSelection.model,
107+
resolveApiModelId(modelSelection),
107108
...(normalizedOptions?.effort ? ["--effort", normalizedOptions.effort] : []),
108109
...(Object.keys(settings).length > 0 ? ["--settings", JSON.stringify(settings)] : []),
109110
"--dangerously-skip-permissions",

apps/server/src/open.test.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,24 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
4040
args: ["/tmp/workspace"],
4141
});
4242

43+
const vscodeInsidersLaunch = yield* resolveEditorLaunch(
44+
{ cwd: "/tmp/workspace", editor: "vscode-insiders" },
45+
"darwin",
46+
);
47+
assert.deepEqual(vscodeInsidersLaunch, {
48+
command: "code-insiders",
49+
args: ["/tmp/workspace"],
50+
});
51+
52+
const vscodiumLaunch = yield* resolveEditorLaunch(
53+
{ cwd: "/tmp/workspace", editor: "vscodium" },
54+
"darwin",
55+
);
56+
assert.deepEqual(vscodiumLaunch, {
57+
command: "codium",
58+
args: ["/tmp/workspace"],
59+
});
60+
4361
const zedLaunch = yield* resolveEditorLaunch(
4462
{ cwd: "/tmp/workspace", editor: "zed" },
4563
"darwin",
@@ -80,6 +98,24 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
8098
args: ["--goto", "/tmp/workspace/src/open.ts:71:5"],
8199
});
82100

101+
const vscodeInsidersLineAndColumn = yield* resolveEditorLaunch(
102+
{ cwd: "/tmp/workspace/src/open.ts:71:5", editor: "vscode-insiders" },
103+
"darwin",
104+
);
105+
assert.deepEqual(vscodeInsidersLineAndColumn, {
106+
command: "code-insiders",
107+
args: ["--goto", "/tmp/workspace/src/open.ts:71:5"],
108+
});
109+
110+
const vscodiumLineAndColumn = yield* resolveEditorLaunch(
111+
{ cwd: "/tmp/workspace/src/open.ts:71:5", editor: "vscodium" },
112+
"darwin",
113+
);
114+
assert.deepEqual(vscodiumLineAndColumn, {
115+
command: "codium",
116+
args: ["--goto", "/tmp/workspace/src/open.ts:71:5"],
117+
});
118+
83119
const zedLineAndColumn = yield* resolveEditorLaunch(
84120
{ cwd: "/tmp/workspace/src/open.ts:71:5", editor: "zed" },
85121
"darwin",
@@ -220,13 +256,14 @@ it.layer(NodeServices.layer)("resolveAvailableEditors", (it) => {
220256
const path = yield* Path.Path;
221257
const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-editors-" });
222258

223-
yield* fs.writeFileString(path.join(dir, "cursor.CMD"), "@echo off\r\n");
259+
yield* fs.writeFileString(path.join(dir, "code-insiders.CMD"), "@echo off\r\n");
260+
yield* fs.writeFileString(path.join(dir, "codium.CMD"), "@echo off\r\n");
224261
yield* fs.writeFileString(path.join(dir, "explorer.CMD"), "MZ");
225262
const editors = resolveAvailableEditors("win32", {
226263
PATH: dir,
227264
PATHEXT: ".COM;.EXE;.BAT;.CMD",
228265
});
229-
assert.deepEqual(editors, ["cursor", "file-manager"]);
266+
assert.deepEqual(editors, ["vscode-insiders", "vscodium", "file-manager"]);
230267
}),
231268
);
232269
});

apps/server/src/open.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,8 @@ interface CommandAvailabilityOptions {
3939

4040
const LINE_COLUMN_SUFFIX_PATTERN = /:\d+(?::\d+)?$/;
4141

42-
function shouldUseGotoFlag(editorId: EditorId, target: string): boolean {
43-
return (
44-
(editorId === "cursor" || editorId === "vscode") && LINE_COLUMN_SUFFIX_PATTERN.test(target)
45-
);
42+
function shouldUseGotoFlag(editor: (typeof EDITORS)[number], target: string): boolean {
43+
return editor.supportsGoto && LINE_COLUMN_SUFFIX_PATTERN.test(target);
4644
}
4745

4846
function fileManagerCommandForPlatform(platform: NodeJS.Platform): string {
@@ -213,7 +211,7 @@ export const resolveEditorLaunch = Effect.fnUntraced(function* (
213211
}
214212

215213
if (editorDef.command) {
216-
return shouldUseGotoFlag(editorDef.id, input.cwd)
214+
return shouldUseGotoFlag(editorDef, input.cwd)
217215
? { command: editorDef.command, args: ["--goto", input.cwd] }
218216
: { command: editorDef.command, args: [input.cwd] };
219217
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ describe("ClaudeAdapterLive", () => {
347347
);
348348
});
349349

350-
it.effect("ignores unsupported max effort for Sonnet 4.6", () => {
350+
it.effect("falls back to default effort when unsupported max is requested for Sonnet 4.6", () => {
351351
const harness = makeHarness();
352352
return Effect.gen(function* () {
353353
const adapter = yield* ClaudeAdapter;
@@ -365,7 +365,7 @@ describe("ClaudeAdapterLive", () => {
365365
});
366366

367367
const createInput = harness.getLastCreateQueryInput();
368-
assert.equal(createInput?.options.effort, undefined);
368+
assert.equal(createInput?.options.effort, "high");
369369
}).pipe(
370370
Effect.provideService(Random.Random, makeDeterministicRandomService()),
371371
Effect.provide(harness.layer),
@@ -532,7 +532,7 @@ describe("ClaudeAdapterLive", () => {
532532
});
533533

534534
const createInput = harness.getLastCreateQueryInput();
535-
assert.equal(createInput?.options.effort, undefined);
535+
assert.equal(createInput?.options.effort, "high");
536536
const promptText = yield* Effect.promise(() => readFirstPromptText(createInput));
537537
assert.equal(promptText, "Ultrathink:\nInvestigate the edge cases");
538538
}).pipe(

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

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,12 @@ import {
4040
type UserInputQuestion,
4141
ClaudeCodeEffort,
4242
} from "@t3tools/contracts";
43-
import { hasEffortLevel, applyClaudePromptEffortPrefix, trimOrNull } from "@t3tools/shared/model";
43+
import {
44+
applyClaudePromptEffortPrefix,
45+
resolveApiModelId,
46+
resolveEffort,
47+
trimOrNull,
48+
} from "@t3tools/shared/model";
4449
import {
4550
Cause,
4651
DateTime,
@@ -506,16 +511,15 @@ const CLAUDE_SETTING_SOURCES = [
506511
function buildPromptText(input: ProviderSendTurnInput): string {
507512
const rawEffort =
508513
input.modelSelection?.provider === "claudeAgent" ? input.modelSelection.options?.effort : null;
509-
const requestedEffort = trimOrNull(rawEffort);
510514
const claudeModel =
511515
input.modelSelection?.provider === "claudeAgent" ? input.modelSelection.model : undefined;
512516
const caps = getClaudeModelCapabilities(claudeModel);
517+
518+
// For prompt injection, we check if the raw effort is a prompt-injected level (e.g. "ultrathink").
519+
// resolveEffort strips prompt-injected values (returning the default instead), so we check the raw value directly.
520+
const trimmedEffort = trimOrNull(rawEffort);
513521
const promptEffort =
514-
requestedEffort === "ultrathink" && caps.reasoningEffortLevels.length > 0
515-
? "ultrathink"
516-
: requestedEffort && hasEffortLevel(caps, requestedEffort)
517-
? requestedEffort
518-
: null;
522+
trimmedEffort && caps.promptInjectedEffortLevels.includes(trimmedEffort) ? trimmedEffort : null;
519523
return applyClaudePromptEffortPrefix(input.input?.trim() ?? "", promptEffort);
520524
}
521525

@@ -2727,10 +2731,10 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
27272731
const claudeBinaryPath = claudeSettings.binaryPath;
27282732
const modelSelection =
27292733
input.modelSelection?.provider === "claudeAgent" ? input.modelSelection : undefined;
2730-
const requestedEffort = trimOrNull(modelSelection?.options?.effort ?? null);
27312734
const caps = getClaudeModelCapabilities(modelSelection?.model);
2732-
const effort =
2733-
requestedEffort && hasEffortLevel(caps, requestedEffort) ? requestedEffort : null;
2735+
const apiModelId = modelSelection ? resolveApiModelId(modelSelection) : undefined;
2736+
const effort = (resolveEffort(caps, modelSelection?.options?.effort) ??
2737+
null) as ClaudeCodeEffort | null;
27342738
const fastMode = modelSelection?.options?.fastMode === true && caps.supportsFastMode;
27352739
const thinking =
27362740
typeof modelSelection?.options?.thinking === "boolean" && caps.supportsThinkingToggle
@@ -2746,7 +2750,7 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
27462750

27472751
const queryOptions: ClaudeQueryOptions = {
27482752
...(input.cwd ? { cwd: input.cwd } : {}),
2749-
...(modelSelection?.model ? { model: modelSelection.model } : {}),
2753+
...(apiModelId ? { model: apiModelId } : {}),
27502754
pathToClaudeCodeExecutable: claudeBinaryPath,
27512755
settingSources: [...CLAUDE_SETTING_SOURCES],
27522756
...(effectiveEffort ? { effort: effectiveEffort } : {}),
@@ -2840,7 +2844,7 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
28402844
threadId,
28412845
payload: {
28422846
config: {
2843-
...(modelSelection?.model ? { model: modelSelection.model } : {}),
2847+
...(apiModelId ? { model: apiModelId } : {}),
28442848
...(input.cwd ? { cwd: input.cwd } : {}),
28452849
...(effectiveEffort ? { effort: effectiveEffort } : {}),
28462850
...(permissionMode ? { permissionMode } : {}),
@@ -2893,8 +2897,9 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
28932897
}
28942898

28952899
if (modelSelection?.model) {
2900+
const apiModelId = resolveApiModelId(modelSelection);
28962901
yield* Effect.tryPromise({
2897-
try: () => context.query.setModel(modelSelection.model),
2902+
try: () => context.query.setModel(apiModelId),
28982903
catch: (cause) => toRequestError(input.threadId, "turn/setModel", cause),
28992904
});
29002905
}

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

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type {
99
} from "@t3tools/contracts";
1010
import { Effect, Equal, Layer, Option, Result, Stream } from "effect";
1111
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
12-
import { getDefaultEffort, hasEffortLevel, trimOrNull } from "@t3tools/shared/model";
12+
import { resolveContextWindow, resolveEffort } from "@t3tools/shared/model";
1313

1414
import {
1515
buildServerProvider,
@@ -42,6 +42,10 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
4242
],
4343
supportsFastMode: true,
4444
supportsThinkingToggle: false,
45+
contextWindowOptions: [
46+
{ value: "200k", label: "200k" },
47+
{ value: "1m", label: "1M", isDefault: true },
48+
],
4549
promptInjectedEffortLevels: ["ultrathink"],
4650
} satisfies ModelCapabilities,
4751
},
@@ -58,6 +62,10 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
5862
],
5963
supportsFastMode: false,
6064
supportsThinkingToggle: false,
65+
contextWindowOptions: [
66+
{ value: "200k", label: "200k" },
67+
{ value: "1m", label: "1M", isDefault: true },
68+
],
6169
promptInjectedEffortLevels: ["ultrathink"],
6270
} satisfies ModelCapabilities,
6371
},
@@ -69,6 +77,7 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
6977
reasoningEffortLevels: [],
7078
supportsFastMode: false,
7179
supportsThinkingToggle: true,
80+
contextWindowOptions: [],
7281
promptInjectedEffortLevels: [],
7382
} satisfies ModelCapabilities,
7483
},
@@ -81,6 +90,7 @@ export function getClaudeModelCapabilities(model: string | null | undefined): Mo
8190
reasoningEffortLevels: [],
8291
supportsFastMode: false,
8392
supportsThinkingToggle: false,
93+
contextWindowOptions: [],
8494
promptInjectedEffortLevels: [],
8595
}
8696
);
@@ -91,23 +101,16 @@ export function normalizeClaudeModelOptions(
91101
modelOptions: ClaudeModelOptions | null | undefined,
92102
): ClaudeModelOptions | undefined {
93103
const caps = getClaudeModelCapabilities(model);
94-
const defaultReasoningEffort = getDefaultEffort(caps);
95-
const resolvedEffort = trimOrNull(modelOptions?.effort);
96-
const isPromptInjected = caps.promptInjectedEffortLevels.includes(resolvedEffort ?? "");
97-
const effort =
98-
resolvedEffort &&
99-
!isPromptInjected &&
100-
hasEffortLevel(caps, resolvedEffort) &&
101-
resolvedEffort !== defaultReasoningEffort
102-
? resolvedEffort
103-
: undefined;
104+
const effort = resolveEffort(caps, modelOptions?.effort);
104105
const thinking =
105106
caps.supportsThinkingToggle && modelOptions?.thinking === false ? false : undefined;
106107
const fastMode = caps.supportsFastMode && modelOptions?.fastMode === true ? true : undefined;
108+
const contextWindow = resolveContextWindow(caps, modelOptions?.contextWindow);
107109
const nextOptions: ClaudeModelOptions = {
108110
...(thinking === false ? { thinking: false } : {}),
109-
...(effort ? { effort } : {}),
111+
...(effort ? { effort: effort as ClaudeModelOptions["effort"] } : {}),
110112
...(fastMode ? { fastMode: true } : {}),
113+
...(contextWindow ? { contextWindow } : {}),
111114
};
112115
return Object.keys(nextOptions).length > 0 ? nextOptions : undefined;
113116
}

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type {
1010
} from "@t3tools/contracts";
1111
import { Effect, Equal, FileSystem, Layer, Option, Path, Result, Stream } from "effect";
1212
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
13-
import { getDefaultEffort, trimOrNull } from "@t3tools/shared/model";
13+
import { resolveEffort } from "@t3tools/shared/model";
1414

1515
import {
1616
buildServerProvider,
@@ -48,6 +48,7 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
4848
],
4949
supportsFastMode: true,
5050
supportsThinkingToggle: false,
51+
contextWindowOptions: [],
5152
promptInjectedEffortLevels: [],
5253
},
5354
},
@@ -64,6 +65,7 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
6465
],
6566
supportsFastMode: true,
6667
supportsThinkingToggle: false,
68+
contextWindowOptions: [],
6769
promptInjectedEffortLevels: [],
6870
},
6971
},
@@ -80,6 +82,7 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
8082
],
8183
supportsFastMode: true,
8284
supportsThinkingToggle: false,
85+
contextWindowOptions: [],
8386
promptInjectedEffortLevels: [],
8487
},
8588
},
@@ -96,6 +99,7 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
9699
],
97100
supportsFastMode: true,
98101
supportsThinkingToggle: false,
102+
contextWindowOptions: [],
99103
promptInjectedEffortLevels: [],
100104
},
101105
},
@@ -112,6 +116,7 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
112116
],
113117
supportsFastMode: true,
114118
supportsThinkingToggle: false,
119+
contextWindowOptions: [],
115120
promptInjectedEffortLevels: [],
116121
},
117122
},
@@ -128,6 +133,7 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
128133
],
129134
supportsFastMode: true,
130135
supportsThinkingToggle: false,
136+
contextWindowOptions: [],
131137
promptInjectedEffortLevels: [],
132138
},
133139
},
@@ -140,6 +146,7 @@ export function getCodexModelCapabilities(model: string | null | undefined): Mod
140146
reasoningEffortLevels: [],
141147
supportsFastMode: false,
142148
supportsThinkingToggle: false,
149+
contextWindowOptions: [],
143150
promptInjectedEffortLevels: [],
144151
}
145152
);
@@ -150,11 +157,10 @@ export function normalizeCodexModelOptions(
150157
modelOptions: CodexModelOptions | null | undefined,
151158
): CodexModelOptions | undefined {
152159
const caps = getCodexModelCapabilities(model);
153-
const defaultReasoningEffort = getDefaultEffort(caps);
154-
const reasoningEffort = trimOrNull(modelOptions?.reasoningEffort) ?? defaultReasoningEffort;
160+
const reasoningEffort = resolveEffort(caps, modelOptions?.reasoningEffort);
155161
const fastModeEnabled = modelOptions?.fastMode === true;
156162
const nextOptions: CodexModelOptions = {
157-
...(reasoningEffort && reasoningEffort !== defaultReasoningEffort
163+
...(reasoningEffort
158164
? { reasoningEffort: reasoningEffort as CodexModelOptions["reasoningEffort"] }
159165
: {}),
160166
...(fastModeEnabled ? { fastMode: true } : {}),

0 commit comments

Comments
 (0)