Skip to content

feat: edit model and extensions of a recipe from GUI#6804

Merged
zanesq merged 10 commits intoblock:mainfrom
Abhijay007:feat/editModelGUI
Feb 11, 2026
Merged

feat: edit model and extensions of a recipe from GUI#6804
zanesq merged 10 commits intoblock:mainfrom
Abhijay007:feat/editModelGUI

Conversation

@Abhijay007
Copy link
Collaborator

Closes #6652

Summary

Type of Change

  • Feature

AI Assistance

  • This PR was created or reviewed with AI assistance

Testing

Screenshots/Demos (for UX changes)

Before:

After:

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds GUI support for editing a recipe’s model/provider settings and enabled extensions, so users don’t need to manually edit recipe YAML.

Changes:

  • Extend the recipe form schema to include model, provider, and extensions.
  • Add new UI components for selecting provider/model and toggling recipe extensions.
  • Wire new fields into the recipe form and ensure they’re included in the recipe payload on save.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
ui/desktop/src/components/recipes/shared/recipeFormSchema.ts Adds optional model, provider, and extensions to the form schema.
ui/desktop/src/components/recipes/shared/RecipeModelSelector.tsx New selector UI for choosing provider + model from configured providers.
ui/desktop/src/components/recipes/shared/RecipeExtensionSelector.tsx New selector UI for choosing extensions from the configured extension list.
ui/desktop/src/components/recipes/shared/RecipeFormFields.tsx Adds the new selectors to the Advanced Options section and updates “has advanced data” detection.
ui/desktop/src/components/recipes/CreateEditRecipeModal.tsx Persists selected model/provider/extensions into the saved recipe payload.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

@zanesq
Copy link
Collaborator

zanesq commented Jan 29, 2026

UI looks good! From initial testing I think I found a bug where editing a recipe that already has extensions defined isn't showing the extensions as enabled. Can you take a look?

Goose also confirmed it but I haven't checked its work

🐛 Bug Found: Extensions Not Loading from Existing Recipe

The Problem

In RecipeExtensionSelector.tsx, the component gets allExtensions from useConfig():

const { extensionsList: allExtensions } = useConfig();

This extensionsList is of type FixedExtensionEntry[] - which are the currently configured extensions in the user's settings.

The component then checks if a recipe's extension is selected by matching names:

const selectedExtensionNames = useMemo(
  () => new Set(selectedExtensions.map((ext) => ext.name)),
  [selectedExtensions]
);

And displays only extensions from allExtensions (the user's current config):

const sortedExtensions = useMemo(() => {
  return [...filteredExtensions].sort((a, b) => {
    const aSelected = selectedExtensionNames.has(a.name);
    // ...
  });
}, [filteredExtensions, selectedExtensionNames]);

The Bug

If a recipe has extensions that are NOT in the user's current extensionsList, those extensions:

  1. Won't appear in the UI list at all
  2. The selectedExtensions count will show correctly (e.g., "3 extensions selected")
  3. But the switches won't be visible because the extensions aren't in allExtensions

This happens because:

  • The recipe stores ExtensionConfig[]
  • The UI only displays extensions from extensionsList (user's current config)
  • Recipe extensions that don't exist in user's config are silently ignored in the display

How to Reproduce

  1. Create a recipe with extensions A, B, C
  2. Remove extension B from your settings
  3. Open the recipe to edit
  4. Extension B won't show as selected (even though it's in the recipe)

Suggested Fix

The component should merge the recipe's selectedExtensions with allExtensions to ensure all recipe extensions are displayed, even if they're not currently in the user's config. Something like:

const displayExtensions = useMemo(() => {
  // Start with all available extensions
  const extensionMap = new Map(allExtensions.map(ext => [ext.name, ext]));
  
  // Add any selected extensions that aren't in the current config
  selectedExtensions.forEach(ext => {
    if (!extensionMap.has(ext.name)) {
      extensionMap.set(ext.name, ext);
    }
  });
  
  return Array.from(extensionMap.values());
}, [allExtensions, selectedExtensions]);

Then use `displayExtensions` instead of `filteredExtensions` for filtering/sorting.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

@zanesq
Copy link
Collaborator

zanesq commented Feb 2, 2026

@Abhijay007 another thought I had is goose desktop actually respecting the model choice when the recipe is loaded? I thought models was a global thing now in the desktop. If its not working maybe we should remove model selection until it is supported?

@Abhijay007
Copy link
Collaborator Author

Abhijay007 commented Feb 2, 2026

@Abhijay007 another thought I had is goose desktop actually respecting the model choice when the recipe is loaded? I thought models was a global thing now in the desktop. If its not working maybe we should remove model selection until it is supported?

I added a PR for this yesterday : #6884

@zanesq
Copy link
Collaborator

zanesq commented Feb 2, 2026

Nice will take a look!

Copy link
Collaborator

@zanesq zanesq left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, only 2 more pieces of feedback to wrap this up:

  1. Can you add error handling in RecipeModelSelector so it shows a message that fetching models failed rather than swallowing the error?
  2. Can you align the extensions selected count message to the right side? Visually it gets in the way when its on the left.
Image

Copilot AI review requested due to automatic review settings February 11, 2026 20:38
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comment on lines +98 to +101
const filteredModelOptions = selectedProvider
? modelOptions.filter((group) => group.options[0]?.provider === selectedProvider)
: [];

Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filteredModelOptions becomes [] when selectedProvider is unset, which makes it impossible to view/select a model for recipes that only set settings.goose_model while relying on the default provider (this is supported by the CLI/session builder logic). Consider allowing model entry/selection when selectedProvider is undefined (e.g., fall back to a free-text model input, and treat a pre-filled selectedModel with no provider as custom).

Copilot uses AI. Check for mistakes.
Comment on lines +917 to +946
it('allows selecting extensions', async () => {
const user = userEvent.setup();
const TestComponent = () => {
const form = useForm({
defaultValues: {
title: 'Test Recipe',
description: 'Test',
instructions: 'Test',
prompt: 'Test',
activities: [],
parameters: [],
jsonSchema: '',
model: undefined,
provider: undefined,
extensions: undefined,
} as RecipeFormData,
onSubmit: async ({ value }) => {
console.log('Form submitted:', value);
},
});

return <RecipeFormFields form={form} />;
};

render(<TestComponent />);

await expandAdvancedSection(user);

expect(screen.getByText('Extensions (Optional)')).toBeInTheDocument();
});
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test name says it "allows selecting extensions", but it only asserts that the Extensions label renders, so it doesn’t validate the toggle/selection behavior; either drive the UI to toggle an extension and assert the selected count/value, or rename the test to reflect a render-only check.

Copilot uses AI. Check for mistakes.
Comment on lines +474 to +499
<form.Field name="provider">
{(providerField: FormFieldApi<string | undefined>) => (
<form.Field name="model">
{(modelField: FormFieldApi<string | undefined>) => (
<RecipeModelSelector
selectedProvider={providerField.state.value}
selectedModel={modelField.state.value}
onProviderChange={(provider) => providerField.handleChange(provider)}
onModelChange={(model) => modelField.handleChange(model)}
/>
)}
</form.Field>
)}
</form.Field>

{/* Extensions Field */}
<form.Field name="extensions">
{(field: FormFieldApi<ExtensionConfig[] | undefined>) => (
<RecipeExtensionSelector
selectedExtensions={field.state.value || []}
onExtensionsChange={(extensions) =>
field.handleChange(extensions.length > 0 ? extensions : undefined)
}
/>
)}
</form.Field>
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RecipeFormFields now always renders provider/model/extensions fields, but some submit flows (e.g., CreateRecipeFromSessionModal builds the Recipe payload without mapping these fields) will silently drop user selections; consider either plumbing these values through all consumers or making these selectors opt-in via props for contexts that don't persist them.

Suggested change
<form.Field name="provider">
{(providerField: FormFieldApi<string | undefined>) => (
<form.Field name="model">
{(modelField: FormFieldApi<string | undefined>) => (
<RecipeModelSelector
selectedProvider={providerField.state.value}
selectedModel={modelField.state.value}
onProviderChange={(provider) => providerField.handleChange(provider)}
onModelChange={(model) => modelField.handleChange(model)}
/>
)}
</form.Field>
)}
</form.Field>
{/* Extensions Field */}
<form.Field name="extensions">
{(field: FormFieldApi<ExtensionConfig[] | undefined>) => (
<RecipeExtensionSelector
selectedExtensions={field.state.value || []}
onExtensionsChange={(extensions) =>
field.handleChange(extensions.length > 0 ? extensions : undefined)
}
/>
)}
</form.Field>
{(() => {
const values = (form as any)?.state?.values ?? {};
const showProviderModelFields = 'provider' in values || 'model' in values;
if (!showProviderModelFields) {
return null;
}
return (
<form.Field name="provider">
{(providerField: FormFieldApi<string | undefined>) => (
<form.Field name="model">
{(modelField: FormFieldApi<string | undefined>) => (
<RecipeModelSelector
selectedProvider={providerField.state.value}
selectedModel={modelField.state.value}
onProviderChange={(provider) => providerField.handleChange(provider)}
onModelChange={(model) => modelField.handleChange(model)}
/>
)}
</form.Field>
)}
</form.Field>
);
})()}
{/* Extensions Field */}
{(() => {
const values = (form as any)?.state?.values ?? {};
const showExtensionsField = 'extensions' in values;
if (!showExtensionsField) {
return null;
}
return (
<form.Field name="extensions">
{(field: FormFieldApi<ExtensionConfig[] | undefined>) => (
<RecipeExtensionSelector
selectedExtensions={field.state.value || []}
onExtensionsChange={(extensions) =>
field.handleChange(extensions.length > 0 ? extensions : undefined)
}
/>
)}
</form.Field>
);
})()}

Copilot uses AI. Check for mistakes.
@zanesq
Copy link
Collaborator

zanesq commented Feb 11, 2026

@Abhijay007 why do we need the useMemos are they necessary? Also we have a extension selector already in the codebase. Wondering if you could abstract that out to reuse it? If not quick we can follow up on that in another PR later.

@Abhijay007
Copy link
Collaborator Author

@Abhijay007 why do we need the useMemos are they necessary? Also we have a extension selector already in the codebase. Wondering if you could abstract that out to reuse it? If not quick we can follow up on that in another PR later.

I will add another PR handling that refactor I think that would be better

@zanesq
Copy link
Collaborator

zanesq commented Feb 11, 2026

What about the useMemos? Do we need them? Don't want to add complexity if not needed.

Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>
Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>
Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>
Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>
Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>
Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>
Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>
Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>
… nits

Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>
Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>
@Abhijay007
Copy link
Collaborator Author

What about the useMemos? Do we need them? Don't want to add complexity if not needed.

removed it 👍

@zanesq zanesq added this pull request to the merge queue Feb 11, 2026
Merged via the queue into block:main with commit 153872a Feb 11, 2026
19 checks passed
lifeizhou-ap added a commit that referenced this pull request Feb 12, 2026
* main:
  fix text editor view broken (#7167)
  docs: White label guide (#6857)
  Add PATH detection back to developer extension (#7161)
  docs: pin version in ci/cd (#7168)
  Desktop: - No Custom Headers field for custom OpenAI-compatible providers  (#6681)
  feat: edit model and extensions of a recipe from GUI (#6804)
  feat: MCP support for agentic CLI providers (#6972)
jh-block added a commit that referenced this pull request Feb 12, 2026
* origin/main: (33 commits)
  fix: replace panic with proper error handling in get_tokenizer (#7175)
  Lifei/smoke test for developer (#7174)
  fix text editor view broken (#7167)
  docs: White label guide (#6857)
  Add PATH detection back to developer extension (#7161)
  docs: pin version in ci/cd (#7168)
  Desktop: - No Custom Headers field for custom OpenAI-compatible providers  (#6681)
  feat: edit model and extensions of a recipe from GUI (#6804)
  feat: MCP support for agentic CLI providers (#6972)
  docs: keyring fallback to secrets.yaml (#7165)
  feat: load provider/model specified inside the recipe config (#6884)
  fix ask-ai bot hitting tool call limits (#7162)
  fix flatpak icon (#7154)
  [docs] Skills Marketplace UI Improvements (#7158)
  More no-window flags (#7122)
  feat: Allow overriding default bat themes using environment variables (#7140)
  Make the system prompt smaller (#6991)
  Pre release script (#7145)
  Spelling (#7137)
  feat(mcp): upgrade rmcp to 0.15.0 and advertise MCP Apps UI extension capability (#6927)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Edit the model and extension of a recipe directly from the GUI.

2 participants