Skip to content

Commit ea54ac6

Browse files
committed
fix: validate contextWindow against model capabilities before resolveApiModelId
- ClaudeTextGeneration.ts: pass normalized options (with validated contextWindow) to resolveApiModelId instead of raw modelSelection - ClaudeAdapter.ts: resolve contextWindow against model capabilities before computing apiModelId in both startSession and sendTurn paths - contracts/model.ts: use TrimmedNonEmptyString for ContextWindowOption.value to match EffortOption pattern and prevent empty string values
1 parent ff503d1 commit ea54ac6

File tree

3 files changed

+23
-5
lines changed

3 files changed

+23
-5
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,10 @@ const makeClaudeTextGeneration = Effect.gen(function* () {
104104
"--json-schema",
105105
jsonSchemaStr,
106106
"--model",
107-
resolveApiModelId(modelSelection),
107+
resolveApiModelId({
108+
...modelSelection,
109+
options: normalizedOptions ?? {},
110+
}),
108111
...(normalizedOptions?.effort ? ["--effort", normalizedOptions.effort] : []),
109112
...(Object.keys(settings).length > 0 ? ["--settings", JSON.stringify(settings)] : []),
110113
"--dangerously-skip-permissions",

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
hasEffortLevel,
4545
applyClaudePromptEffortPrefix,
4646
resolveApiModelId,
47+
resolveContextWindow,
4748
trimOrNull,
4849
} from "@t3tools/shared/model";
4950
import {
@@ -2732,9 +2733,15 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
27322733
const claudeBinaryPath = claudeSettings.binaryPath;
27332734
const modelSelection =
27342735
input.modelSelection?.provider === "claudeAgent" ? input.modelSelection : undefined;
2735-
const apiModelId = modelSelection ? resolveApiModelId(modelSelection) : undefined;
2736-
const requestedEffort = trimOrNull(modelSelection?.options?.effort ?? null);
27372736
const caps = getClaudeModelCapabilities(modelSelection?.model);
2737+
const contextWindow = resolveContextWindow(caps, modelSelection?.options?.contextWindow);
2738+
const apiModelId = modelSelection
2739+
? resolveApiModelId({
2740+
...modelSelection,
2741+
options: { ...modelSelection.options, contextWindow },
2742+
})
2743+
: undefined;
2744+
const requestedEffort = trimOrNull(modelSelection?.options?.effort ?? null);
27382745
const effort =
27392746
requestedEffort && hasEffortLevel(caps, requestedEffort) ? requestedEffort : null;
27402747
const fastMode = modelSelection?.options?.fastMode === true && caps.supportsFastMode;
@@ -2899,7 +2906,15 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
28992906
}
29002907

29012908
if (modelSelection?.model) {
2902-
const apiModelId = resolveApiModelId(modelSelection);
2909+
const turnCaps = getClaudeModelCapabilities(modelSelection.model);
2910+
const turnContextWindow = resolveContextWindow(
2911+
turnCaps,
2912+
modelSelection.options?.contextWindow,
2913+
);
2914+
const apiModelId = resolveApiModelId({
2915+
...modelSelection,
2916+
options: { ...modelSelection.options, contextWindow: turnContextWindow },
2917+
});
29032918
yield* Effect.tryPromise({
29042919
try: () => context.query.setModel(apiModelId),
29052920
catch: (cause) => toRequestError(input.threadId, "turn/setModel", cause),

packages/contracts/src/model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const EffortOption = Schema.Struct({
3636
export type EffortOption = typeof EffortOption.Type;
3737

3838
export const ContextWindowOption = Schema.Struct({
39-
value: Schema.String,
39+
value: TrimmedNonEmptyString,
4040
label: TrimmedNonEmptyString,
4141
isDefault: Schema.optional(Schema.Boolean),
4242
});

0 commit comments

Comments
 (0)