11import type { LoopSchemas } from "@posthog/api-client/loops" ;
2- import { useModelCatalog } from "@posthog/ui/features/agent-applications/hooks/useModelCatalog" ;
2+ import { GLM_MODEL_FLAG } from "@posthog/shared" ;
3+ import { useFeatureFlag } from "@posthog/ui/features/feature-flags/useFeatureFlag" ;
34import { SettingsOptionSelect } from "@posthog/ui/features/settings/SettingsOptionSelect" ;
45import { Flex } from "@radix-ui/themes" ;
56import { useMemo } from "react" ;
7+ import { useLoopModelConfigOptions } from "../hooks/useLoopModelConfigOptions" ;
8+ import {
9+ clampLoopReasoningEffort ,
10+ loopModelOptions ,
11+ loopReasoningEffortOptions ,
12+ } from "../loopModels" ;
613import { Field } from "./LoopFormPrimitives" ;
714
815const ADAPTER_OPTIONS : {
@@ -16,18 +23,6 @@ const ADAPTER_OPTIONS: {
1623const AUTO_REASONING_VALUE = "auto" ;
1724const DEFAULT_MODEL_VALUE = "__default__" ;
1825
19- const REASONING_EFFORT_OPTIONS : {
20- value : LoopSchemas . LoopReasoningEffortEnum | typeof AUTO_REASONING_VALUE ;
21- label : string ;
22- } [ ] = [
23- { value : AUTO_REASONING_VALUE , label : "Auto" } ,
24- { value : "low" , label : "Low" } ,
25- { value : "medium" , label : "Medium" } ,
26- { value : "high" , label : "High" } ,
27- { value : "xhigh" , label : "Extra high" } ,
28- { value : "max" , label : "Max" } ,
29- ] ;
30-
3126interface LoopModelFieldsProps {
3227 adapter : LoopSchemas . LoopRuntimeAdapterEnum ;
3328 model : string ;
@@ -44,9 +39,11 @@ interface LoopModelFieldsProps {
4439 * Static model configuration for a loop: model, adapter, and reasoning effort.
4540 * Loops have no live agent session, so the interactive
4641 * `UnifiedModelSelector`/`ReasoningLevelSelector` (which read a session's
47- * `SessionConfigOption`) don't apply here, so this presents the same choices
48- * as a dropdown against the served model catalog instead. The server validates
49- * the final value against the catalog in `process_task/utils.py`.
42+ * `SessionConfigOption`) don't apply here; instead this presents the same
43+ * per-adapter choices as the main create-task picker (see `loopModels.ts`),
44+ * so every selectable combo passes the server's validation in
45+ * `process_task/utils.py`. Adapter and model switches clamp a now-unsupported
46+ * reasoning effort back to Auto for the same reason.
5047 */
5148export function LoopModelFields ( {
5249 adapter,
@@ -57,34 +54,47 @@ export function LoopModelFields({
5754 onReasoningEffortChange,
5855 disabled,
5956} : LoopModelFieldsProps ) {
60- const { catalog } = useModelCatalog ( ) ;
57+ const glmEnabled = useFeatureFlag ( GLM_MODEL_FLAG ) ;
58+ const configOptions = useLoopModelConfigOptions ( adapter ) ;
6159
62- // Prefer the served catalog; fall back to the known level models while it
63- // loads or if the endpoint is down. Always keep the current value selectable
64- // so an existing loop's model never drops out of the list.
65- const modelOptions = useMemo ( ( ) => {
66- const ids = new Set < string > ( ) ;
67- for ( const entry of catalog . models ) {
68- ids . add ( entry . model ) ;
69- }
70- if ( ids . size === 0 ) {
71- for ( const level of Object . values ( catalog . levels ) ) {
72- for ( const id of level ) {
73- ids . add ( id ) ;
74- }
75- }
76- }
77- if ( model ) {
78- ids . add ( model ) ;
79- }
80- const catalogOptions = Array . from ( ids )
81- . sort ( ( a , b ) => a . localeCompare ( b ) )
82- . map ( ( id ) => ( { value : id , label : id } ) ) ;
83- return [
60+ const modelOptions = useMemo (
61+ ( ) => [
8462 { value : DEFAULT_MODEL_VALUE , label : "Default (recommended)" } ,
85- ...catalogOptions ,
86- ] ;
87- } , [ catalog , model ] ) ;
63+ ...loopModelOptions ( adapter , configOptions , {
64+ glmEnabled,
65+ pinnedModel : model ,
66+ } ) ,
67+ ] ,
68+ [ adapter , configOptions , glmEnabled , model ] ,
69+ ) ;
70+
71+ const reasoningOptions = useMemo (
72+ ( ) => [
73+ { value : AUTO_REASONING_VALUE , label : "Auto" } ,
74+ ...loopReasoningEffortOptions ( adapter , model ) ,
75+ ] ,
76+ [ adapter , model ] ,
77+ ) ;
78+
79+ const handleAdapterChange = ( value : string ) => {
80+ const nextAdapter = value as LoopSchemas . LoopRuntimeAdapterEnum ;
81+ onAdapterChange ( nextAdapter ) ;
82+ // Adapters have disjoint model catalogs, so a pinned model can't carry over.
83+ if ( model ) onModelChange ( "" ) ;
84+ const clamped = clampLoopReasoningEffort ( nextAdapter , "" , reasoningEffort ) ;
85+ if ( clamped !== reasoningEffort ) onReasoningEffortChange ( clamped ) ;
86+ } ;
87+
88+ const handleModelChange = ( value : string ) => {
89+ const nextModel = value === DEFAULT_MODEL_VALUE ? "" : value ;
90+ onModelChange ( nextModel ) ;
91+ const clamped = clampLoopReasoningEffort (
92+ adapter ,
93+ nextModel ,
94+ reasoningEffort ,
95+ ) ;
96+ if ( clamped !== reasoningEffort ) onReasoningEffortChange ( clamped ) ;
97+ } ;
8898
8999 return (
90100 < Flex direction = "column" gap = "4" >
@@ -96,9 +106,7 @@ export function LoopModelFields({
96106 value = { model || DEFAULT_MODEL_VALUE }
97107 options = { modelOptions }
98108 placeholder = "Default (recommended)"
99- onValueChange = { ( value ) =>
100- onModelChange ( value === DEFAULT_MODEL_VALUE ? "" : value )
101- }
109+ onValueChange = { handleModelChange }
102110 disabled = { disabled }
103111 size = "lg"
104112 ariaLabel = "Model"
@@ -110,9 +118,7 @@ export function LoopModelFields({
110118 < SettingsOptionSelect
111119 value = { adapter }
112120 options = { ADAPTER_OPTIONS }
113- onValueChange = { ( value ) =>
114- onAdapterChange ( value as LoopSchemas . LoopRuntimeAdapterEnum )
115- }
121+ onValueChange = { handleAdapterChange }
116122 disabled = { disabled }
117123 size = "lg"
118124 ariaLabel = "Adapter"
@@ -122,7 +128,8 @@ export function LoopModelFields({
122128 < Field label = "Reasoning effort" className = "min-w-[180px] flex-1" >
123129 < SettingsOptionSelect
124130 value = { reasoningEffort ?? AUTO_REASONING_VALUE }
125- options = { REASONING_EFFORT_OPTIONS }
131+ options = { reasoningOptions }
132+ placeholder = "Auto"
126133 onValueChange = { ( value ) =>
127134 onReasoningEffortChange (
128135 value === AUTO_REASONING_VALUE
0 commit comments