fabro/lib/crates/fabro-workflows/src/handler
brynary-fabro[bot] 1349ee14e2 Fix: Default provider should respect configured API keys (#103)
This PR fixes a bug where users with only OpenAI or Gemini API keys
configured would hit errors when running commands without an explicit
`--provider` flag, because the system hardcoded `Provider::Anthropic` as
the fallback in multiple places.

The core change introduces `Provider::default_from_env()`, which checks
which providers have API keys configured and selects the best available
one using a precedence order (Anthropic → OpenAI → Gemini), falling back
to Anthropic if none are found. A testable `default_with()` helper
enables thorough unit testing of the selection logic without relying on
environment variables. A companion `catalog::default_model_from_env()`
function pairs the provider selection with the correct default model
lookup.

All six call sites across the CLI, API server, workflow handler, and
agent that previously hardcoded `Provider::Anthropic` or used
`catalog::default_model()` without considering available keys have been
updated to use the new environment-aware defaults. The catalog functions
also now normalize provider aliases (e.g. `"open_ai"` → `"openai"`) to
prevent silent lookup misses.

### Fabro Details

<details>
<summary>Ran 10 stages in 28m 34s for $6.23</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 1m 8s | – | 0 |
| preflight_lint | 13s | – | 0 |
| implement | 4m 40s | $0.79 | 0 |
| simplify_opus | 7m 23s | $1.84 | 0 |
| simplify_gemini | 6m 24s | $1.44 | 0 |
| simplify_gpt | 7m 56s | $2.16 | 0 |
| verify | 14s | – | 0 |
| fmt | 1s | – | 0 |
| **Total** | **28m 34s** | **$6.23** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (13 nodes and 16
edges)</summary>

```dot
digraph ImplementAndSimplify {
    graph [
        goal="Implement and simplify",
        model_stylesheet="
            * { backend: api; model: claude-opus-4-6;}
        "
    ]
    rankdir=LR

    start [shape=Mdiamond, label="Start"]
    exit  [shape=Msquare, label="Exit"]

    toolchain         [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0]
    preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check -q --workspace 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -q --workspace -- -D warnings 2>&1", max_retries=0]
    fix_lints         [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
    implement         [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD."]
    simplify_opus     [label="Simplify (Opus)", prompt="@prompts/simplify.md"]
    simplify_gemini   [label="Simplify (Gemini)", prompt="@prompts/simplify.md", model="gemini-3.1-pro-preview-customtools"]
    simplify_gpt      [label="Simplify (GPT-54)", prompt="@prompts/simplify.md", model="gpt-54"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -q --workspace -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 2>&1", goal_gate=true, retry_target="fixup"]
    fixup             [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all clippy lint warnings and test failures.", max_visits=3]
    fmt               [label="Format", shape=parallelogram, script="cargo fmt --all 2>&1", max_retries=0]

    start -> toolchain
    toolchain -> preflight_compile [condition="outcome=success"]
    toolchain -> exit
    preflight_compile -> preflight_lint [condition="outcome=success"]
    preflight_compile -> exit
    preflight_lint -> implement [condition="outcome=success"]
    preflight_lint -> fix_lints
    fix_lints -> preflight_lint
    implement -> simplify_opus -> simplify_gemini -> simplify_gpt -> verify
    verify -> fmt   [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
    fmt -> exit
}

```

</details>

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Fabro <fabro@fabro.sh>
2026-03-19 12:30:17 -04:00
..
agent.rs Move workflow CLI ownership into fabro-cli 2026-03-18 09:31:11 -04:00
command.rs Extract fabro-graphviz crate from fabro-workflows 2026-03-17 11:20:55 -04:00
conditional.rs Extract fabro-graphviz crate from fabro-workflows 2026-03-17 11:20:55 -04:00
exit.rs Extract fabro-graphviz crate from fabro-workflows 2026-03-17 11:20:55 -04:00
fan_in.rs Extract fabro-graphviz crate from fabro-workflows 2026-03-17 11:20:55 -04:00
human.rs Extract fabro-interview crate from fabro-workflows 2026-03-17 13:53:36 -04:00
manager_loop.rs Clean up fabro-validate extraction 2026-03-17 13:42:54 -04:00
mod.rs Extract fabro-hooks crate from fabro-workflows 2026-03-17 14:08:02 -04:00
parallel.rs Clean up fabro-hooks extraction 2026-03-17 14:12:26 -04:00
prompt.rs Fix: Default provider should respect configured API keys (#103) 2026-03-19 12:30:17 -04:00
start.rs Extract fabro-graphviz crate from fabro-workflows 2026-03-17 11:20:55 -04:00
wait.rs Extract fabro-graphviz crate from fabro-workflows 2026-03-17 11:20:55 -04:00