Skip to content

Commit 066c4f3

Browse files
committed
Add prompt enhancement controls and GPT-5.5 support
- add rewrite prompt enhancement in the compact composer menu - hide Codex fast mode when the model/backend cannot use it - register GPT-5.5 and GPT-5.5 Mini across model catalogs
1 parent 81f4613 commit 066c4f3

16 files changed

Lines changed: 548 additions & 64 deletions

apps/server/src/provider/providerCatalog.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,38 @@ const noCapabilities = null;
1515

1616
export const BUILT_IN_PROVIDER_MODELS: Record<ProviderKind, ReadonlyArray<ProviderCatalogEntry>> = {
1717
codex: [
18+
{
19+
slug: "gpt-5.5",
20+
name: "GPT-5.5",
21+
capabilities: {
22+
reasoningEffortLevels: [
23+
{ value: "low", label: "Low" },
24+
{ value: "medium", label: "Medium" },
25+
{ value: "high", label: "High", isDefault: true },
26+
{ value: "xhigh", label: "Extra High" },
27+
],
28+
supportsFastMode: true,
29+
supportsThinkingToggle: false,
30+
contextWindowOptions: [],
31+
promptInjectedEffortLevels: [],
32+
},
33+
},
34+
{
35+
slug: "gpt-5.5-mini",
36+
name: "GPT-5.5 Mini",
37+
capabilities: {
38+
reasoningEffortLevels: [
39+
{ value: "low", label: "Low" },
40+
{ value: "medium", label: "Medium" },
41+
{ value: "high", label: "High", isDefault: true },
42+
{ value: "xhigh", label: "Extra High" },
43+
],
44+
supportsFastMode: true,
45+
supportsThinkingToggle: false,
46+
contextWindowOptions: [],
47+
promptInjectedEffortLevels: [],
48+
},
49+
},
1850
{
1951
slug: "gpt-5.4",
2052
name: "GPT-5.4",
@@ -117,6 +149,8 @@ export const BUILT_IN_PROVIDER_MODELS: Record<ProviderKind, ReadonlyArray<Provid
117149
],
118150
openclaw: [],
119151
copilot: [
152+
{ slug: "gpt-5.5", name: "GPT-5.5", capabilities: noCapabilities },
153+
{ slug: "gpt-5.5-mini", name: "GPT-5.5 Mini", capabilities: noCapabilities },
120154
{ slug: "gpt-5.4", name: "GPT-5.4", capabilities: noCapabilities },
121155
{ slug: "gpt-5.4-mini", name: "GPT-5.4 Mini", capabilities: noCapabilities },
122156
{ slug: "gpt-5.3-codex", name: "GPT-5.3 Codex", capabilities: noCapabilities },

apps/web/src/components/ChatView.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -939,15 +939,17 @@ export default function ChatView({
939939
],
940940
);
941941
const selectedModel = getModelSelectionModel(selectedModelSelection);
942+
const codexBackendId = serverConfigQuery.data?.codexConfig?.selectedModelProviderId ?? null;
942943
const composerProviderState = useMemo(
943944
() =>
944945
getComposerProviderState({
945946
provider: selectedProvider,
946947
model: selectedModel,
947948
prompt,
948949
modelOptions: draftModelOptions,
950+
codexBackendId,
949951
}),
950-
[draftModelOptions, prompt, selectedModel, selectedProvider],
952+
[codexBackendId, draftModelOptions, prompt, selectedModel, selectedProvider],
951953
);
952954
const selectedPromptEffort = composerProviderState.promptEffort;
953955
const selectedModelOptionsForDispatch = composerProviderState.modelOptionsForDispatch;
@@ -4407,12 +4409,14 @@ export default function ChatView({
44074409
threadId,
44084410
model: selectedModel,
44094411
onPromptChange: setPromptFromTraits,
4412+
codexBackendId,
44104413
});
44114414
const providerTraitsPicker = renderProviderTraitsPicker({
44124415
provider: selectedProvider,
44134416
threadId,
44144417
model: selectedModel,
44154418
onPromptChange: setPromptFromTraits,
4419+
codexBackendId,
44164420
});
44174421
const onEnvModeChange = useCallback(
44184422
(mode: DraftThreadEnvMode) => {
@@ -5512,6 +5516,10 @@ export default function ChatView({
55125516
planSidebarOpen={planSidebarOpen}
55135517
runtimeMode={runtimeMode}
55145518
traitsMenuContent={providerTraitsMenuContent}
5519+
promptEnhancement={composerPromptEnhancement}
5520+
promptEnhancementAvailable={pendingUserInputs.length === 0}
5521+
promptEnhancementBusy={isEnhancingPrompt}
5522+
onPromptEnhancementChange={onPromptEnhancementChange}
55155523
onInteractionModeChange={handleInteractionModeChange}
55165524
onTogglePlanSidebar={togglePlanSidebar}
55175525
onToggleRuntimeMode={toggleRuntimeMode}

apps/web/src/components/chat/ClaudeTraitsPicker.browser.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,27 @@ describe("ClaudeTraitsPicker", () => {
176176
}
177177
});
178178

179+
it("checks the Ultrathink option when ultrathink is active in the prompt", async () => {
180+
const mounted = await mountPicker({
181+
model: "claude-opus-4-6",
182+
prompt: "Ultrathink:\nInvestigate this",
183+
});
184+
185+
try {
186+
await page.getByRole("button").click();
187+
188+
const ultrathinkItem = page.getByRole("menuitemradio", { name: "Ultrathink" });
189+
await vi.waitFor(() => {
190+
expect(ultrathinkItem.element().getAttribute("aria-checked")).toBe("true");
191+
});
192+
193+
const highItem = page.getByRole("menuitemradio", { name: "High (default)" });
194+
expect(highItem.element().getAttribute("aria-checked")).toBe("false");
195+
} finally {
196+
await mounted.cleanup();
197+
}
198+
});
199+
179200
it("persists sticky claude model options when traits change", async () => {
180201
const mounted = await mountPicker({
181202
model: "claude-opus-4-6",

apps/web/src/components/chat/ClaudeTraitsPicker.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,13 @@ function getSelectedClaudeTraits(
4646
prompt: string,
4747
modelOptions: ClaudeModelOptions | null | undefined,
4848
): {
49-
effort: Exclude<ClaudeCodeEffort, "ultrathink"> | null;
49+
/**
50+
* The effort value to mirror in the radio group. When ultrathink is currently
51+
* active (because the prompt carries the `ultrathink` keyword), this is
52+
* `"ultrathink"` so that the radio group draws a checkmark next to the
53+
* Ultrathink option, matching how every other thinking level renders.
54+
*/
55+
effort: ClaudeCodeEffort | null;
5056
thinkingEnabled: boolean | null;
5157
fastModeEnabled: boolean;
5258
options: ReadonlyArray<ClaudeCodeEffort>;
@@ -59,7 +65,7 @@ function getSelectedClaudeTraits(
5965
"ultrathink"
6066
>;
6167
const resolvedEffort = resolveReasoningEffortForProvider(PROVIDER, modelOptions?.effort);
62-
const effort =
68+
const baseEffort: Exclude<ClaudeCodeEffort, "ultrathink"> | null =
6369
resolvedEffort && resolvedEffort !== "ultrathink" && options.includes(resolvedEffort)
6470
? resolvedEffort
6571
: options.includes(defaultReasoningEffort)
@@ -69,13 +75,15 @@ function getSelectedClaudeTraits(
6975
? (modelOptions?.thinking ?? true)
7076
: null;
7177
const supportsFastMode = supportsClaudeFastMode(model);
78+
const ultrathinkPromptControlled =
79+
supportsClaudeUltrathinkKeyword(model) && isClaudeUltrathinkPrompt(prompt);
80+
const effort: ClaudeCodeEffort | null = ultrathinkPromptControlled ? "ultrathink" : baseEffort;
7281
return {
7382
effort,
7483
thinkingEnabled,
7584
fastModeEnabled: supportsFastMode && modelOptions?.fastMode === true,
7685
options,
77-
ultrathinkPromptControlled:
78-
supportsClaudeUltrathinkKeyword(model) && isClaudeUltrathinkPrompt(prompt),
86+
ultrathinkPromptControlled,
7987
supportsFastMode,
8088
};
8189
}

apps/web/src/components/chat/CodexTraitsPicker.browser.tsx

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ import { COMPOSER_DRAFT_STORAGE_KEY, useComposerDraftStore } from "../../compose
1111
async function mountPicker(props: {
1212
reasoningEffort?: "low" | "medium" | "high" | "xhigh";
1313
fastModeEnabled: boolean;
14+
model?: string | null;
15+
backendId?: string | null;
1416
}) {
1517
const threadId = ThreadId.makeUnsafe("thread-codex-traits");
1618
const draftsByThreadId = {} as ReturnType<
1719
typeof useComposerDraftStore.getState
1820
>["draftsByThreadId"];
21+
const model = props.model === undefined ? "gpt-5.4" : props.model;
1922
draftsByThreadId[threadId] = {
2023
prompt: "",
2124
attachments: [],
@@ -25,7 +28,7 @@ async function mountPicker(props: {
2528
promptEnhancement: null,
2629
promptEnhancementOriginalPrompt: null,
2730
provider: "codex",
28-
model: null,
31+
model,
2932
modelOptions: {
3033
codex: {
3134
...(props.reasoningEffort ? { reasoningEffort: props.reasoningEffort } : {}),
@@ -44,7 +47,10 @@ async function mountPicker(props: {
4447
});
4548
const host = document.createElement("div");
4649
document.body.append(host);
47-
const screen = await render(<CodexTraitsPicker threadId={threadId} />, { container: host });
50+
const screen = await render(
51+
<CodexTraitsPicker threadId={threadId} model={model} backendId={props.backendId ?? null} />,
52+
{ container: host },
53+
);
4854

4955
return {
5056
cleanup: async () => {
@@ -168,7 +174,10 @@ describe("CodexTraitsPicker", () => {
168174

169175
const host = document.createElement("div");
170176
document.body.append(host);
171-
const screen = await render(<CodexTraitsPicker threadId={threadId} />, { container: host });
177+
const screen = await render(
178+
<CodexTraitsPicker threadId={threadId} model="gpt-5.4" backendId={null} />,
179+
{ container: host },
180+
);
172181

173182
try {
174183
await useComposerDraftStore.persist.rehydrate();
@@ -187,4 +196,43 @@ describe("CodexTraitsPicker", () => {
187196
host.remove();
188197
}
189198
});
199+
200+
it("hides fast mode controls when the model does not support priority service tier", async () => {
201+
const mounted = await mountPicker({
202+
fastModeEnabled: false,
203+
model: "gpt-5.3-codex",
204+
});
205+
206+
try {
207+
await page.getByRole("button").click();
208+
209+
await vi.waitFor(() => {
210+
const text = document.body.textContent ?? "";
211+
expect(text).toContain("Reasoning");
212+
expect(text).not.toContain("Fast Mode");
213+
});
214+
} finally {
215+
await mounted.cleanup();
216+
}
217+
});
218+
219+
it("hides fast mode controls when a non-OpenAI codex backend is selected", async () => {
220+
const mounted = await mountPicker({
221+
fastModeEnabled: false,
222+
model: "gpt-5.4",
223+
backendId: "ollama",
224+
});
225+
226+
try {
227+
await page.getByRole("button").click();
228+
229+
await vi.waitFor(() => {
230+
const text = document.body.textContent ?? "";
231+
expect(text).toContain("Reasoning");
232+
expect(text).not.toContain("Fast Mode");
233+
});
234+
} finally {
235+
await mounted.cleanup();
236+
}
237+
});
190238
});

0 commit comments

Comments
 (0)