| author | |
| committer | |
| log | f5f98cf0ae48a14b524480a0942f9ce37190656c |
| tree | b09dc4a52ee5ed6d52c92f06b39bbaa19ed6858f |
| parent | 37ac970e289f6a79940c00e17c5a58114d94d4cf |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com>4 files changed, 30 insertions(+), 15 deletions(-)
apps/web/src/components/chat/ChatComposer.tsx+7-2| ... | ... | @@ -78,6 +78,7 @@ import { ComposerPlanFollowUpBanner } from "./ComposerPlanFollowUpBanner"; |
| 78 | 78 | import { resolveComposerMenuActiveItemId } from "./composerMenuHighlight"; |
| 79 | 79 | import { searchSlashCommandItems } from "./composerSlashCommandSearch"; |
| 80 | 80 | import { |
| 81 | getComposerPromptInjectionState, | |
| 81 | 82 | getComposerProviderState, |
| 82 | 83 | renderProviderTraitsMenuContent, |
| 83 | 84 | renderProviderTraitsPicker, |
| ... | ... | @@ -791,18 +792,22 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 791 | 792 | [selectedProviderEntry], |
| 792 | 793 | ); |
| 793 | 794 | |
| 795 | const composerPromptInjectionState = useMemo( | |
| 796 | () => getComposerPromptInjectionState(prompt), | |
| 797 | [prompt], | |
| 798 | ); | |
| 794 | 799 | const composerProviderState = useMemo( |
| 795 | 800 | () => |
| 796 | 801 | getComposerProviderState({ |
| 797 | 802 | provider: selectedProvider, |
| 798 | 803 | model: selectedModel, |
| 799 | 804 | models: selectedProviderModels, |
| 800 | prompt, | |
| 805 | promptInjectionState: composerPromptInjectionState, | |
| 801 | 806 | modelOptions: composerModelOptions?.[selectedInstanceId], |
| 802 | 807 | }), |
| 803 | 808 | [ |
| 804 | 809 | composerModelOptions, |
| 805 | prompt, | |
| 810 | composerPromptInjectionState, | |
| 806 | 811 | selectedInstanceId, |
| 807 | 812 | selectedModel, |
| 808 | 813 | selectedProvider, |
apps/web/src/components/chat/composerProviderState.test.tsx+14-8| ... | ... | @@ -6,6 +6,7 @@ import { |
| 6 | 6 | type ServerProviderModel, |
| 7 | 7 | } from "@t3tools/contracts"; |
| 8 | 8 | import { |
| 9 | getComposerPromptInjectionState, | |
| 9 | 10 | getComposerProviderState, |
| 10 | 11 | renderProviderTraitsMenuContent, |
| 11 | 12 | renderProviderTraitsPicker, |
| ... | ... | @@ -61,6 +62,13 @@ const ULTRATHINK_FRAME_CLASSES = { |
| 61 | 62 | } as const; |
| 62 | 63 | |
| 63 | 64 | describe("getComposerProviderState", () => { |
| 65 | it("derives a stable prompt injection state for ordinary prompt edits", () => { | |
| 66 | expect(getComposerPromptInjectionState("Investigate this failure")).toBe("none"); | |
| 67 | expect(getComposerPromptInjectionState("Ultrathink:\nInvestigate this failure")).toBe( | |
| 68 | "ultrathink", | |
| 69 | ); | |
| 70 | }); | |
| 71 | ||
| 64 | 72 | it("returns descriptor defaults when no selections are provided", () => { |
| 65 | 73 | const state = getComposerProviderState({ |
| 66 | 74 | provider: PROVIDER, |
| ... | ... | @@ -71,7 +79,6 @@ describe("getComposerProviderState", () => { |
| 71 | 79 | { id: "high", label: "High", isDefault: true }, |
| 72 | 80 | ]), |
| 73 | 81 | ]), |
| 74 | prompt: "", | |
| 75 | 82 | modelOptions: undefined, |
| 76 | 83 | }); |
| 77 | 84 | |
| ... | ... | @@ -93,7 +100,6 @@ describe("getComposerProviderState", () => { |
| 93 | 100 | ]), |
| 94 | 101 | booleanDescriptor("fastMode"), |
| 95 | 102 | ]), |
| 96 | prompt: "", | |
| 97 | 103 | modelOptions: selections(["effort", "low"], ["fastMode", true]), |
| 98 | 104 | }); |
| 99 | 105 | |
| ... | ... | @@ -112,7 +118,6 @@ describe("getComposerProviderState", () => { |
| 112 | 118 | selectDescriptor("effort", [{ id: "high", label: "High", isDefault: true }]), |
| 113 | 119 | booleanDescriptor("fastMode"), |
| 114 | 120 | ]), |
| 115 | prompt: "", | |
| 116 | 121 | modelOptions: selections(["effort", "high"], ["fastMode", false]), |
| 117 | 122 | }); |
| 118 | 123 | |
| ... | ... | @@ -126,7 +131,6 @@ describe("getComposerProviderState", () => { |
| 126 | 131 | provider: PROVIDER, |
| 127 | 132 | model: MODEL, |
| 128 | 133 | models: modelWith([booleanDescriptor("thinking")]), |
| 129 | prompt: "", | |
| 130 | 134 | modelOptions: selections(["effort", "max"], ["thinking", false]), |
| 131 | 135 | }); |
| 132 | 136 | |
| ... | ... | @@ -152,7 +156,6 @@ describe("getComposerProviderState", () => { |
| 152 | 156 | { id: "plan", label: "Plan" }, |
| 153 | 157 | ]), |
| 154 | 158 | ]), |
| 155 | prompt: "", | |
| 156 | 159 | modelOptions: selections(["agent", "plan"]), |
| 157 | 160 | }); |
| 158 | 161 | |
| ... | ... | @@ -167,7 +170,6 @@ describe("getComposerProviderState", () => { |
| 167 | 170 | provider: PROVIDER, |
| 168 | 171 | model: MODEL, |
| 169 | 172 | models: modelWith([]), |
| 170 | prompt: "", | |
| 171 | 173 | modelOptions: selections(["anything", "value"]), |
| 172 | 174 | }); |
| 173 | 175 | |
| ... | ... | @@ -193,7 +195,9 @@ describe("getComposerProviderState", () => { |
| 193 | 195 | ["ultrathink"], |
| 194 | 196 | ), |
| 195 | 197 | ]), |
| 196 | prompt: "Ultrathink:\nInvestigate this failure", | |
| 198 | promptInjectionState: getComposerPromptInjectionState( | |
| 199 | "Ultrathink:\nInvestigate this failure", | |
| 200 | ), | |
| 197 | 201 | modelOptions: selections(["effort", "medium"]), |
| 198 | 202 | }); |
| 199 | 203 | |
| ... | ... | @@ -212,7 +216,9 @@ describe("getComposerProviderState", () => { |
| 212 | 216 | models: modelWith([ |
| 213 | 217 | selectDescriptor("effort", [{ id: "high", label: "High", isDefault: true }]), |
| 214 | 218 | ]), |
| 215 | prompt: "Ultrathink:\nInvestigate this failure", | |
| 219 | promptInjectionState: getComposerPromptInjectionState( | |
| 220 | "Ultrathink:\nInvestigate this failure", | |
| 221 | ), | |
| 216 | 222 | modelOptions: undefined, |
| 217 | 223 | }); |
| 218 | 224 |
apps/web/src/components/chat/composerProviderState.tsx+9-3| ... | ... | @@ -21,10 +21,12 @@ export type ComposerProviderStateInput = { |
| 21 | 21 | provider: ProviderDriverKind; |
| 22 | 22 | model: string; |
| 23 | 23 | models: ReadonlyArray<ServerProviderModel>; |
| 24 | prompt: string; | |
| 24 | promptInjectionState?: ComposerPromptInjectionState; | |
| 25 | 25 | modelOptions: ReadonlyArray<ProviderOptionSelection> | null | undefined; |
| 26 | 26 | }; |
| 27 | 27 | |
| 28 | export type ComposerPromptInjectionState = "none" | "ultrathink"; | |
| 29 | ||
| 28 | 30 | export type ComposerProviderState = { |
| 29 | 31 | provider: ProviderDriverKind; |
| 30 | 32 | promptEffort: string | null; |
| ... | ... | @@ -46,8 +48,12 @@ type TraitsRenderInput = { |
| 46 | 48 | onPromptChange: (prompt: string) => void; |
| 47 | 49 | }; |
| 48 | 50 | |
| 51 | export function getComposerPromptInjectionState(prompt: string): ComposerPromptInjectionState { | |
| 52 | return isClaudeUltrathinkPrompt(prompt) ? "ultrathink" : "none"; | |
| 53 | } | |
| 54 | ||
| 49 | 55 | export function getComposerProviderState(input: ComposerProviderStateInput): ComposerProviderState { |
| 50 | const { provider, model, models, prompt, modelOptions } = input; | |
| 56 | const { provider, model, models, modelOptions, promptInjectionState = "none" } = input; | |
| 51 | 57 | const caps = getProviderModelCapabilities(models, model, provider); |
| 52 | 58 | const descriptors = getProviderOptionDescriptors({ caps, selections: modelOptions }); |
| 53 | 59 | const primarySelectDescriptor = descriptors.find( |
| ... | ... | @@ -58,7 +64,7 @@ export function getComposerProviderState(input: ComposerProviderStateInput): Com |
| 58 | 64 | const promptEffort = typeof primaryValue === "string" ? primaryValue : null; |
| 59 | 65 | const ultrathinkActive = |
| 60 | 66 | (primarySelectDescriptor?.promptInjectedValues?.length ?? 0) > 0 && |
| 61 | isClaudeUltrathinkPrompt(prompt); | |
| 67 | promptInjectionState === "ultrathink"; | |
| 62 | 68 | |
| 63 | 69 | return { |
| 64 | 70 | provider, |
apps/web/src/modelSelection.ts-2| ... | ... | @@ -303,7 +303,6 @@ export function resolveAppModelSelectionState( |
| 303 | 303 | provider, |
| 304 | 304 | model, |
| 305 | 305 | models: entry.models, |
| 306 | prompt: "", | |
| 307 | 306 | modelOptions: selectedEntry ? selection.options : undefined, |
| 308 | 307 | }); |
| 309 | 308 | |
| ... | ... | @@ -321,7 +320,6 @@ export function resolveAppModelSelectionState( |
| 321 | 320 | provider, |
| 322 | 321 | model, |
| 323 | 322 | models: getProviderModels(providers, provider), |
| 324 | prompt: "", | |
| 325 | 323 | modelOptions: keptSelectedProvider ? selection.options : undefined, |
| 326 | 324 | }); |
| 327 | 325 |