Skip to content

Commit 7c9dbfd

Browse files
Merge pull request #1572 from ksylvan/0704-OpenAI-Image-Generation-Tool
Add Image Generation Support to Fabric
2 parents fe5900a + d9260bd commit 7c9dbfd

File tree

10 files changed

+312
-82
lines changed

10 files changed

+312
-82
lines changed

README.md

Lines changed: 85 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@ Fabric is graciously supported by…
1515
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/danielmiessler/fabric)
1616

1717
<div align="center">
18-
<p class="align center">
1918
<h4><code>fabric</code> is an open-source framework for augmenting humans using AI.</h4>
20-
</p>
2119
</div>
2220

2321
[Updates](#updates)
@@ -41,9 +39,9 @@ Since the start of modern AI in late 2022 we've seen an **_extraordinary_** numb
4139

4240
It's all really exciting and powerful, but _it's not easy to integrate this functionality into our lives._
4341

44-
<p class="align center">
42+
<div class="align center">
4543
<h4>In other words, AI doesn't have a capabilities problem—it has an <em>integration</em> problem.</h4>
46-
</p>
44+
</div>
4745

4846
**Fabric was created to address this by creating and organizing the fundamental units of AI—the prompts themselves!**
4947

@@ -120,6 +118,8 @@ Keep in mind that many of these were recorded when Fabric was Python-based, so r
120118
> - Web search is available for both Anthropic and OpenAI providers
121119
> - Previous plugin-level search configurations have been removed in favor of the new flag-based approach.
122120
> - If you used the previous approach, consider cleaning up your `~/.config/fabric/.env` file, removing the unused `ANTHROPIC_WEB_SEARCH_TOOL_ENABLED` and `ANTHROPIC_WEB_SEARCH_TOOL_LOCATION` variables.
121+
> - Fabric now supports image generation using the `--image-file` flag with OpenAI models
122+
> - Image generation works with both text prompts and input images (via `--attachment`) for image editing tasks
123123
>
124124
>
125125
>June 17, 2025
@@ -292,88 +292,88 @@ yt() {
292292
293293
You can add the below code for the equivalent aliases inside PowerShell by running `notepad $PROFILE` inside a PowerShell window:
294294
295-
```powershell
296-
# Path to the patterns directory
297-
$patternsPath = Join-Path $HOME ".config/fabric/patterns"
298-
foreach ($patternDir in Get-ChildItem -Path $patternsPath -Directory) {
299-
$patternName = $patternDir.Name
300-
301-
# Dynamically define a function for each pattern
302-
$functionDefinition = @"
303-
function $patternName {
304-
[CmdletBinding()]
305-
param(
306-
[Parameter(ValueFromPipeline = `$true)]
307-
[string] `$InputObject,
308-
309-
[Parameter(ValueFromRemainingArguments = `$true)]
310-
[String[]] `$patternArgs
311-
)
312-
313-
begin {
314-
# Initialize an array to collect pipeline input
315-
`$collector = @()
316-
}
317-
318-
process {
319-
# Collect pipeline input objects
320-
if (`$InputObject) {
321-
`$collector += `$InputObject
295+
```powershell
296+
# Path to the patterns directory
297+
$patternsPath = Join-Path $HOME ".config/fabric/patterns"
298+
foreach ($patternDir in Get-ChildItem -Path $patternsPath -Directory) {
299+
$patternName = $patternDir.Name
300+
301+
# Dynamically define a function for each pattern
302+
$functionDefinition = @"
303+
function $patternName {
304+
[CmdletBinding()]
305+
param(
306+
[Parameter(ValueFromPipeline = `$true)]
307+
[string] `$InputObject,
308+
309+
[Parameter(ValueFromRemainingArguments = `$true)]
310+
[String[]] `$patternArgs
311+
)
312+
313+
begin {
314+
# Initialize an array to collect pipeline input
315+
`$collector = @()
322316
}
323-
}
324317
325-
end {
326-
# Join all pipeline input into a single string, separated by newlines
327-
`$pipelineContent = `$collector -join "`n"
318+
process {
319+
# Collect pipeline input objects
320+
if (`$InputObject) {
321+
`$collector += `$InputObject
322+
}
323+
}
328324
329-
# If there's pipeline input, include it in the call to fabric
330-
if (`$pipelineContent) {
331-
`$pipelineContent | fabric --pattern $patternName `$patternArgs
332-
} else {
333-
# No pipeline input; just call fabric with the additional args
334-
fabric --pattern $patternName `$patternArgs
325+
end {
326+
# Join all pipeline input into a single string, separated by newlines
327+
`$pipelineContent = `$collector -join "`n"
328+
329+
# If there's pipeline input, include it in the call to fabric
330+
if (`$pipelineContent) {
331+
`$pipelineContent | fabric --pattern $patternName `$patternArgs
332+
} else {
333+
# No pipeline input; just call fabric with the additional args
334+
fabric --pattern $patternName `$patternArgs
335+
}
335336
}
336337
}
337-
}
338-
"@
339-
# Add the function to the current session
340-
Invoke-Expression $functionDefinition
341-
}
338+
"@
339+
# Add the function to the current session
340+
Invoke-Expression $functionDefinition
341+
}
342342
343-
# Define the 'yt' function as well
344-
function yt {
345-
[CmdletBinding()]
346-
param(
347-
[Parameter()]
348-
[Alias("timestamps")]
349-
[switch]$t,
350-
351-
[Parameter(Position = 0, ValueFromPipeline = $true)]
352-
[string]$videoLink
353-
)
354-
355-
begin {
356-
$transcriptFlag = "--transcript"
357-
if ($t) {
358-
$transcriptFlag = "--transcript-with-timestamps"
343+
# Define the 'yt' function as well
344+
function yt {
345+
[CmdletBinding()]
346+
param(
347+
[Parameter()]
348+
[Alias("timestamps")]
349+
[switch]$t,
350+
351+
[Parameter(Position = 0, ValueFromPipeline = $true)]
352+
[string]$videoLink
353+
)
354+
355+
begin {
356+
$transcriptFlag = "--transcript"
357+
if ($t) {
358+
$transcriptFlag = "--transcript-with-timestamps"
359+
}
359360
}
360-
}
361361
362-
process {
363-
if (-not $videoLink) {
364-
Write-Error "Usage: yt [-t | --timestamps] youtube-link"
365-
return
362+
process {
363+
if (-not $videoLink) {
364+
Write-Error "Usage: yt [-t | --timestamps] youtube-link"
365+
return
366+
}
366367
}
367-
}
368368
369-
end {
370-
if ($videoLink) {
371-
# Execute and allow output to flow through the pipeline
372-
fabric -y $videoLink $transcriptFlag
369+
end {
370+
if ($videoLink) {
371+
# Execute and allow output to flow through the pipeline
372+
fabric -y $videoLink $transcriptFlag
373+
}
373374
}
374375
}
375-
}
376-
```
376+
```
377377
378378
This also creates a `yt` alias that allows you to use `yt https://www.youtube.com/watch?v=4b0iet22VIk` to get transcripts, comments, and metadata.
379379
@@ -493,7 +493,6 @@ fabric -h
493493
```
494494
495495
```plaintext
496-
497496
Usage:
498497
fabric [OPTIONS]
499498
@@ -508,7 +507,9 @@ Application Options:
508507
-T, --topp= Set top P (default: 0.9)
509508
-s, --stream Stream
510509
-P, --presencepenalty= Set presence penalty (default: 0.0)
511-
-r, --raw Use the defaults of the model without sending chat options (like temperature etc.) and use the user role instead of the system role for patterns.
510+
-r, --raw Use the defaults of the model without sending chat options (like
511+
temperature etc.) and use the user role instead of the system role for
512+
patterns.
512513
-F, --frequencypenalty= Set frequency penalty (default: 0.0)
513514
-l, --listpatterns List all patterns
514515
-L, --listmodels List all available models
@@ -522,9 +523,12 @@ Application Options:
522523
--output-session Output the entire session (also a temporary one) to the output file
523524
-n, --latest= Number of latest patterns to list (default: 0)
524525
-d, --changeDefaultModel Change default model
525-
-y, --youtube= YouTube video or play list "URL" to grab transcript, comments from it and send to chat or print it put to the console and store it in the output file
526+
-y, --youtube= YouTube video or play list "URL" to grab transcript, comments from it
527+
and send to chat or print it put to the console and store it in the
528+
output file
526529
--playlist Prefer playlist over video if both ids are present in the URL
527-
--transcript Grab transcript from YouTube video and send to chat (it is used per default).
530+
--transcript Grab transcript from YouTube video and send to chat (it is used per
531+
default).
528532
--transcript-with-timestamps Grab transcript from YouTube video with timestamps and send to chat
529533
--comments Grab comments from YouTube video and send to chat
530534
--metadata Output video metadata
@@ -552,6 +556,9 @@ Application Options:
552556
--liststrategies List all strategies
553557
--listvendors List all vendors
554558
--shell-complete-list Output raw list without headers/formatting (for shell completion)
559+
--search Enable web search tool for supported models (Anthropic, OpenAI)
560+
--search-location= Set location for web search results (e.g., 'America/Los_Angeles')
561+
--image-file= Save generated image to specified file path (e.g., 'output.png')
555562
556563
Help Options:
557564
-h, --help Show this help message

cli/flags.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ type Flags struct {
7676
ShellCompleteOutput bool `long:"shell-complete-list" description:"Output raw list without headers/formatting (for shell completion)"`
7777
Search bool `long:"search" description:"Enable web search tool for supported models (Anthropic, OpenAI)"`
7878
SearchLocation string `long:"search-location" description:"Set location for web search results (e.g., 'America/Los_Angeles')"`
79+
ImageFile string `long:"image-file" description:"Save generated image to specified file path (e.g., 'output.png')"`
7980
}
8081

8182
var debug = false
@@ -267,6 +268,7 @@ func (o *Flags) BuildChatOptions() (ret *common.ChatOptions) {
267268
ModelContextLength: o.ModelContextLength,
268269
Search: o.Search,
269270
SearchLocation: o.SearchLocation,
271+
ImageFile: o.ImageFile,
270272
}
271273
return
272274
}

common/domain.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type ChatOptions struct {
2828
MaxTokens int
2929
Search bool
3030
SearchLocation string
31+
ImageFile string
3132
}
3233

3334
// NormalizeMessages remove empty messages and ensure messages order user-assist-user

completions/_fabric

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ _fabric() {
9696
'(--api-key)--api-key[API key used to secure server routes]:api-key:' \
9797
'(--config)--config[Path to YAML config file]:config file:_files -g "*.yaml *.yml"' \
9898
'(--version)--version[Print current version]' \
99+
'(--search)--search[Enable web search tool for supported models (Anthropic, OpenAI)]' \
100+
'(--search-location)--search-location[Set location for web search results]:location:' \
101+
'(--image-file)--image-file[Save generated image to specified file path]:image file:_files -g "*.png *.jpg *.jpeg *.gif *.bmp"' \
99102
'(--listextensions)--listextensions[List all registered extensions]' \
100103
'(--addextension)--addextension[Register a new extension from config file path]:config file:_files -g "*.yaml *.yml"' \
101104
'(--rmextension)--rmextension[Remove a registered extension by name]:extension:_fabric_extensions' \

completions/fabric.bash

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ _fabric() {
1313
_get_comp_words_by_ref -n : cur prev words cword
1414

1515
# Define all possible options/flags
16-
local opts="--pattern -p --variable -v --context -C --session --attachment -a --setup -S --temperature -t --topp -T --stream -s --presencepenalty -P --raw -r --frequencypenalty -F --listpatterns -l --listmodels -L --listcontexts -x --listsessions -X --updatepatterns -U --copy -c --model -m --modelContextLength --output -o --output-session --latest -n --changeDefaultModel -d --youtube -y --playlist --transcript --transcript-with-timestamps --comments --metadata --language -g --scrape_url -u --scrape_question -q --seed -e --wipecontext -w --wipesession -W --printcontext --printsession --readability --input-has-vars --dry-run --serve --serveOllama --address --api-key --config --version --listextensions --addextension --rmextension --strategy --liststrategies --listvendors --shell-complete-list --help -h"
16+
local opts="--pattern -p --variable -v --context -C --session --attachment -a --setup -S --temperature -t --topp -T --stream -s --presencepenalty -P --raw -r --frequencypenalty -F --listpatterns -l --listmodels -L --listcontexts -x --listsessions -X --updatepatterns -U --copy -c --model -m --modelContextLength --output -o --output-session --latest -n --changeDefaultModel -d --youtube -y --playlist --transcript --transcript-with-timestamps --comments --metadata --language -g --scrape_url -u --scrape_question -q --seed -e --wipecontext -w --wipesession -W --printcontext --printsession --readability --input-has-vars --dry-run --serve --serveOllama --address --api-key --config --search --search-location --image-file --version --listextensions --addextension --rmextension --strategy --liststrategies --listvendors --shell-complete-list --help -h"
1717

1818
# Helper function for dynamic completions
1919
_fabric_get_list() {
@@ -63,12 +63,12 @@ _fabric() {
6363
return 0
6464
;;
6565
# Options requiring file/directory paths
66-
-a | --attachment | -o | --output | --config | --addextension)
66+
-a | --attachment | -o | --output | --config | --addextension | --image-file)
6767
_filedir
6868
return 0
6969
;;
7070
# Options requiring simple arguments (no specific completion logic here)
71-
-v | --variable | -t | --temperature | -T | --topp | -P | --presencepenalty | -F | --frequencypenalty | --modelContextLength | -n | --latest | -y | --youtube | -g | --language | -u | --scrape_url | -q | --scrape_question | -e | --seed | --address | --api-key)
71+
-v | --variable | -t | --temperature | -T | --topp | -P | --presencepenalty | -F | --frequencypenalty | --modelContextLength | -n | --latest | -y | --youtube | -g | --language | -u | --scrape_url | -q | --scrape_question | -e | --seed | --address | --api-key | --search-location)
7272
# No specific completion suggestions, user types the value
7373
return 0
7474
;;

completions/fabric.fish

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ complete -c fabric -l printsession -d "Print session" -a "(__fabric_get_sessions
6060
complete -c fabric -l address -d "The address to bind the REST API (default: :8080)"
6161
complete -c fabric -l api-key -d "API key used to secure server routes"
6262
complete -c fabric -l config -d "Path to YAML config file" -r -a "*.yaml *.yml"
63+
complete -c fabric -l search-location -d "Set location for web search results (e.g., 'America/Los_Angeles')"
64+
complete -c fabric -l image-file -d "Save generated image to specified file path (e.g., 'output.png')" -r -a "*.png *.jpg *.jpeg *.gif *.bmp"
6365
complete -c fabric -l addextension -d "Register a new extension from config file path" -r -a "*.yaml *.yml"
6466
complete -c fabric -l rmextension -d "Remove a registered extension by name" -a "(__fabric_get_extensions)"
6567
complete -c fabric -l strategy -d "Choose a strategy from the available strategies" -a "(__fabric_get_strategies)"
@@ -84,6 +86,7 @@ complete -c fabric -l metadata -d "Output video metadata"
8486
complete -c fabric -l readability -d "Convert HTML input into a clean, readable view"
8587
complete -c fabric -l input-has-vars -d "Apply variables to user input"
8688
complete -c fabric -l dry-run -d "Show what would be sent to the model without actually sending it"
89+
complete -c fabric -l search -d "Enable web search tool for supported models (Anthropic, OpenAI)"
8790
complete -c fabric -l serve -d "Serve the Fabric Rest API"
8891
complete -c fabric -l serveOllama -d "Serve the Fabric Rest API with ollama endpoints"
8992
complete -c fabric -l version -d "Print current version"

plugins/ai/dryrun/dryrun.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ func (c *Client) formatOptions(opts *common.ChatOptions) string {
8282
builder.WriteString(fmt.Sprintf("SearchLocation: %s\n", opts.SearchLocation))
8383
}
8484
}
85+
if opts.ImageFile != "" {
86+
builder.WriteString(fmt.Sprintf("ImageFile: %s\n", opts.ImageFile))
87+
}
8588

8689
return builder.String()
8790
}

plugins/ai/openai/openai.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ func (o *Client) sendResponses(ctx context.Context, msgs []*chat.ChatCompletionM
134134
if resp, err = o.ApiClient.Responses.New(ctx, req); err != nil {
135135
return
136136
}
137+
138+
// Extract and save images if requested
139+
if err = o.extractAndSaveImages(resp, opts); err != nil {
140+
return
141+
}
142+
137143
ret = o.extractText(resp)
138144
return
139145
}
@@ -183,6 +189,9 @@ func (o *Client) buildResponseParams(
183189
},
184190
}
185191

192+
// Add tools if enabled
193+
var tools []responses.ToolUnionParam
194+
186195
// Add web search tool if enabled
187196
if opts.Search {
188197
webSearchTool := responses.ToolParamOfWebSearchPreview("web_search_preview")
@@ -195,7 +204,14 @@ func (o *Client) buildResponseParams(
195204
}
196205
}
197206

198-
ret.Tools = []responses.ToolUnionParam{webSearchTool}
207+
tools = append(tools, webSearchTool)
208+
}
209+
210+
// Add image generation tool if needed
211+
tools = o.addImageGenerationTool(opts, tools)
212+
213+
if len(tools) > 0 {
214+
ret.Tools = tools
199215
}
200216

201217
if !opts.Raw {

0 commit comments

Comments
 (0)