@@ -11,6 +11,7 @@ import (
1111 "github.com/stretchr/testify/require"
1212
1313 "github.com/docker/cagent/pkg/config"
14+ latest "github.com/docker/cagent/pkg/config/v2"
1415 "github.com/docker/cagent/pkg/environment"
1516)
1617
@@ -160,3 +161,153 @@ func collectExamples(t *testing.T) []string {
160161
161162 return files
162163}
164+
165+ func TestApplyModelOverrides (t * testing.T ) {
166+ tests := []struct {
167+ name string
168+ agents map [string ]latest.AgentConfig
169+ overrides []string
170+ expected map [string ]string // agent name -> expected model
171+ expectError bool
172+ errorMsg string
173+ }{
174+ {
175+ name : "global override" ,
176+ agents : map [string ]latest.AgentConfig {
177+ "root" : {Model : "openai/gpt-4" },
178+ "other" : {Model : "anthropic/claude-3" },
179+ },
180+ overrides : []string {"google/gemini-pro" },
181+ expected : map [string ]string {
182+ "root" : "google/gemini-pro" ,
183+ "other" : "google/gemini-pro" ,
184+ },
185+ },
186+ {
187+ name : "single per-agent override" ,
188+ agents : map [string ]latest.AgentConfig {
189+ "root" : {Model : "openai/gpt-4" },
190+ "other" : {Model : "anthropic/claude-3" },
191+ },
192+ overrides : []string {"other=google/gemini-pro" },
193+ expected : map [string ]string {
194+ "root" : "openai/gpt-4" ,
195+ "other" : "google/gemini-pro" ,
196+ },
197+ },
198+ {
199+ name : "multiple separate flags" ,
200+ agents : map [string ]latest.AgentConfig {
201+ "root" : {Model : "openai/gpt-4" },
202+ "other" : {Model : "anthropic/claude-3" },
203+ },
204+ overrides : []string {"root=openai/gpt-5" , "other=anthropic/claude-sonnet-4-0" },
205+ expected : map [string ]string {
206+ "root" : "openai/gpt-5" ,
207+ "other" : "anthropic/claude-sonnet-4-0" ,
208+ },
209+ },
210+ {
211+ name : "comma-separated format" ,
212+ agents : map [string ]latest.AgentConfig {
213+ "root" : {Model : "openai/gpt-4" },
214+ "other" : {Model : "anthropic/claude-3" },
215+ "third" : {Model : "google/gemini-pro" },
216+ },
217+ overrides : []string {"root=openai/gpt-5,other=anthropic/claude-sonnet-4-0" },
218+ expected : map [string ]string {
219+ "root" : "openai/gpt-5" ,
220+ "other" : "anthropic/claude-sonnet-4-0" ,
221+ "third" : "google/gemini-pro" ,
222+ },
223+ },
224+ {
225+ name : "mixed formats" ,
226+ agents : map [string ]latest.AgentConfig {
227+ "root" : {Model : "openai/gpt-4" },
228+ "other" : {Model : "anthropic/claude-3" },
229+ "third" : {Model : "google/gemini-pro" },
230+ "reviewer" : {Model : "openai/gpt-3.5-turbo" },
231+ },
232+ overrides : []string {"root=openai/gpt-5,other=anthropic/claude-4" , "reviewer=google/gemini-1.5-pro" },
233+ expected : map [string ]string {
234+ "root" : "openai/gpt-5" ,
235+ "other" : "anthropic/claude-4" ,
236+ "third" : "google/gemini-pro" ,
237+ "reviewer" : "google/gemini-1.5-pro" ,
238+ },
239+ },
240+ {
241+ name : "last override wins" ,
242+ agents : map [string ]latest.AgentConfig {
243+ "root" : {Model : "openai/gpt-4" },
244+ },
245+ overrides : []string {"root=openai/gpt-5" , "root=anthropic/claude-4" },
246+ expected : map [string ]string {
247+ "root" : "anthropic/claude-4" ,
248+ },
249+ },
250+ {
251+ name : "unknown agent error" ,
252+ agents : map [string ]latest.AgentConfig {
253+ "root" : {Model : "openai/gpt-4" },
254+ },
255+ overrides : []string {"nonexistent=openai/gpt-5" },
256+ expectError : true ,
257+ errorMsg : "unknown agent 'nonexistent'" ,
258+ },
259+ {
260+ name : "empty model spec error" ,
261+ agents : map [string ]latest.AgentConfig {
262+ "root" : {Model : "openai/gpt-4" },
263+ },
264+ overrides : []string {"root=" },
265+ expectError : true ,
266+ errorMsg : "empty model specification in override: root=" ,
267+ },
268+ {
269+ name : "empty global model spec is skipped" ,
270+ agents : map [string ]latest.AgentConfig {
271+ "root" : {Model : "openai/gpt-4" },
272+ },
273+ overrides : []string {"" },
274+ expected : map [string ]string {
275+ "root" : "openai/gpt-4" ,
276+ },
277+ },
278+ {
279+ name : "whitespace handling" ,
280+ agents : map [string ]latest.AgentConfig {
281+ "root" : {Model : "openai/gpt-4" },
282+ "other" : {Model : "anthropic/claude-3" },
283+ },
284+ overrides : []string {" root = openai/gpt-5 , other = anthropic/claude-4 " },
285+ expected : map [string ]string {
286+ "root" : "openai/gpt-5" ,
287+ "other" : "anthropic/claude-4" ,
288+ },
289+ },
290+ }
291+
292+ for _ , tt := range tests {
293+ t .Run (tt .name , func (t * testing.T ) {
294+ t .Parallel ()
295+
296+ cfg := & latest.Config {
297+ Agents : tt .agents ,
298+ Models : make (map [string ]latest.ModelConfig ),
299+ }
300+
301+ err := config .ApplyModelOverrides (cfg , tt .overrides )
302+
303+ if tt .expectError {
304+ require .ErrorContains (t , err , tt .errorMsg )
305+ } else {
306+ require .NoError (t , err )
307+ for agentName , expectedModel := range tt .expected {
308+ assert .Equal (t , expectedModel , cfg .Agents [agentName ].Model )
309+ }
310+ }
311+ })
312+ }
313+ }
0 commit comments