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