feat(imagegen): optional per-request generation settings #9

Merged
steve merged 1 commits from feat/imagegen-settings into main 2026-06-29 02:00:40 +00:00
Owner

Add optional, additive overrides to imagegen.RequestSteps, CFGScale, NegativePrompt, Sampler, Seed — with mirror options. nil/"" means "leave the backend's per-model default", so the v1 contract is unchanged for callers that don't set them. provider/llamaswap forwards them to sd-server as steps/cfg_scale/negative_prompt/sample_method/seed (omitempty).

Enables callers (mort drawbots) to override only what they explicitly set. Hermetic test added; ADR-0016 updated.

Add optional, additive overrides to `imagegen.Request` — `Steps`, `CFGScale`, `NegativePrompt`, `Sampler`, `Seed` — with mirror options. nil/"" means "leave the backend's per-model default", so the v1 contract is unchanged for callers that don't set them. `provider/llamaswap` forwards them to sd-server as `steps`/`cfg_scale`/`negative_prompt`/`sample_method`/`seed` (omitempty). Enables callers (mort drawbots) to override only what they explicitly set. Hermetic test added; ADR-0016 updated.

🪰 Gadfly — live review status

7/7 reviewers finished · updated 2026-06-28 22:54:50Z

claude-code/opus · claude-code — done

  • security — No material issues found
  • correctness — No material issues found
  • maintainability — Minor issues
  • performance — No material issues found
  • error-handling — Minor issues

claude-code/opus:max · claude-code — done

  • security — No material issues found
  • correctness — No material issues found
  • maintainability — Minor issues
  • performance — No material issues found
  • error-handling — Minor issues

deepseek-v4-pro:cloud · ollama-cloud — done

  • security — No material issues found
  • correctness — No material issues found
  • maintainability — No material issues found
  • performance — No material issues found
  • error-handling — Minor issues

devstral-2:123b-cloud · ollama-cloud — done

  • security — Minor issues
  • correctness — Minor issues
  • maintainability — Minor issues
  • performance — No material issues found
  • error-handling — Minor issues

glm-5.2:cloud · ollama-cloud — done

  • security — No material issues found
  • correctness — Minor issues
  • maintainability — No material issues found
  • performance — No material issues found
  • error-handling — Minor issues

kimi-k2.6:cloud · ollama-cloud — done

  • security — No material issues found
  • correctness — No material issues found
  • maintainability — No material issues found
  • performance — No material issues found
  • error-handling — No material issues found

qwen3.5:397b-cloud · ollama-cloud — done

  • security — No material issues found
  • correctness — No material issues found
  • maintainability — No material issues found
  • performance — No material issues found
  • error-handling — Minor issues

Live status board. Findings are posted in each model's own comment. Advisory only — does not block merge.

<!-- gadfly-status-board --> ## 🪰 Gadfly — live review status 7/7 reviewers finished · updated 2026-06-28 22:54:50Z #### `claude-code/opus` · claude-code — ✅ done - ✅ **security** — No material issues found - ✅ **correctness** — No material issues found - ✅ **maintainability** — Minor issues - ✅ **performance** — No material issues found - ✅ **error-handling** — Minor issues #### `claude-code/opus:max` · claude-code — ✅ done - ✅ **security** — No material issues found - ✅ **correctness** — No material issues found - ✅ **maintainability** — Minor issues - ✅ **performance** — No material issues found - ✅ **error-handling** — Minor issues #### `deepseek-v4-pro:cloud` · ollama-cloud — ✅ done - ✅ **security** — No material issues found - ✅ **correctness** — No material issues found - ✅ **maintainability** — No material issues found - ✅ **performance** — No material issues found - ✅ **error-handling** — Minor issues #### `devstral-2:123b-cloud` · ollama-cloud — ✅ done - ✅ **security** — Minor issues - ✅ **correctness** — Minor issues - ✅ **maintainability** — Minor issues - ✅ **performance** — No material issues found - ✅ **error-handling** — Minor issues #### `glm-5.2:cloud` · ollama-cloud — ✅ done - ✅ **security** — No material issues found - ✅ **correctness** — Minor issues - ✅ **maintainability** — No material issues found - ✅ **performance** — No material issues found - ✅ **error-handling** — Minor issues #### `kimi-k2.6:cloud` · ollama-cloud — ✅ done - ✅ **security** — No material issues found - ✅ **correctness** — No material issues found - ✅ **maintainability** — No material issues found - ✅ **performance** — No material issues found - ✅ **error-handling** — No material issues found #### `qwen3.5:397b-cloud` · ollama-cloud — ✅ done - ✅ **security** — No material issues found - ✅ **correctness** — No material issues found - ✅ **maintainability** — No material issues found - ✅ **performance** — No material issues found - ✅ **error-handling** — Minor issues <sub>Live status board. Findings are posted in each model's own comment. Advisory only — does not block merge.</sub>

🪰 Gadfly review — ragnaros/qwen3.6-27b (ragnaros)

Reviewing… this comment will update with findings and run time.

<!-- gadfly-review:ollama:ragnaros/qwen3.6-27b --> ### 🪰 Gadfly review — `ragnaros/qwen3.6-27b` (ragnaros) ⏳ Reviewing… this comment will update with findings and run time.

🪰 Gadfly review — glm-5.2:cloud (ollama-cloud)

Verdict: Minor issues — 5 reviewers: security, correctness, maintainability, performance, error-handling

🔒 Security — No material issues found

VERDICT: No material issues found

  • I read provider/llamaswap/image.go and imagegen/imagegen.go in full. The new override fields (NegativePrompt, Sampler strings; Steps, CFGScale, Seed numerics) are carried only as fields of the imageRequest struct and marshaled by doJSON into a JSON POST body to the fixed path /v1/images/generations. They are values in a JSON object, not interpolated into a URL, query string, SQL, command, or template — so there is no injection/SSRF/path-traversal surface introduced by this change. The existing Prompt/Size/Model fields already follow the same trusted-backend pattern.
  • No validation of negative/absurd numeric values (e.g. Steps < 0) is added, but that is a correctness/DoS-resilience concern against a trusted internal sd-server, not a security boundary in scope here; the backend clamps/ignores them.
  • No secrets, credentials, or untrusted-deserialization paths are touched. Raw remains an escape hatch but is unchanged.

Nothing in my security lane is materially wrong with this diff.

🎯 Correctness — Minor issues

VERDICT: Minor issues

  • imagegen/imagegen.go:62 — The Seed field doc says // Seed fixes the RNG seed for reproducible output; nil = random., but that contradicts both the block comment at imagegen/imagegen.go:42-45 (nil/"" means "leave the backend's own default" baked into the llama-swap launch flags) and the forwarding semantics in provider/llamaswap/image.go:77. Because Seed *int64 carries json:"seed,omitempty" and image.go:77 copies req.Seed straight through, a nil seed is omitted from the wire request, so sd-server falls back to its per-model launch-flag default — which is not guaranteed to be random. If the launch flags pin a fixed seed, nil yields deterministic output, not random. Every other override field is honestly documented as "nil = backend default"; Seed is the only one over-claimed as "nil = random." Suggested fix: change the comment to something like // Seed fixes the RNG seed for reproducible output; nil = backend default (random only if the backend's launch default is random). so a caller relying on "unset = random" doesn't silently get deterministic results.

No other correctness issues found. The field-name mapping (steps/cfg_scale/negative_prompt/sample_method/seed) matches the stable-diffusion.cpp OpenAI-compatible endpoint conventions; the pointer+omitempty encoding correctly makes unset values absent on the wire so the backend keeps its defaults; Apply copies the request value and the option closures capture their args by value, so the &n/&s/&seed pointers remain valid for the single apply pass; and the hermetic test's expected JSON types (float64 for numeric fields) are correct for encoding/json decoding into map[string]any.

🧹 Code cleanliness & maintainability — No material issues found

VERDICT: No material issues found

  • Reviewed imagegen/imagegen.go, provider/llamaswap/image.go, and provider/llamaswap/llamaswap_test.go. The new fields/options mirror the existing WithN/WithSize pattern exactly (same Option-func shape, same pointer-for-nullable semantics), and the wire-struct additions reuse the established omitempty convention already present for N/Size. The provider forwarding block is a clean 1:1 mapping with a single naming transform (SamplerSampleMethod), well-documented in the struct comment.
  • One intentional-but-noteworthy wording inconsistency: NegativePrompt's comment reads "" = none while every other override reads nil/"" = backend default. This is semantically deliberate (no negative prompt is itself a meaningful state distinct from "default"), so it is not a defect — but a future reader scanning for the uniform "backend default" phrasing may pause. No change required.
  • The test follows the structure of the neighboring TestImageGenerate (same httptest + gotBody capture idiom) and cleanly covers both the omit-when-unset and forward-when-set paths. The want map's mixed float64(8) / 3.5 literals are idiomatic for a map[string]any against encoding/json-decoded numbers and match the repo's existing test style.

No cleanliness/maintainability concerns worth acting on.

Performance — No material issues found

No material issues found.

Through the performance lens this change is clean: it adds five scalar/pointer fields to a request struct that is constructed once per Generate call, copies them by value into the wire struct (no new allocations beyond the pre-existing *int/*float64/*int64 pointers the caller already created via the With* options), and uses omitempty so the JSON payload only grows when a caller actually sets an override. There are no loops, no extra queries, no blocking work, and no unbounded growth introduced. The Apply loop is unchanged in structure and runs over the same variadic opts slice as before. Nothing material to flag.

🧯 Error handling & edge cases — Minor issues

VERDICT: Minor issues

  • imagegen/imagegen.go:86-99 / provider/llamaswap/image.go:73-77 — The new numeric overrides are forwarded to the wire with no bounds validation, while the same Generate already validates req.N < 0 (image.go:63). A caller doing WithSteps(-5), WithCFGScale(-1), or WithSeed with a nonsensical value sends it straight to sd-server, which will likely fail with an opaque upstream error rather than a clear llm.ErrUnsupported/validation message. This is an inconsistency in the unhappy-path handling within the same function. Since nil/unset is the documented "leave default" path and the design defers semantics to the backend, this is minor — but if N deserves a guard, the explicitly-set numeric overrides arguably do too. Suggested fix: validate non-nil Steps/CFGScale (and optionally Seed) for sensible ranges in Generate and return a wrapped llm.ErrUnsupported, mirroring the existing N check.

No swallowed errors, missing defer, or nil/empty-edge-case defects were introduced. The pointer-taking option constructors (WithSteps/WithCFGScale/WithSeed) each capture a fresh local, so there's no aliasing/shared-pointer hazard across calls. The hermetic test's defer srv.Close() and decode-error discard are appropriate for a test handler.

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 2m 12s

<!-- gadfly-review:ollama:glm-5.2:cloud --> ### 🪰 Gadfly review — `glm-5.2:cloud` (ollama-cloud) **Verdict: Minor issues** — 5 reviewers: security, correctness, maintainability, performance, error-handling <details><summary><b>🔒 Security</b> — No material issues found</summary> VERDICT: No material issues found - I read `provider/llamaswap/image.go` and `imagegen/imagegen.go` in full. The new override fields (`NegativePrompt`, `Sampler` strings; `Steps`, `CFGScale`, `Seed` numerics) are carried only as fields of the `imageRequest` struct and marshaled by `doJSON` into a JSON POST body to the fixed path `/v1/images/generations`. They are values in a JSON object, not interpolated into a URL, query string, SQL, command, or template — so there is no injection/SSRF/path-traversal surface introduced by this change. The existing `Prompt`/`Size`/`Model` fields already follow the same trusted-backend pattern. - No validation of negative/absurd numeric values (e.g. `Steps` < 0) is added, but that is a correctness/DoS-resilience concern against a trusted internal sd-server, not a security boundary in scope here; the backend clamps/ignores them. - No secrets, credentials, or untrusted-deserialization paths are touched. `Raw` remains an escape hatch but is unchanged. Nothing in my security lane is materially wrong with this diff. </details> <details><summary><b>🎯 Correctness</b> — Minor issues</summary> ## VERDICT: Minor issues - `imagegen/imagegen.go:62` — The `Seed` field doc says `// Seed fixes the RNG seed for reproducible output; nil = random.`, but that contradicts both the block comment at `imagegen/imagegen.go:42-45` (nil/"" means "leave the backend's own default" baked into the llama-swap launch flags) and the forwarding semantics in `provider/llamaswap/image.go:77`. Because `Seed *int64` carries `json:"seed,omitempty"` and `image.go:77` copies `req.Seed` straight through, a nil seed is **omitted** from the wire request, so sd-server falls back to its per-model launch-flag default — which is **not** guaranteed to be random. If the launch flags pin a fixed seed, `nil` yields deterministic output, not random. Every other override field is honestly documented as "nil = backend default"; `Seed` is the only one over-claimed as "nil = random." Suggested fix: change the comment to something like `// Seed fixes the RNG seed for reproducible output; nil = backend default (random only if the backend's launch default is random).` so a caller relying on "unset = random" doesn't silently get deterministic results. No other correctness issues found. The field-name mapping (`steps`/`cfg_scale`/`negative_prompt`/`sample_method`/`seed`) matches the stable-diffusion.cpp OpenAI-compatible endpoint conventions; the pointer+`omitempty` encoding correctly makes unset values absent on the wire so the backend keeps its defaults; `Apply` copies the request value and the option closures capture their args by value, so the `&n`/`&s`/`&seed` pointers remain valid for the single apply pass; and the hermetic test's expected JSON types (`float64` for numeric fields) are correct for `encoding/json` decoding into `map[string]any`. </details> <details><summary><b>🧹 Code cleanliness & maintainability</b> — No material issues found</summary> VERDICT: No material issues found - Reviewed `imagegen/imagegen.go`, `provider/llamaswap/image.go`, and `provider/llamaswap/llamaswap_test.go`. The new fields/options mirror the existing `WithN`/`WithSize` pattern exactly (same Option-func shape, same pointer-for-nullable semantics), and the wire-struct additions reuse the established `omitempty` convention already present for `N`/`Size`. The provider forwarding block is a clean 1:1 mapping with a single naming transform (`Sampler`→`SampleMethod`), well-documented in the struct comment. - One intentional-but-noteworthy wording inconsistency: `NegativePrompt`'s comment reads `"" = none` while every other override reads `nil/"" = backend default`. This is semantically deliberate (no negative prompt is itself a meaningful state distinct from "default"), so it is not a defect — but a future reader scanning for the uniform "backend default" phrasing may pause. No change required. - The test follows the structure of the neighboring `TestImageGenerate` (same `httptest` + `gotBody` capture idiom) and cleanly covers both the omit-when-unset and forward-when-set paths. The `want` map's mixed `float64(8)` / `3.5` literals are idiomatic for a `map[string]any` against `encoding/json`-decoded numbers and match the repo's existing test style. No cleanliness/maintainability concerns worth acting on. </details> <details><summary><b>⚡ Performance</b> — No material issues found</summary> No material issues found. Through the performance lens this change is clean: it adds five scalar/pointer fields to a request struct that is constructed once per `Generate` call, copies them by value into the wire struct (no new allocations beyond the pre-existing `*int`/`*float64`/`*int64` pointers the caller already created via the `With*` options), and uses `omitempty` so the JSON payload only grows when a caller actually sets an override. There are no loops, no extra queries, no blocking work, and no unbounded growth introduced. The `Apply` loop is unchanged in structure and runs over the same variadic opts slice as before. Nothing material to flag. </details> <details><summary><b>🧯 Error handling & edge cases</b> — Minor issues</summary> **VERDICT: Minor issues** - `imagegen/imagegen.go:86-99` / `provider/llamaswap/image.go:73-77` — The new numeric overrides are forwarded to the wire with no bounds validation, while the same `Generate` already validates `req.N < 0` (`image.go:63`). A caller doing `WithSteps(-5)`, `WithCFGScale(-1)`, or `WithSeed` with a nonsensical value sends it straight to sd-server, which will likely fail with an opaque upstream error rather than a clear `llm.ErrUnsupported`/validation message. This is an inconsistency in the unhappy-path handling within the same function. Since `nil`/unset is the documented "leave default" path and the design defers semantics to the backend, this is minor — but if `N` deserves a guard, the explicitly-set numeric overrides arguably do too. Suggested fix: validate non-nil `Steps`/`CFGScale` (and optionally `Seed`) for sensible ranges in `Generate` and return a wrapped `llm.ErrUnsupported`, mirroring the existing `N` check. No swallowed errors, missing `defer`, or nil/empty-edge-case defects were introduced. The pointer-taking option constructors (`WithSteps`/`WithCFGScale`/`WithSeed`) each capture a fresh local, so there's no aliasing/shared-pointer hazard across calls. The hermetic test's `defer srv.Close()` and decode-error discard are appropriate for a test handler. </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 2m 12s</sub>

🪰 Gadfly review — minimax-m3:cloud (ollama-cloud)

Verdict: Minor issues — 5 reviewers: security, correctness, maintainability, performance, error-handling

🔒 Security — No material issues found

Final review.

VERDICT: No material issues found (security lens)

The change is purely additive at the wire layer: five new optional fields are appended to imagegen.Request, threaded through the existing Option/Apply pipeline, and serialized by encoding/json with omitempty so unset values are dropped from the request body. I verified the following through the lens and found nothing materially wrong:

  • Auth: doJSON (provider/llamaswap/llamaswap.go:188-227) still attaches Authorization: Bearer <token> when configured (lines 207-209). The new fields share the same code path; no new unauthenticated surface is created.
  • Injection / tainted input: NegativePrompt, Sampler, and the rest are user-controllable strings/numbers that flow into the JSON request body only. encoding/json correctly escapes quotes, backslashes, and control characters, so an attacker who controls Sampler cannot break out of the string to inject extra JSON fields or HTTP headers. They never touch a URL, shell, or SQL sink.
  • SSRF: No new URL construction. The path is still the hard-coded /v1/images/generations; baseURL is unaffected by the new fields.
  • Deserialization / unsafe defaults: Nothing new is decoded; no unsafe, no reflection-based decoding. omitempty on the pointer/empty-string fields preserves the prior defaulting contract — confirmed by TestImageGenerateSettings (provider/llamaswap/llamaswap_test.go:215-224), which asserts the unset fields are absent from the wire body.
  • Secret / credential leakage: No logging is introduced. Even if a caller later adds error-path logging of the wire body, no token is mixed into the body (it's in the header).

Minor note (input validation, within lens)

imagegen/imagegen.go:48-63 and the new WithSteps / WithCFGScale / WithSeed options accept arbitrary values with no client-side validation: Steps may be 0 or negative, CFGScale may be NaN/Inf/-Inf, Seed may be any int64. NaN/Inf encode into JSON as non-standard tokens that some strict sd-server builds reject, and negative Steps can trigger pathological behavior in diffusion backends. Not a direct security exploit against this Go layer, but it's an unsafe-default concern: a buggy or hostile caller that supplies imagegen.WithSteps(0) or imagegen.WithCFGScale(math.NaN()) will get a malformed-by-spec body sent to the upstream. Two reasonable fixes:

  • Mirror the existing req.N < 0 check at provider/llamaswap/image.go:63 and reject Steps <= 0, non-finite CFGScale, or Seed < 0 (or document the contract and let the backend validate).
  • Or, given that omitempty already gives the caller a "default" channel, drop these values silently rather than forwarding garbage.

Verified by reading imagegen/imagegen.go, provider/llamaswap/image.go, provider/llamaswap/llamaswap.go, and provider/llamaswap/llamaswap_test.go in full.

🎯 Correctness — Minor issues

VERDICT: Minor issues

Findings (Correctness lens):

  • provider/llamaswap/image.go:45SampleMethod's JSON tag is "sample_method". The field is wired from imagegen.Request.Sampler (a string like "euler" / "euler_a"). This tag is consistent with the ADR (docs/adr/0016-imagegen-interface.md) and with the test assertion at provider/llamaswap/llamaswap_test.go:238 ("sample_method": "euler"). Confirmed by grep that no sd-server client/source is checked into this repo, so the canonical upstream wire field name cannot be independently verified from the codebase — the field name is taken on faith from the ADR. Not a bug in this repo, but worth flagging as a non-blocker cross-system assumption.

  • provider/llamaswap/image.go:42-46Steps *int, CFGScale *float64, Seed *int64 with omitempty. Verified at lines 73-77 that the assignment is a direct pointer copy (Steps: req.Steps, etc.), so encoding/json will omit nil pointers entirely. Matches the test assertion at llamaswap_test.go:220-224 that unset overrides are absent from the wire body. No bug.

  • imagegen/imagegen.go:86-99WithSteps, WithCFGScale, WithNegativePrompt, WithSampler, WithSeed all take the address of their parameter to populate *int / *float64 / *int64 / string fields. The captured variable is a fresh stack copy per call, so no aliasing across calls. The string-field options (WithNegativePrompt, WithSampler) assign directly without &. Verified — no correctness issue.

  • provider/llamaswap/image.go:30-47 — The doc comment explains the omitempty + pointer semantics and notes that unrecognized field names are silently ignored by older sd-server builds. Consistent with the nil/empty "use backend default" contract in imagegen.Request (imagegen/imagegen.go:42-63). No bug.

  • majordomo.go:122-124 — Only WithImageCount and WithImageSize are re-exported at the top-level facade. The five new options (WithSteps, WithCFGScale, WithNegativePrompt, WithSampler, WithSeed) are deliberately NOT re-exported — callers must import imagegen directly to use them. This is an intentional design choice (the same pattern already applies to other imagegen internals) and is not a correctness bug, but it is a non-blocker inconsistency worth noting: the ADR-advertised "mirror options" exist only in imagegen, not on the majordomo facade.

🧹 Code cleanliness & maintainability — Minor issues

VERDICT: Minor issues

Findings (code cleanliness & maintainability lens)

  • imagegen/imagegen.go:28 — stale doc comment. The package-level doc says Request has "Pointer-free zero values mean 'provider default'". After this change, three of the new fields (Steps, CFGScale, Seed) are pointers, so a literal reading is now wrong. The intent — "zero value == backend default" — still holds, but the prose no longer matches the struct. Suggested fix: reword to something like "Zero values (0, '', or nil) mean 'provider default' for the corresponding field".

  • majordomo.go:122-124 — partial re-export of imagegen options, intentional asymmetry. The top-level facade already re-exports WithImageCount/WithImageSize for two of the existing four options, but adds none for the five new options (WithSteps, WithCFGScale, WithNegativePrompt, WithSampler, WithSeed). That leaves callers in two camps (some imagegen options reachable via majordomo.WithImage*, others only via imagegen.With*), which is exactly the half-and-half readability/maintainability trap functional-option facades are designed to avoid. Pick one: either re-export the full set (WithImageStepsWithImageSeed), or drop the existing pair to keep majordomo as a thin re-export and push all callers at imagegen.* consistently.

  • provider/llamaswap/image.go:30-35 — chatty doc comment on imageRequest. The comment (a) re-explains b64_json (already covered upstream), and (b) embeds a defensive parenthetical about unrecognized sd-server builds that reads as speculative implementation detail rather than contract. Not wrong, but harder to scan than the rest of the package's doc comments. A single line ("OpenAI /v1/images/generations shape, with sd-server extension fields forwarded as omitempty pointers/strings") would carry the same information with less noise.

  • provider/llamaswap/image.go:45SamplerSampleMethod rename is asymmetric across layers. The canonical field on imagegen.Request is Sampler / WithSampler, but the wire field is SampleMethod. That split (Go name ≠ JSON tag ≠ option name) is what *Request callers will hit first when grepping. Nothing to fix here — sd-server's spelling is what it is — but worth a one-line note on the Sampler field comment in imagegen/imagegen.go:58 clarifying it's mapped to sample_method on the wire, so a future reader doesn't go looking for a Sampler JSON tag.

Verified

  • All new fields, options, and the wire mapping cross-check cleanly (imagegen/imagegen.go lines 47-63, 86-99; provider/llamaswap/image.go lines 42-46, 73-77).
  • The hermetic test TestImageGenerateSettings (provider/llamaswap/llamaswap_test.go:204-244) is well-shaped: it explicitly asserts both the omit-when-unset and round-trip-when-set paths, and the want numeric values are typed to match JSON's float64 decode.
  • ADR update is consistent with the code (docs/adr/0016-imagegen-interface.md:46-54).

No other cleanliness issues: the new code follows the existing WithN/WithSize/Apply pattern, names mirror the dominant domain convention, and there's no duplication or copy-paste worth refactoring.

Performance — No material issues found

VERDICT: No material issues found

Through the performance lens, this change is clean:

  • No hot-path regressions. Generate already performs exactly one HTTP round-trip; the change adds five small fields to the wire struct literal built once per call. No loops, no extra allocations per image.
  • Pointer fields are the right call. Steps *int, CFGScale *float64, Seed *int64 are necessary for omitempty semantics (Go's encoding/json treats numeric zero as non-empty), and Go's encoder correctly omits nil pointers — so unset values add zero wire bytes and zero extra JSON work.
  • Option closures are cheap and one-shot. WithSteps/WithCFGScale/WithSeed escape one int/float/int64 to the heap per call. This is unavoidable given the pointer-field API and happens at most once per override per request — not measurable.
  • req.Apply(opts...) is still O(opts). Unchanged.
  • Test is efficient. The new TestImageGenerateSettings reuses a single httptest.NewServer and Provider across both the unset and set-override calls. Good.
  • No unbounded growth, no quadratic behavior, no extra I/O, no new blocking calls.

Nothing in this diff creates a perf concern in my lane.

🧯 Error handling & edge cases — No material issues found

VERDICT: No material issues found

Verified by reading imagegen/imagegen.go, provider/llamaswap/image.go, provider/llamaswap/llamaswap_test.go, and majordomo.go:

  • The new optional fields use *int/*float64/*int64 pointers (for Steps/CFGScale/Seed) and plain string (for NegativePrompt/Sampler), so the unset-vs-set edge case the contract hinges on is type-safe — no risk of "set to 0" being silently treated as "unset". The omitempty JSON tags on the wire struct align: an unset pointer/nil-slice/string drops out of the body entirely, so sd-server keeps its own per-model defaults rather than receiving a zeroed value and producing a degenerate generation.
  • Generate's existing validation (Prompt non-empty after TrimSpace, N >= 0) is unchanged and still runs before the new fields are forwarded — no new error paths swallowed.
  • The new TestImageGenerateSettings test exercises both the unset-omitted and set-forwarded branches, including JSON-number decoding (steps/seed arrive as float64 in map[string]any, matched against float64(8)/float64(42) in the expected map), so the omitempty semantics on the pointers are actually pinned down.
  • No defer/Close paths are introduced; no new I/O or resources. The srv.Close() defer in the new test is correctly placed.

Nothing material in this lens — the diff treats the "unset means default" edge case carefully via pointer types and omitempty, and adds no error paths or cleanup obligations.

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 1m 40s

<!-- gadfly-review:ollama:minimax-m3:cloud --> ### 🪰 Gadfly review — `minimax-m3:cloud` (ollama-cloud) **Verdict: Minor issues** — 5 reviewers: security, correctness, maintainability, performance, error-handling <details><summary><b>🔒 Security</b> — No material issues found</summary> Final review. ## VERDICT: No material issues found (security lens) The change is purely additive at the wire layer: five new optional fields are appended to `imagegen.Request`, threaded through the existing `Option`/`Apply` pipeline, and serialized by `encoding/json` with `omitempty` so unset values are dropped from the request body. I verified the following through the lens and found nothing materially wrong: - **Auth**: `doJSON` (provider/llamaswap/llamaswap.go:188-227) still attaches `Authorization: Bearer <token>` when configured (lines 207-209). The new fields share the same code path; no new unauthenticated surface is created. - **Injection / tainted input**: `NegativePrompt`, `Sampler`, and the rest are user-controllable strings/numbers that flow into the JSON request body only. `encoding/json` correctly escapes quotes, backslashes, and control characters, so an attacker who controls `Sampler` cannot break out of the string to inject extra JSON fields or HTTP headers. They never touch a URL, shell, or SQL sink. - **SSRF**: No new URL construction. The path is still the hard-coded `/v1/images/generations`; `baseURL` is unaffected by the new fields. - **Deserialization / unsafe defaults**: Nothing new is decoded; no `unsafe`, no reflection-based decoding. `omitempty` on the pointer/empty-string fields preserves the prior defaulting contract — confirmed by `TestImageGenerateSettings` (provider/llamaswap/llamaswap_test.go:215-224), which asserts the unset fields are absent from the wire body. - **Secret / credential leakage**: No logging is introduced. Even if a caller later adds error-path logging of the wire body, no token is mixed into the body (it's in the header). ### Minor note (input validation, within lens) `imagegen/imagegen.go:48-63` and the new `WithSteps` / `WithCFGScale` / `WithSeed` options accept arbitrary values with no client-side validation: `Steps` may be 0 or negative, `CFGScale` may be NaN/Inf/-Inf, `Seed` may be any int64. NaN/Inf encode into JSON as non-standard tokens that some strict sd-server builds reject, and negative `Steps` can trigger pathological behavior in diffusion backends. Not a *direct* security exploit against this Go layer, but it's an unsafe-default concern: a buggy or hostile caller that supplies `imagegen.WithSteps(0)` or `imagegen.WithCFGScale(math.NaN())` will get a malformed-by-spec body sent to the upstream. Two reasonable fixes: - Mirror the existing `req.N < 0` check at `provider/llamaswap/image.go:63` and reject `Steps <= 0`, non-finite `CFGScale`, or `Seed < 0` (or document the contract and let the backend validate). - Or, given that `omitempty` already gives the caller a "default" channel, drop these values silently rather than forwarding garbage. Verified by reading `imagegen/imagegen.go`, `provider/llamaswap/image.go`, `provider/llamaswap/llamaswap.go`, and `provider/llamaswap/llamaswap_test.go` in full. </details> <details><summary><b>🎯 Correctness</b> — Minor issues</summary> VERDICT: Minor issues Findings (Correctness lens): - `provider/llamaswap/image.go:45` — `SampleMethod`'s JSON tag is `"sample_method"`. The field is wired from `imagegen.Request.Sampler` (a string like `"euler"` / `"euler_a"`). This tag is consistent with the ADR (`docs/adr/0016-imagegen-interface.md`) and with the test assertion at `provider/llamaswap/llamaswap_test.go:238` (`"sample_method": "euler"`). Confirmed by grep that no sd-server client/source is checked into this repo, so the canonical upstream wire field name cannot be independently verified from the codebase — the field name is taken on faith from the ADR. Not a bug in this repo, but worth flagging as a non-blocker cross-system assumption. - `provider/llamaswap/image.go:42-46` — `Steps *int`, `CFGScale *float64`, `Seed *int64` with `omitempty`. Verified at lines 73-77 that the assignment is a direct pointer copy (`Steps: req.Steps`, etc.), so `encoding/json` will omit nil pointers entirely. Matches the test assertion at `llamaswap_test.go:220-224` that unset overrides are absent from the wire body. No bug. - `imagegen/imagegen.go:86-99` — `WithSteps`, `WithCFGScale`, `WithNegativePrompt`, `WithSampler`, `WithSeed` all take the address of their parameter to populate `*int` / `*float64` / `*int64` / `string` fields. The captured variable is a fresh stack copy per call, so no aliasing across calls. The string-field options (`WithNegativePrompt`, `WithSampler`) assign directly without `&`. Verified — no correctness issue. - `provider/llamaswap/image.go:30-47` — The doc comment explains the omitempty + pointer semantics and notes that unrecognized field names are silently ignored by older sd-server builds. Consistent with the nil/empty "use backend default" contract in `imagegen.Request` (`imagegen/imagegen.go:42-63`). No bug. - `majordomo.go:122-124` — Only `WithImageCount` and `WithImageSize` are re-exported at the top-level facade. The five new options (`WithSteps`, `WithCFGScale`, `WithNegativePrompt`, `WithSampler`, `WithSeed`) are deliberately NOT re-exported — callers must import `imagegen` directly to use them. This is an intentional design choice (the same pattern already applies to other `imagegen` internals) and is not a correctness bug, but it is a non-blocker inconsistency worth noting: the ADR-advertised "mirror options" exist only in `imagegen`, not on the `majordomo` facade. </details> <details><summary><b>🧹 Code cleanliness & maintainability</b> — Minor issues</summary> ## VERDICT: Minor issues ### Findings (code cleanliness & maintainability lens) - **`imagegen/imagegen.go:28` — stale doc comment.** The package-level doc says `Request` has "Pointer-free zero values mean 'provider default'". After this change, three of the new fields (`Steps`, `CFGScale`, `Seed`) are pointers, so a literal reading is now wrong. The intent — "zero value == backend default" — still holds, but the prose no longer matches the struct. Suggested fix: reword to something like "Zero values (0, '', or nil) mean 'provider default' for the corresponding field". - **`majordomo.go:122-124` — partial re-export of imagegen options, intentional asymmetry.** The top-level facade already re-exports `WithImageCount`/`WithImageSize` for two of the existing four options, but adds none for the five new options (`WithSteps`, `WithCFGScale`, `WithNegativePrompt`, `WithSampler`, `WithSeed`). That leaves callers in two camps (some imagegen options reachable via `majordomo.WithImage*`, others only via `imagegen.With*`), which is exactly the half-and-half readability/maintainability trap functional-option facades are designed to avoid. Pick one: either re-export the full set (`WithImageSteps` … `WithImageSeed`), or drop the existing pair to keep `majordomo` as a thin re-export and push all callers at `imagegen.*` consistently. - **`provider/llamaswap/image.go:30-35` — chatty doc comment on `imageRequest`.** The comment (a) re-explains `b64_json` (already covered upstream), and (b) embeds a defensive parenthetical about unrecognized sd-server builds that reads as speculative implementation detail rather than contract. Not wrong, but harder to scan than the rest of the package's doc comments. A single line ("OpenAI /v1/images/generations shape, with sd-server extension fields forwarded as `omitempty` pointers/strings") would carry the same information with less noise. - **`provider/llamaswap/image.go:45` — `Sampler` ↔ `SampleMethod` rename is asymmetric across layers.** The canonical field on `imagegen.Request` is `Sampler` / `WithSampler`, but the wire field is `SampleMethod`. That split (Go name ≠ JSON tag ≠ option name) is what `*Request` callers will hit first when grepping. Nothing to fix here — sd-server's spelling is what it is — but worth a one-line note on the `Sampler` field comment in `imagegen/imagegen.go:58` clarifying it's mapped to `sample_method` on the wire, so a future reader doesn't go looking for a `Sampler` JSON tag. ### Verified - All new fields, options, and the wire mapping cross-check cleanly (`imagegen/imagegen.go` lines 47-63, 86-99; `provider/llamaswap/image.go` lines 42-46, 73-77). - The hermetic test `TestImageGenerateSettings` (`provider/llamaswap/llamaswap_test.go:204-244`) is well-shaped: it explicitly asserts both the omit-when-unset and round-trip-when-set paths, and the `want` numeric values are typed to match JSON's `float64` decode. - ADR update is consistent with the code (`docs/adr/0016-imagegen-interface.md:46-54`). No other cleanliness issues: the new code follows the existing `WithN`/`WithSize`/`Apply` pattern, names mirror the dominant domain convention, and there's no duplication or copy-paste worth refactoring. </details> <details><summary><b>⚡ Performance</b> — No material issues found</summary> ## VERDICT: No material issues found Through the performance lens, this change is clean: - **No hot-path regressions.** `Generate` already performs exactly one HTTP round-trip; the change adds five small fields to the wire struct literal built once per call. No loops, no extra allocations per image. - **Pointer fields are the right call.** `Steps *int`, `CFGScale *float64`, `Seed *int64` are necessary for `omitempty` semantics (Go's `encoding/json` treats numeric zero as non-empty), and Go's encoder correctly omits nil pointers — so unset values add zero wire bytes and zero extra JSON work. - **Option closures are cheap and one-shot.** `WithSteps`/`WithCFGScale`/`WithSeed` escape one int/float/int64 to the heap per call. This is unavoidable given the pointer-field API and happens at most once per override per request — not measurable. - **`req.Apply(opts...)` is still O(opts).** Unchanged. - **Test is efficient.** The new `TestImageGenerateSettings` reuses a single `httptest.NewServer` and `Provider` across both the unset and set-override calls. Good. - **No unbounded growth, no quadratic behavior, no extra I/O, no new blocking calls.** Nothing in this diff creates a perf concern in my lane. </details> <details><summary><b>🧯 Error handling & edge cases</b> — No material issues found</summary> **VERDICT:** No material issues found Verified by reading `imagegen/imagegen.go`, `provider/llamaswap/image.go`, `provider/llamaswap/llamaswap_test.go`, and `majordomo.go`: - The new optional fields use `*int`/`*float64`/`*int64` pointers (for Steps/CFGScale/Seed) and plain `string` (for NegativePrompt/Sampler), so the unset-vs-set edge case the contract hinges on is type-safe — no risk of "set to 0" being silently treated as "unset". The `omitempty` JSON tags on the wire struct align: an unset pointer/nil-slice/string drops out of the body entirely, so sd-server keeps its own per-model defaults rather than receiving a zeroed value and producing a degenerate generation. - `Generate`'s existing validation (`Prompt` non-empty after `TrimSpace`, `N >= 0`) is unchanged and still runs before the new fields are forwarded — no new error paths swallowed. - The new `TestImageGenerateSettings` test exercises both the unset-omitted and set-forwarded branches, including JSON-number decoding (`steps`/`seed` arrive as `float64` in `map[string]any`, matched against `float64(8)`/`float64(42)` in the expected map), so the omitempty semantics on the pointers are actually pinned down. - No `defer`/`Close` paths are introduced; no new I/O or resources. The `srv.Close()` defer in the new test is correctly placed. Nothing material in this lens — the diff treats the "unset means default" edge case carefully via pointer types and omitempty, and adds no error paths or cleanup obligations. </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 1m 40s</sub>

🪰 Gadfly review — claude-code/opus (claude-code)

Verdict: Minor issues — 5 reviewers: security, correctness, maintainability, performance, error-handling

🔒 Security — No material issues found

I've traced the data flow for the new fields. Here's my security review.

VERDICT: No material issues found

The change is a clean, additive pass-through of five optional fields. Through the security lens:

  • No injection. NegativePrompt and Sampler are caller-controlled strings, but they reach the wire only via json.Marshal of imageRequest in doJSON (provider/llamaswap/llamaswap.go:194) — verified. encoding/json escapes string values, so there is no way to inject extra JSON fields or break the request structure. Steps/CFGScale/Seed are numeric. No string from these fields ever touches a URL, path, SQL, shell, or template.
  • No SSRF / path traversal. None of the new fields influence the request URL (p.baseURL+"/v1/images/generations", image.go:81) or any path. The model id — the only value in the path-ish position — is unchanged by this PR.
  • No secret/credential exposure. The Authorization: Bearer header (llamaswap.go:207-209) is unaffected; nothing new is logged or echoed. The new fields are not credentials.
  • Untrusted-data handling unchanged. Response parsing is still bounded by io.LimitReader(maxResponseBytes) (llamaswap.go:219); base64 decode of returned bytes is already error-checked (image.go:97-100). This PR doesn't widen that surface.
  • Insecure defaults. The defaults are nil/"" → "omit and let the backend decide," which is the safe, conservative choice; nothing is forced on by default.

Note (not a security finding): there's no upper-bound validation on Steps/N/CFGScale, so a caller could request an enormous step count. But the target is the operator's own trusted sd-server behind their own llama-swap, not an untrusted boundary, so this is a resource/correctness concern outside my lens rather than a security gap — flagging only so it's not mistaken for one.

The hermetic test (TestImageGenerateSettings) correctly asserts that unset overrides are omitted from the wire, which preserves the conservative default behavior.

🎯 Correctness — No material issues found

Both draft findings are explicitly self-described as unverifiable from this repo, and neither can be positively confirmed against the code:

  • Finding 1 (imagegen.go:62 "nil = random" vs "backend default"): The alleged "internal contradiction" dissolves on reading the code. For a seed, the backend's own default behavior is to pick a random seed when none is supplied (standard sd-server/stable-diffusion.cpp behavior), so "nil = random" and "leave the backend's own default" describe the same thing for this field. The draft concedes it could not verify that sd-server uses a fixed default seed — the half of the claim that would make this a real contradiction is unverified. Cannot confirm; dropped.

  • Finding 2 (image.go:42-46 field-name correctness): The draft itself states this is "not a code defect I can confirm" and that sd-server's accepted field set isn't in this repo. Pure unverified speculation about an external backend. Dropped.

The remainder of the draft's analysis (pointer/omitempty semantics, reuse safety via value-copy in Apply, and the test assertions) is correct and describes sound code, not defects.


VERDICT: No material issues found

The per-request settings change is correct: the *int/*float64/*int64 pointers with omitempty correctly distinguish "explicitly set" (including zero) from "unset → backend default"; Request.Apply copies by value and each With* option allocates a fresh pointee, so a reused Request is never mutated; and provider/llamaswap forwards the fields under the documented sd-server names with matching omitempty tags, covered by TestImageGenerateSettings for both the omitted-when-unset and forwarded-when-set paths. No correctness, logic, or formula defects confirmed.

🧹 Code cleanliness & maintainability — Minor issues

The finding is confirmed. Lines 28-30 say "Pointer-free zero values mean 'provider default'" and enumerate only N and Size, while the struct now contains Steps *int, CFGScale *float64, and Seed *int64 — pointers, deliberately, to distinguish unset from a meaningful zero. The contradiction is real.


VERDICT: Minor issues

  • imagegen/imagegen.go:28-30 — stale type doc comment now contradicts the struct. The Request doc says "Pointer-free zero values mean 'provider default'" and enumerates only N and Size. After this PR the struct is no longer pointer-free (Steps *int, CFGScale *float64, Seed *int64), so the header comment misdescribes the type it sits on. The per-field comments added (lines 42-63) are correct and clear, but a reader scanning the type doc gets a wrong mental model ("this is a pointer-free value type"). Low-churn fix: soften the header to something like "Zero values (0, "", nil) mean 'provider default'" and drop the "Pointer-free" framing, since the design now deliberately uses pointers to distinguish unset from a meaningful zero.
Performance — No material issues found

VERDICT: No material issues found

Reviewed through the Performance lens. This is a small, additive change to imagegen.Request and its llama-swap forwarding. I read the full checked-out imagegen/imagegen.go and provider/llamaswap/image.go rather than just the diff.

  • No new loops, allocations in hot paths, N+1 patterns, or unbounded growth. The forwarding in Generate (provider/llamaswap/image.go:67-78) is a flat struct copy of pointers/scalars per request — O(1), unchanged in shape.
  • The option constructors (WithSteps/WithCFGScale/WithSeed, imagegen/imagegen.go:87-99) heap-allocate one small value via &n per option used. This is once-per-request at request-construction time, not on any hot loop, and is the idiomatic Go pointer-option pattern — not a regression worth flagging.
  • Request.Apply (imagegen/imagegen.go:103-108) copies the Request by value; the struct grew by a few words (three pointers + two strings/scalars). Negligible per-call cost, no aliasing concern since the copy is intentional.
  • omitempty on the wire struct keeps the serialized payload identical for callers that don't set overrides — no extra bytes on the existing path.

Nothing in the performance lane is materially wrong here.

🧯 Error handling & edge cases — Minor issues

I've verified both files against the draft's claims.

The technical mechanics check out: req.N < 0 is guarded (image.go:63) while the new fields are not; WithSteps/WithCFGScale/WithSeed take non-pointer args and store their address (r.Steps = &n), so an explicit zero produces a non-nil pointer; and omitempty on a *int/*float64 only suppresses nil, not a pointer-to-zero — so WithSteps(0) does serialize "steps":0. The test (TestImageGenerateSettings) only exercises the nil-omitted and non-zero paths, never the explicit-zero boundary. All confirmed against the source.


VERDICT: Minor issues

My lens is error handling & edge cases. I read provider/llamaswap/image.go and imagegen/imagegen.go at the checked-out state to verify the new unhappy paths.

Findings

  • imagegen/imagegen.go:86-99 / provider/llamaswap/image.go:63-78Zero/negative values for the new numeric overrides are forwarded unguarded, inconsistent with the existing validation in the same function. Generate already guards req.N < 0 (image.go:63) and empty prompt, but adds no equivalent check for the new fields. The contract is "nil = backend default," yet WithSteps/WithCFGScale/WithSeed take a non-pointer arg and store its address (r.Steps = &n), so an explicit zero is a non-nil pointer. steps,omitempty on a *int only omits when the pointer is nil — a pointer to 0 is non-nil and is serialized. Therefore WithSteps(0) or WithSteps(-1) sends "steps":0 / "steps":-1 to sd-server, a nonsensical diffusion request, rather than being treated as "unset." Same for WithCFGScale(0) ("cfg_scale":0). Given the function already establishes a >= 0 guard for N, a caller would reasonably expect Steps to be similarly validated. Impact: a caller passing a bad literal gets an opaque backend error or a degenerate image instead of an early, clear validation error. Suggested fix: either validate (req.Steps != nil && *req.Steps <= 0ErrUnsupported, and CFGScale < 0), or document explicitly that these accept any value and are passed through verbatim. This is minor — sd-server is a backstop and the values are caller-explicit — but the asymmetry with the adjacent N check is the real smell.

  • Coverage note (not a bug): TestImageGenerateSettings only exercises the nil-omitted path and non-zero set values. It does not cover the explicit-zero boundary above, which is exactly where the omitempty/pointer nuance bites. Worth a case if the behavior is intended.

Everything else in my lane is clean: the base64-decode error, url-only/b64_json=="", and empty-data paths are all handled and return real errors; the new fields introduce no new cleanup/rollback/defer obligations and no nil-deref risk (pointers are only ever copied into the wire struct, never dereferenced here).

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 1m 35s

<!-- gadfly-review:ollama:claude-code/opus --> ### 🪰 Gadfly review — `claude-code/opus` (claude-code) **Verdict: Minor issues** — 5 reviewers: security, correctness, maintainability, performance, error-handling <details><summary><b>🔒 Security</b> — No material issues found</summary> I've traced the data flow for the new fields. Here's my security review. ## VERDICT: No material issues found The change is a clean, additive pass-through of five optional fields. Through the security lens: - **No injection.** `NegativePrompt` and `Sampler` are caller-controlled strings, but they reach the wire only via `json.Marshal` of `imageRequest` in `doJSON` (`provider/llamaswap/llamaswap.go:194`) — verified. `encoding/json` escapes string values, so there is no way to inject extra JSON fields or break the request structure. `Steps`/`CFGScale`/`Seed` are numeric. No string from these fields ever touches a URL, path, SQL, shell, or template. - **No SSRF / path traversal.** None of the new fields influence the request URL (`p.baseURL+"/v1/images/generations"`, `image.go:81`) or any path. The model id — the only value in the path-ish position — is unchanged by this PR. - **No secret/credential exposure.** The `Authorization: Bearer` header (`llamaswap.go:207-209`) is unaffected; nothing new is logged or echoed. The new fields are not credentials. - **Untrusted-data handling unchanged.** Response parsing is still bounded by `io.LimitReader(maxResponseBytes)` (`llamaswap.go:219`); base64 decode of returned bytes is already error-checked (`image.go:97-100`). This PR doesn't widen that surface. - **Insecure defaults.** The defaults are `nil`/`""` → "omit and let the backend decide," which is the safe, conservative choice; nothing is forced on by default. Note (not a security finding): there's no upper-bound validation on `Steps`/`N`/`CFGScale`, so a caller could request an enormous step count. But the target is the operator's own trusted sd-server behind their own llama-swap, not an untrusted boundary, so this is a resource/correctness concern outside my lens rather than a security gap — flagging only so it's not mistaken for one. The hermetic test (`TestImageGenerateSettings`) correctly asserts that unset overrides are omitted from the wire, which preserves the conservative default behavior. </details> <details><summary><b>🎯 Correctness</b> — No material issues found</summary> Both draft findings are explicitly self-described as unverifiable from this repo, and neither can be positively confirmed against the code: - **Finding 1 (`imagegen.go:62` "nil = random" vs "backend default"):** The alleged "internal contradiction" dissolves on reading the code. For a seed, the backend's own default behavior *is* to pick a random seed when none is supplied (standard sd-server/stable-diffusion.cpp behavior), so "nil = random" and "leave the backend's own default" describe the same thing for this field. The draft concedes it could not verify that sd-server uses a fixed default seed — the half of the claim that would make this a real contradiction is unverified. Cannot confirm; dropped. - **Finding 2 (`image.go:42-46` field-name correctness):** The draft itself states this is "not a code defect I can confirm" and that sd-server's accepted field set isn't in this repo. Pure unverified speculation about an external backend. Dropped. The remainder of the draft's analysis (pointer/`omitempty` semantics, reuse safety via value-copy in `Apply`, and the test assertions) is correct and describes sound code, not defects. --- VERDICT: No material issues found The per-request settings change is correct: the `*int`/`*float64`/`*int64` pointers with `omitempty` correctly distinguish "explicitly set" (including zero) from "unset → backend default"; `Request.Apply` copies by value and each `With*` option allocates a fresh pointee, so a reused `Request` is never mutated; and `provider/llamaswap` forwards the fields under the documented sd-server names with matching `omitempty` tags, covered by `TestImageGenerateSettings` for both the omitted-when-unset and forwarded-when-set paths. No correctness, logic, or formula defects confirmed. </details> <details><summary><b>🧹 Code cleanliness & maintainability</b> — Minor issues</summary> The finding is confirmed. Lines 28-30 say *"Pointer-free zero values mean 'provider default'"* and enumerate only `N` and `Size`, while the struct now contains `Steps *int`, `CFGScale *float64`, and `Seed *int64` — pointers, deliberately, to distinguish unset from a meaningful zero. The contradiction is real. --- **VERDICT: Minor issues** - **`imagegen/imagegen.go:28-30` — stale type doc comment now contradicts the struct.** The `Request` doc says *"Pointer-free zero values mean 'provider default'"* and enumerates only `N` and `Size`. After this PR the struct is no longer pointer-free (`Steps *int`, `CFGScale *float64`, `Seed *int64`), so the header comment misdescribes the type it sits on. The per-field comments added (lines 42-63) are correct and clear, but a reader scanning the type doc gets a wrong mental model ("this is a pointer-free value type"). Low-churn fix: soften the header to something like *"Zero values (0, "", nil) mean 'provider default'"* and drop the "Pointer-free" framing, since the design now deliberately uses pointers to distinguish unset from a meaningful zero. </details> <details><summary><b>⚡ Performance</b> — No material issues found</summary> **VERDICT: No material issues found** Reviewed through the ⚡ Performance lens. This is a small, additive change to `imagegen.Request` and its llama-swap forwarding. I read the full checked-out `imagegen/imagegen.go` and `provider/llamaswap/image.go` rather than just the diff. - No new loops, allocations in hot paths, N+1 patterns, or unbounded growth. The forwarding in `Generate` (`provider/llamaswap/image.go:67-78`) is a flat struct copy of pointers/scalars per request — O(1), unchanged in shape. - The option constructors (`WithSteps`/`WithCFGScale`/`WithSeed`, `imagegen/imagegen.go:87-99`) heap-allocate one small value via `&n` per option used. This is once-per-request at request-construction time, not on any hot loop, and is the idiomatic Go pointer-option pattern — not a regression worth flagging. - `Request.Apply` (`imagegen/imagegen.go:103-108`) copies the `Request` by value; the struct grew by a few words (three pointers + two strings/scalars). Negligible per-call cost, no aliasing concern since the copy is intentional. - `omitempty` on the wire struct keeps the serialized payload identical for callers that don't set overrides — no extra bytes on the existing path. Nothing in the performance lane is materially wrong here. </details> <details><summary><b>🧯 Error handling & edge cases</b> — Minor issues</summary> I've verified both files against the draft's claims. The technical mechanics check out: `req.N < 0` is guarded (image.go:63) while the new fields are not; `WithSteps`/`WithCFGScale`/`WithSeed` take non-pointer args and store their address (`r.Steps = &n`), so an explicit zero produces a non-nil pointer; and `omitempty` on a `*int`/`*float64` only suppresses nil, not a pointer-to-zero — so `WithSteps(0)` does serialize `"steps":0`. The test (`TestImageGenerateSettings`) only exercises the nil-omitted and non-zero paths, never the explicit-zero boundary. All confirmed against the source. --- VERDICT: Minor issues My lens is error handling & edge cases. I read `provider/llamaswap/image.go` and `imagegen/imagegen.go` at the checked-out state to verify the new unhappy paths. **Findings** - `imagegen/imagegen.go:86-99` / `provider/llamaswap/image.go:63-78` — **Zero/negative values for the new numeric overrides are forwarded unguarded, inconsistent with the existing validation in the same function.** `Generate` already guards `req.N < 0` (image.go:63) and empty prompt, but adds no equivalent check for the new fields. The contract is "nil = backend default," yet `WithSteps`/`WithCFGScale`/`WithSeed` take a non-pointer arg and store its address (`r.Steps = &n`), so an *explicit zero* is a non-nil pointer. `steps,omitempty` on a `*int` only omits when the pointer is nil — a pointer to `0` is non-nil and **is** serialized. Therefore `WithSteps(0)` or `WithSteps(-1)` sends `"steps":0` / `"steps":-1` to sd-server, a nonsensical diffusion request, rather than being treated as "unset." Same for `WithCFGScale(0)` (`"cfg_scale":0`). Given the function already establishes a `>= 0` guard for `N`, a caller would reasonably expect Steps to be similarly validated. Impact: a caller passing a bad literal gets an opaque backend error or a degenerate image instead of an early, clear validation error. Suggested fix: either validate (`req.Steps != nil && *req.Steps <= 0` → `ErrUnsupported`, and `CFGScale < 0`), or document explicitly that these accept any value and are passed through verbatim. This is minor — sd-server is a backstop and the values are caller-explicit — but the asymmetry with the adjacent `N` check is the real smell. - Coverage note (not a bug): `TestImageGenerateSettings` only exercises the nil-omitted path and non-zero set values. It does not cover the explicit-zero boundary above, which is exactly where the omitempty/pointer nuance bites. Worth a case if the behavior is intended. Everything else in my lane is clean: the base64-decode error, url-only/`b64_json==""`, and empty-`data` paths are all handled and return real errors; the new fields introduce no new cleanup/rollback/defer obligations and no nil-deref risk (pointers are only ever copied into the wire struct, never dereferenced here). </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 1m 35s</sub>

🪰 Gadfly review — deepseek-v4-pro:cloud (ollama-cloud)

Verdict: Minor issues — 5 reviewers: security, correctness, maintainability, performance, error-handling

🔒 Security — No material issues found

No material issues found

The diff adds optional per-request overrides (Steps, CFGScale, NegativePrompt, Sampler, Seed) that flow from imagegen.Request → JSON-serialized imageRequest → HTTP POST to the sd-server backend. From a security lens:

  • No new injection vectors: All fields are JSON-encoded via json.Marshal in doJSON (llamaswap.go:194), which prevents JSON structure injection. The string fields (NegativePrompt, Sampler) are text values forwarded to the backend — same trust model as the pre-existing Prompt field. No command construction, path building, or template interpolation is done with these values in the Go code.
  • No authn/authz changes: The same bearer token (p.token) is used; no new authorization paths.
  • No credential leakage: No new logging of request bodies; doJSON doesn't log parameters.
  • No SSRF: The backend URL is fixed at construction time (p.baseURL); the new fields don't influence routing.
  • No unsafe deserialization: Response handling is unchanged — JSON decoded into a fixed struct with io.LimitReader bounded at 64 MiB.

Minor observation (not a finding): The numeric fields (Steps, CFGScale) lack bounds validation (negative/zero/extreme values are forwarded as-is). This is consistent with the existing pattern — N only checks < 0, with no upper bound — and the callers are internal trusted code (mort drawbots). The backend is expected to reject or clamp invalid values. If this library were ever exposed to untrusted callers, input validation would need to be added, but that's not the current threat model.

🎯 Correctness — No material issues found

Verdict: No material issues found

After thorough review through the 🎯 Correctness lens, I find no logic bugs or semantic errors in this change. Here's what I verified:

  • Field mapping (image.go:73-77): req.Stepswire.Steps, req.CFGScalewire.CFGScale, req.NegativePromptwire.NegativePrompt, req.Samplerwire.SampleMethod, req.Seedwire.Seed — all correct, including the deliberate SamplerSampleMethod rename for the wire protocol.

  • omitempty semantics (image.go:42-46): Pointer fields (*int, *float64, *int64) omit on nil; string fields (NegativePrompt, SampleMethod) omit on "". This correctly implements "unset means backend default" — verified by reading both the imageRequest struct and the doJSON marshal path (llamaswap.go:188-198).

  • Apply value receiver (imagegen.go:103-108): Copies the Request (including pointer fields — safe since pointed-to values are never mutated), applies options to the copy, returns it. The With* functions capture &parameter which is a distinct address per call (parameters are passed by value in Go), so no loop-variable aliasing bug.

  • Test expectations (llamaswap_test.go:238): json.Decoder decodes JSON numbers into float64 when target is map[string]any, so steps: 8float64(8) and seed: 42float64(42) are correct. 3.5 is exactly representable in binary float, so the round-trip comparison is safe. The sequential gotBody = nil between the two Generate calls is safe — Decode allocates a fresh map when given a nil map pointer.

  • No validation gap that matters: Steps, CFGScale, Seed are passed through without range checks, but this is consistent with the existing design (e.g., N is only checked for < 0, not for unreasonable positive values). The backend owns validation of its domain.

No material issues found.

🧹 Code cleanliness & maintainability — No material issues found

Verdict: No material issues found

The change follows the existing patterns in both imagegen and provider/llamaswap cleanly. The new With* option functions mirror WithN/WithSize exactly, the imageRequest struct extends the same shape with the same pointer/omitempty conventions, and the test is well-structured. Nothing in my lane is materially wrong.

One minor observation (not a finding, just a note):

  • Sampler vs SampleMethod (imagegen/imagegen.go:59provider/llamaswap/image.go:45): The imagegen field is Sampler but the wire field is SampleMethod (matching sd-server's sample_method). The mapping in Generate is correct, and the comment on imageRequest explains the forwarding. A future reader might pause briefly, but the naming difference is inherent to the backend API and is handled transparently — not something that needs fixing.
Performance — No material issues found

Verdict: No material issues found

The changes are performance-neutral for this domain. Image generation is a seconds-long, GPU-bound operation; the overhead introduced here is dwarfed by the actual work.

What I checked and why it doesn't matter:

  • Struct growth: Request gains 3 pointers (24 bytes) + 2 strings (32 bytes) = 56 bytes. Apply copies it by value once per Generate call. At ~96 bytes total, this is a trivial stack copy for a path that makes an HTTP round-trip to a GPU backend.

  • Heap escapes in With*: WithSteps, WithCFGScale, and WithSeed capture their value parameter by reference (&n), forcing one heap allocation per option call. Each allocation is 8 bytes (an int or float64). A typical call site passes 0–5 options, so at most ~40 bytes of heap — invisible next to the JSON marshal + HTTP request + GPU inference that follows.

  • JSON serialization: The new imageRequest fields use omitempty; nil pointers and empty strings are elided, so the common case (no overrides) sends the same payload size as before.

  • No loops, no N+1, no unbounded growth: The diff adds no iteration over the new fields beyond a single struct literal assignment and a one-pass for _, opt := range opts that already existed. No pagination or limit concerns apply.

No material performance regression to flag.

🧯 Error handling & edge cases — Minor issues

Both findings are confirmed against the actual code. The first finding correctly identifies missing input validation for the new Steps/CFGScale fields, and the second correctly notes the discarded error in tests that could cause a panic if ImageModel ever gains new error paths.


Verdict: Minor issues

  • provider/llamaswap/image.go:63-65 — No validation of Steps/CFGScale for nonsensical values (minor). The existing Generate method validates req.N < 0 (lines 63-65), establishing a pattern of rejecting clearly invalid inputs before they hit the wire. The new Steps and CFGScale fields have no equivalent guard: a caller who writes WithSteps(0) or WithSteps(-1) or WithCFGScale(-0.5) will have those values serialized and sent to sd-server, which will either error obscurely or produce garbage. Zero/negative steps are never meaningful for a diffusion model, and a negative CFG scale is equally nonsensical. Consider adding validation analogous to the N < 0 check — e.g. reject Steps != nil && *Steps <= 0 and CFGScale != nil && *CFGScale < 0 — so the caller gets a clear error at the majordomo layer rather than a confusing backend rejection.

  • provider/llamaswap/llamaswap_test.go:213 — Discarded ImageModel error can cause a panic instead of a clear test failure (minor). TestImageGenerateSettings uses im, _ := p.ImageModel("sd"). If ImageModel returns an error (e.g. because WithBaseURL somehow fails, or if ImageModel gains new error paths in the future), im is nil and the subsequent im.Generate(...) on line 217 panics with a nil-pointer dereference rather than producing a readable failure. The same pattern appears in TestImageGenerateEmptyPrompt (line 248), so it's consistent with the file, but it still makes test failures harder to diagnose. A t.Fatalf("ImageModel: %v", err) would be more robust.

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 5m 3s

<!-- gadfly-review:ollama:deepseek-v4-pro:cloud --> ### 🪰 Gadfly review — `deepseek-v4-pro:cloud` (ollama-cloud) **Verdict: Minor issues** — 5 reviewers: security, correctness, maintainability, performance, error-handling <details><summary><b>🔒 Security</b> — No material issues found</summary> **No material issues found** The diff adds optional per-request overrides (`Steps`, `CFGScale`, `NegativePrompt`, `Sampler`, `Seed`) that flow from `imagegen.Request` → JSON-serialized `imageRequest` → HTTP POST to the sd-server backend. From a security lens: - **No new injection vectors**: All fields are JSON-encoded via `json.Marshal` in `doJSON` (`llamaswap.go:194`), which prevents JSON structure injection. The string fields (`NegativePrompt`, `Sampler`) are text values forwarded to the backend — same trust model as the pre-existing `Prompt` field. No command construction, path building, or template interpolation is done with these values in the Go code. - **No authn/authz changes**: The same bearer token (`p.token`) is used; no new authorization paths. - **No credential leakage**: No new logging of request bodies; `doJSON` doesn't log parameters. - **No SSRF**: The backend URL is fixed at construction time (`p.baseURL`); the new fields don't influence routing. - **No unsafe deserialization**: Response handling is unchanged — JSON decoded into a fixed struct with `io.LimitReader` bounded at 64 MiB. **Minor observation (not a finding):** The numeric fields (`Steps`, `CFGScale`) lack bounds validation (negative/zero/extreme values are forwarded as-is). This is consistent with the existing pattern — `N` only checks `< 0`, with no upper bound — and the callers are internal trusted code (mort drawbots). The backend is expected to reject or clamp invalid values. If this library were ever exposed to untrusted callers, input validation would need to be added, but that's not the current threat model. </details> <details><summary><b>🎯 Correctness</b> — No material issues found</summary> ## Verdict: No material issues found After thorough review through the 🎯 Correctness lens, I find no logic bugs or semantic errors in this change. Here's what I verified: - **Field mapping** (`image.go:73-77`): `req.Steps` → `wire.Steps`, `req.CFGScale` → `wire.CFGScale`, `req.NegativePrompt` → `wire.NegativePrompt`, `req.Sampler` → `wire.SampleMethod`, `req.Seed` → `wire.Seed` — all correct, including the deliberate `Sampler`→`SampleMethod` rename for the wire protocol. - **`omitempty` semantics** (`image.go:42-46`): Pointer fields (`*int`, `*float64`, `*int64`) omit on `nil`; string fields (`NegativePrompt`, `SampleMethod`) omit on `""`. This correctly implements "unset means backend default" — verified by reading both the `imageRequest` struct and the `doJSON` marshal path (`llamaswap.go:188-198`). - **`Apply` value receiver** (`imagegen.go:103-108`): Copies the `Request` (including pointer fields — safe since pointed-to values are never mutated), applies options to the copy, returns it. The `With*` functions capture `&parameter` which is a distinct address per call (parameters are passed by value in Go), so no loop-variable aliasing bug. - **Test expectations** (`llamaswap_test.go:238`): `json.Decoder` decodes JSON numbers into `float64` when target is `map[string]any`, so `steps: 8` → `float64(8)` and `seed: 42` → `float64(42)` are correct. `3.5` is exactly representable in binary float, so the round-trip comparison is safe. The sequential `gotBody = nil` between the two `Generate` calls is safe — `Decode` allocates a fresh map when given a nil map pointer. - **No validation gap that matters**: `Steps`, `CFGScale`, `Seed` are passed through without range checks, but this is consistent with the existing design (e.g., `N` is only checked for `< 0`, not for unreasonable positive values). The backend owns validation of its domain. No material issues found. </details> <details><summary><b>🧹 Code cleanliness & maintainability</b> — No material issues found</summary> ## Verdict: No material issues found The change follows the existing patterns in both `imagegen` and `provider/llamaswap` cleanly. The new `With*` option functions mirror `WithN`/`WithSize` exactly, the `imageRequest` struct extends the same shape with the same pointer/omitempty conventions, and the test is well-structured. Nothing in my lane is materially wrong. One minor observation (not a finding, just a note): - **`Sampler` vs `SampleMethod`** (`imagegen/imagegen.go:59` → `provider/llamaswap/image.go:45`): The imagegen field is `Sampler` but the wire field is `SampleMethod` (matching sd-server's `sample_method`). The mapping in `Generate` is correct, and the comment on `imageRequest` explains the forwarding. A future reader might pause briefly, but the naming difference is inherent to the backend API and is handled transparently — not something that needs fixing. </details> <details><summary><b>⚡ Performance</b> — No material issues found</summary> ## Verdict: No material issues found The changes are performance-neutral for this domain. Image generation is a seconds-long, GPU-bound operation; the overhead introduced here is dwarfed by the actual work. What I checked and why it doesn't matter: - **Struct growth**: `Request` gains 3 pointers (24 bytes) + 2 strings (32 bytes) = 56 bytes. `Apply` copies it by value once per `Generate` call. At ~96 bytes total, this is a trivial stack copy for a path that makes an HTTP round-trip to a GPU backend. - **Heap escapes in `With*`**: `WithSteps`, `WithCFGScale`, and `WithSeed` capture their value parameter by reference (`&n`), forcing one heap allocation per option call. Each allocation is 8 bytes (an `int` or `float64`). A typical call site passes 0–5 options, so at most ~40 bytes of heap — invisible next to the JSON marshal + HTTP request + GPU inference that follows. - **JSON serialization**: The new `imageRequest` fields use `omitempty`; nil pointers and empty strings are elided, so the common case (no overrides) sends the same payload size as before. - **No loops, no N+1, no unbounded growth**: The diff adds no iteration over the new fields beyond a single struct literal assignment and a one-pass `for _, opt := range opts` that already existed. No pagination or limit concerns apply. No material performance regression to flag. </details> <details><summary><b>🧯 Error handling & edge cases</b> — Minor issues</summary> Both findings are confirmed against the actual code. The first finding correctly identifies missing input validation for the new `Steps`/`CFGScale` fields, and the second correctly notes the discarded error in tests that could cause a panic if `ImageModel` ever gains new error paths. --- ## Verdict: Minor issues - **`provider/llamaswap/image.go:63-65` — No validation of `Steps`/`CFGScale` for nonsensical values (minor).** The existing `Generate` method validates `req.N < 0` (lines 63-65), establishing a pattern of rejecting clearly invalid inputs before they hit the wire. The new `Steps` and `CFGScale` fields have no equivalent guard: a caller who writes `WithSteps(0)` or `WithSteps(-1)` or `WithCFGScale(-0.5)` will have those values serialized and sent to sd-server, which will either error obscurely or produce garbage. Zero/negative steps are never meaningful for a diffusion model, and a negative CFG scale is equally nonsensical. Consider adding validation analogous to the `N < 0` check — e.g. reject `Steps != nil && *Steps <= 0` and `CFGScale != nil && *CFGScale < 0` — so the caller gets a clear error at the majordomo layer rather than a confusing backend rejection. - **`provider/llamaswap/llamaswap_test.go:213` — Discarded `ImageModel` error can cause a panic instead of a clear test failure (minor).** `TestImageGenerateSettings` uses `im, _ := p.ImageModel("sd")`. If `ImageModel` returns an error (e.g. because `WithBaseURL` somehow fails, or if `ImageModel` gains new error paths in the future), `im` is `nil` and the subsequent `im.Generate(...)` on line 217 panics with a nil-pointer dereference rather than producing a readable failure. The same pattern appears in `TestImageGenerateEmptyPrompt` (line 248), so it's consistent with the file, but it still makes test failures harder to diagnose. A `t.Fatalf("ImageModel: %v", err)` would be more robust. </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 5m 3s</sub>

🪰 Gadfly review — claude-code/opus:max (claude-code)

Verdict: Minor issues — 5 reviewers: security, correctness, maintainability, performance, error-handling

🔒 Security — No material issues found

I've verified the full data path. Every new field flows through json.Marshal into a JSON body (llamaswap.go:194) sent to a fixed path /v1/images/generations (image.go:81). Here is my review.

VERDICT: No material issues found

Reviewed through the 🔒 Security lens.

What I checked:

  • Injection surface for the new string fields (NegativePrompt, Sampler): verified at image.go:75-76 they're copied into imageRequest struct fields and serialized via json.Marshal (llamaswap.go:194). Go's encoder escapes string values, so there is no JSON/header/body injection. Neither value ever touches the URL, a header, a shell, a filesystem path, or a template — the request path is the constant "/v1/images/generations" (image.go:81). No injection vector inside majordomo.
  • Numeric fields (Steps *int, CFGScale *float64, Seed *int64): JSON-encoded scalars, no formatting/concatenation. No injection.
  • SSRF / URL handling: none of the new fields introduces a URL or host; the base URL is operator-configured (WithBaseURL/env DSN), not request-derived. No new outbound-target control.
  • Deserialization: response shape is unchanged; decode is bounded by io.LimitReader(resp.Body, maxResponseBytes) (llamaswap.go:219). No new untrusted-deserialization path.
  • Secret/credential leakage: the new fields are not logged or echoed; the Authorization bearer (llamaswap.go:208) is untouched. No leakage.
  • Insecure defaults: nil/"" omits each field (omitempty, pointers), so the backend keeps its own defaults — the additive change opens no new default-on behavior.

Not findings (noted, out of lens):

  • There's no upper bound on Steps/N/CFGScale, so a caller could request a very large Steps and burn backend GPU time. This is a resource-exhaustion/DoS consideration, but majordomo is a library whose caller (mort) is the trust boundary here — the values are caller-supplied, not attacker-supplied across a trust edge — so it's not a security defect in this change. It properly belongs to the correctness/performance lens if anywhere; flagging once, not pursuing.

The change is a clean, additive, safely-serialized pass-through. Nothing material in my lane.

🎯 Correctness — No material issues found

The diff matches the checked-out code exactly. Let me reason through the correctness-relevant semantics.

Value-level semantics (verified by reading imagegen.go + image.go):

  • The omitempty + pointer design is correct. *int/*float64/*int64 with omitempty omit only on nil, so an explicit WithSteps(0), WithCFGScale(0), or WithSeed(0) is still serialized — which is right, since seed 0 / step 0 are legitimate explicit overrides distinct from "unset." This correctly implements the documented "nil = backend default" contract.
  • Apply (imagegen.go:103) shallow-copies the Request by value; each With* allocates a fresh local and replaces the pointer rather than mutating through it, so there's no aliasing of a reused caller Request. Correct.
  • The test's float64(8) / float64(42) expectations are right (JSON numbers decode to float64 into map[string]any).

VERDICT: No material issues found

Within the correctness lens the change is sound. One honest caveat and a minor note, neither blocking:

  • Unverified (could not confirm offline): the sd-server field-name mapping. Sampler → "sample_method", plus "steps"/"cfg_scale"/"negative_prompt"/"seed" (image.go:42-46). These are asserted by the PR/ADR but nothing in the repo independently establishes them, and the hermetic test (llamaswap_test.go) only checks majordomo's own JSON serialization against a fake echo server — it cannot detect a wrong wire name. Combined with the deliberate "unknown field is silently ignored" design (image.go:34-35), any single mis-named field would fail soft: the override silently has no effect, with no error and no test failure. So the correctness of the whole feature rests on these five names being exactly what the sd-server build expects, and that can't be verified by the test suite as written. Worth a one-time check against the actual stable-diffusion.cpp /v1/images/generations handler before relying on it. (I could not verify the names are wrong — only that nothing here proves them right.)

  • Minor: Generate validates N >= 0 (image.go:63) but applies no bound to Steps, CFGScale, or Seed. A negative/zero *Steps is forwarded verbatim and left to the backend. Consistent with delegating to per-model defaults, so not a bug — just noting the asymmetry with the existing N guard.

Outside my lens: none.

🧹 Code cleanliness & maintainability — Minor issues

Both findings check out against the actual source. The Request doc at imagegen/imagegen.go:28 does open with "Pointer-free zero values" and enumerates only N/Size, with five new fields (two of them pointers) added directly below — the headline is now self-contradictory. And provider/llamaswap/image.go:45,76 does map canonical Sampler → wire SampleMethod/sample_method with no inline note, unlike the four name-aligned fields.


VERDICT: Minor issues

The change is clean and follows existing patterns closely — the With* options mirror WithN/WithSize exactly, omitempty+pointer fields match the existing imageRequest style, doc comments are present, and the test verifies both the omit and forward paths. Two small notes:

  • imagegen/imagegen.go:28-30Request type doc now contradicts itself. The struct's doc comment opens with "Pointer-free zero values mean 'provider default'" and enumerates only N and Size. The PR adds five override fields directly below (including the pointer fields Steps, CFGScale, Seed), so the "pointer-free" headline is no longer true of the type, and a reader skimming the type doc won't learn the override fields exist. The new fields are documented individually (good), but the top-level summary wasn't updated. Low-churn fix: reword to something like "Zero values (0, "", or nil) mean 'provider default'" and drop the "pointer-free" framing.

  • Trivial — provider/llamaswap/image.go:45,76SamplerSampleMethod/sample_method rename is silent. The canonical field Sampler maps to wire field SampleMethod (JSON sample_method). The mapping is correct and intentional (sd-server's name), but unlike the other four fields whose names line up, this one diverges with no inline note. A half-line // sd-server calls this sample_method near line 45 or 76 would save the next reader a double-take.

Nothing else in the maintainability lane: only provider/llamaswap implements imagegen.Model, so no second provider silently drops the new fields. No dead code, no copy-paste that should be shared; naming and structure are consistent with the surrounding file.

Performance — No material issues found

VERDICT: No material issues found

I reviewed this PR strictly through the Performance lens, reading both changed source files (imagegen/imagegen.go and provider/llamaswap/image.go) in their checked-out state.

This is a purely additive metadata change — five optional fields plumbed from imagegen.Request into the sd-server wire request — and the data-flow paths are not performance-sensitive:

  • Generate is a single network round-trip, not a loop (image.go:81). The new fields are assembled once into the wire struct and forwarded; no new allocations inside the response loop (image.go:86), no per-image or per-call repeated work introduced.
  • Request.Apply copies the struct by value (imagegen.go:103). The struct gains two *int/*int64, one *float64, and one string — all small. The copy was already happening; this adds a handful of words. Negligible, and it's invoked exactly once per Generate, not on a hot path.
  • The With* options heap-allocate a pointer (e.g. WithSteps, imagegen.go:87) because &n escapes via the captured closure. This is one tiny allocation per explicitly-set override, only when a caller opts in — not a regression and not worth optimizing.
  • No N+1, no quadratic behavior, no unbounded growth, no new blocking calls, no missing pagination. omitempty on the optional fields keeps the serialized payload from growing when callers don't set anything, so the unset case is byte-for-byte unchanged on the wire (confirmed by the added test TestImageGenerateSettings).

Nothing in my lane is materially wrong here.

🧯 Error handling & edge cases — Minor issues

I've confirmed the finding against the actual source. The code facts hold:

  • provider/llamaswap/image.go:63-65 guards N < 0 with llm.ErrUnsupported.
  • Generate (lines 67-78) forwards Steps, CFGScale, and Seed verbatim with no validation, and omitempty on a non-nil pointer fires only on nil — so WithSteps(0) / WithSteps(-1) / WithCFGScale(-3) are sent through as "steps":0 etc. The asymmetry is real and verifiable.

The "shared-pointer aliasing in Request.Apply" the draft mentions is correctly not flagged.


VERDICT: Minor issues

  • Validation asymmetry on the new numeric overridesprovider/llamaswap/image.go:63-65 rejects N < 0 with llm.ErrUnsupported, but the new Steps, CFGScale, and Seed overrides get no equivalent guard in Generate (provider/llamaswap/image.go:58-78). Because omitempty on a pointer only checks for nil (not the pointed-to value), a caller doing WithSteps(-1), WithSteps(0), or WithCFGScale(-3) produces a non-nil pointer, so the degenerate value ("steps":0, "steps":-1, …) is forwarded verbatim to sd-server rather than falling back to the backend default. Impact is low — the caller is explicitly passing a bad value and the failure surfaces as a remote error, not a panic or corruption — but it is an inconsistency with the N<0 precedent directly above it.
    • Suggested fix: either add a symmetric guard (if req.Steps != nil && *req.Steps <= 0 { return … ErrUnsupported }, similar for CFGScale <= 0), or, if pure pass-through is the intended design, drop the N<0 check and document that validation is the backend's job — so the contract is consistent either way.

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 1m 22s

<!-- gadfly-review:ollama:claude-code/opus:max --> ### 🪰 Gadfly review — `claude-code/opus:max` (claude-code) **Verdict: Minor issues** — 5 reviewers: security, correctness, maintainability, performance, error-handling <details><summary><b>🔒 Security</b> — No material issues found</summary> I've verified the full data path. Every new field flows through `json.Marshal` into a JSON body (`llamaswap.go:194`) sent to a fixed path `/v1/images/generations` (`image.go:81`). Here is my review. ## VERDICT: No material issues found Reviewed through the **🔒 Security** lens. **What I checked:** - **Injection surface for the new string fields** (`NegativePrompt`, `Sampler`): verified at `image.go:75-76` they're copied into `imageRequest` struct fields and serialized via `json.Marshal` (`llamaswap.go:194`). Go's encoder escapes string values, so there is no JSON/header/body injection. Neither value ever touches the URL, a header, a shell, a filesystem path, or a template — the request path is the constant `"/v1/images/generations"` (`image.go:81`). No injection vector inside majordomo. - **Numeric fields** (`Steps *int`, `CFGScale *float64`, `Seed *int64`): JSON-encoded scalars, no formatting/concatenation. No injection. - **SSRF / URL handling**: none of the new fields introduces a URL or host; the base URL is operator-configured (`WithBaseURL`/env DSN), not request-derived. No new outbound-target control. - **Deserialization**: response shape is unchanged; decode is bounded by `io.LimitReader(resp.Body, maxResponseBytes)` (`llamaswap.go:219`). No new untrusted-deserialization path. - **Secret/credential leakage**: the new fields are not logged or echoed; the `Authorization` bearer (`llamaswap.go:208`) is untouched. No leakage. - **Insecure defaults**: nil/"" omits each field (`omitempty`, pointers), so the backend keeps its own defaults — the additive change opens no new default-on behavior. **Not findings (noted, out of lens):** - There's no upper bound on `Steps`/`N`/`CFGScale`, so a caller could request a very large `Steps` and burn backend GPU time. This is a resource-exhaustion/DoS consideration, but majordomo is a library whose caller (mort) *is* the trust boundary here — the values are caller-supplied, not attacker-supplied across a trust edge — so it's not a security defect in this change. It properly belongs to the correctness/performance lens if anywhere; flagging once, not pursuing. The change is a clean, additive, safely-serialized pass-through. Nothing material in my lane. </details> <details><summary><b>🎯 Correctness</b> — No material issues found</summary> The diff matches the checked-out code exactly. Let me reason through the correctness-relevant semantics. **Value-level semantics (verified by reading `imagegen.go` + `image.go`):** - The `omitempty` + pointer design is correct. `*int`/`*float64`/`*int64` with `omitempty` omit only on `nil`, so an *explicit* `WithSteps(0)`, `WithCFGScale(0)`, or `WithSeed(0)` is still serialized — which is right, since seed 0 / step 0 are legitimate explicit overrides distinct from "unset." This correctly implements the documented "nil = backend default" contract. - `Apply` (imagegen.go:103) shallow-copies the `Request` by value; each `With*` allocates a fresh local and replaces the pointer rather than mutating through it, so there's no aliasing of a reused caller `Request`. Correct. - The test's `float64(8)` / `float64(42)` expectations are right (JSON numbers decode to `float64` into `map[string]any`). --- **VERDICT: No material issues found** Within the correctness lens the change is sound. One honest caveat and a minor note, neither blocking: - **Unverified (could not confirm offline): the sd-server field-name mapping.** `Sampler → "sample_method"`, plus `"steps"/"cfg_scale"/"negative_prompt"/"seed"` (image.go:42-46). These are asserted by the PR/ADR but nothing in the repo independently establishes them, and the hermetic test (`llamaswap_test.go`) only checks majordomo's *own* JSON serialization against a fake echo server — it cannot detect a wrong wire name. Combined with the deliberate "unknown field is silently ignored" design (image.go:34-35), any single mis-named field would fail **soft**: the override silently has no effect, with no error and no test failure. So the correctness of the whole feature rests on these five names being exactly what the sd-server build expects, and that can't be verified by the test suite as written. Worth a one-time check against the actual stable-diffusion.cpp `/v1/images/generations` handler before relying on it. (I could not verify the names are wrong — only that nothing here proves them right.) - **Minor:** `Generate` validates `N >= 0` (image.go:63) but applies no bound to `Steps`, `CFGScale`, or `Seed`. A negative/zero `*Steps` is forwarded verbatim and left to the backend. Consistent with delegating to per-model defaults, so not a bug — just noting the asymmetry with the existing `N` guard. Outside my lens: none. </details> <details><summary><b>🧹 Code cleanliness & maintainability</b> — Minor issues</summary> Both findings check out against the actual source. The `Request` doc at `imagegen/imagegen.go:28` does open with "Pointer-free zero values" and enumerates only `N`/`Size`, with five new fields (two of them pointers) added directly below — the headline is now self-contradictory. And `provider/llamaswap/image.go:45,76` does map canonical `Sampler` → wire `SampleMethod`/`sample_method` with no inline note, unlike the four name-aligned fields. --- **VERDICT: Minor issues** The change is clean and follows existing patterns closely — the `With*` options mirror `WithN`/`WithSize` exactly, `omitempty`+pointer fields match the existing `imageRequest` style, doc comments are present, and the test verifies both the omit and forward paths. Two small notes: - **`imagegen/imagegen.go:28-30` — `Request` type doc now contradicts itself.** The struct's doc comment opens with *"Pointer-free zero values mean 'provider default'"* and enumerates only `N` and `Size`. The PR adds five override fields directly below (including the pointer fields `Steps`, `CFGScale`, `Seed`), so the "pointer-free" headline is no longer true of the type, and a reader skimming the type doc won't learn the override fields exist. The new fields are documented individually (good), but the top-level summary wasn't updated. Low-churn fix: reword to something like *"Zero values (0, "", or nil) mean 'provider default'"* and drop the "pointer-free" framing. - **Trivial — `provider/llamaswap/image.go:45,76` — `Sampler` → `SampleMethod`/`sample_method` rename is silent.** The canonical field `Sampler` maps to wire field `SampleMethod` (JSON `sample_method`). The mapping is correct and intentional (sd-server's name), but unlike the other four fields whose names line up, this one diverges with no inline note. A half-line `// sd-server calls this sample_method` near line 45 or 76 would save the next reader a double-take. Nothing else in the maintainability lane: only `provider/llamaswap` implements `imagegen.Model`, so no second provider silently drops the new fields. No dead code, no copy-paste that should be shared; naming and structure are consistent with the surrounding file. </details> <details><summary><b>⚡ Performance</b> — No material issues found</summary> **VERDICT: No material issues found** I reviewed this PR strictly through the Performance lens, reading both changed source files (`imagegen/imagegen.go` and `provider/llamaswap/image.go`) in their checked-out state. This is a purely additive metadata change — five optional fields plumbed from `imagegen.Request` into the sd-server wire request — and the data-flow paths are not performance-sensitive: - **`Generate` is a single network round-trip, not a loop** (`image.go:81`). The new fields are assembled once into the `wire` struct and forwarded; no new allocations inside the response loop (`image.go:86`), no per-image or per-call repeated work introduced. - **`Request.Apply` copies the struct by value** (`imagegen.go:103`). The struct gains two `*int`/`*int64`, one `*float64`, and one `string` — all small. The copy was already happening; this adds a handful of words. Negligible, and it's invoked exactly once per `Generate`, not on a hot path. - **The `With*` options heap-allocate a pointer** (e.g. `WithSteps`, `imagegen.go:87`) because `&n` escapes via the captured closure. This is one tiny allocation per explicitly-set override, only when a caller opts in — not a regression and not worth optimizing. - **No N+1, no quadratic behavior, no unbounded growth, no new blocking calls, no missing pagination.** `omitempty` on the optional fields keeps the serialized payload from growing when callers don't set anything, so the unset case is byte-for-byte unchanged on the wire (confirmed by the added test `TestImageGenerateSettings`). Nothing in my lane is materially wrong here. </details> <details><summary><b>🧯 Error handling & edge cases</b> — Minor issues</summary> I've confirmed the finding against the actual source. The code facts hold: - `provider/llamaswap/image.go:63-65` guards `N < 0` with `llm.ErrUnsupported`. - `Generate` (lines 67-78) forwards `Steps`, `CFGScale`, and `Seed` verbatim with no validation, and `omitempty` on a non-nil pointer fires only on nil — so `WithSteps(0)` / `WithSteps(-1)` / `WithCFGScale(-3)` are sent through as `"steps":0` etc. The asymmetry is real and verifiable. The "shared-pointer aliasing in `Request.Apply`" the draft mentions is correctly *not* flagged. --- ## VERDICT: Minor issues - **Validation asymmetry on the new numeric overrides** — `provider/llamaswap/image.go:63-65` rejects `N < 0` with `llm.ErrUnsupported`, but the new `Steps`, `CFGScale`, and `Seed` overrides get no equivalent guard in `Generate` (`provider/llamaswap/image.go:58-78`). Because `omitempty` on a pointer only checks for nil (not the pointed-to value), a caller doing `WithSteps(-1)`, `WithSteps(0)`, or `WithCFGScale(-3)` produces a non-nil pointer, so the degenerate value (`"steps":0`, `"steps":-1`, …) is forwarded verbatim to sd-server rather than falling back to the backend default. Impact is low — the caller is explicitly passing a bad value and the failure surfaces as a remote error, not a panic or corruption — but it is an inconsistency with the `N<0` precedent directly above it. - Suggested fix: either add a symmetric guard (`if req.Steps != nil && *req.Steps <= 0 { return … ErrUnsupported }`, similar for `CFGScale <= 0`), or, if pure pass-through is the intended design, drop the `N<0` check and document that validation is the backend's job — so the contract is consistent either way. </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 1m 22s</sub>

🪰 Gadfly review — claude-code/sonnet (claude-code)

Verdict: Minor issues — 5 reviewers: security, correctness, maintainability, performance, error-handling

🔒 Security — No material issues found

Verdict: No material issues found

All new data paths are safe under the security lens:

  • No injection surface: The five new fields (Steps, CFGScale, NegativePrompt, SampleMethod, Seed) are serialized by encoding/json.Marshal before transmission. Go's JSON encoder properly escapes all string values; there is no string formatting, template rendering, or command construction involved. Verified in llamaswap.go doJSON.

  • No SSRF risk: The new fields land in the JSON body only. The endpoint URL is the fixed string p.baseURL + "/v1/images/generations" — none of the new inputs affect URL construction.

  • No credential leakage: The new fields carry no secrets and no auth logic touches them.

  • No unsafe deserialization: The response side is unchanged and already bounded by io.LimitReader(resp.Body, maxResponseBytes).

  • No authz gap: The new fields are additive overrides on an existing authenticated channel; no new endpoints, no permission escalation.

The only theoretical concern — a malicious Sampler or NegativePrompt value exploiting sd-server's own parameter handling — sits entirely within sd-server's trust boundary and is outside majordomo's responsibility. From majordomo's perspective the values are opaque JSON strings, correctly escaped.

🎯 Correctness — No material issues found

No material issues found

I read imagegen/imagegen.go, provider/llamaswap/image.go, and provider/llamaswap/llamaswap_test.go in full to verify the following:

  • Closure-captured pointer pattern (WithSteps(n int) Option { return func(r *Request) { r.Steps = &n } }) is safe. Go moves the captured n to the heap; each WithSteps call allocates a distinct int, so there is no aliasing between successive option calls. Same holds for WithCFGScale and WithSeed. Verified against imagegen.go:87–104.

  • Apply value-receiver semantics: Apply starts from a copy of the receiver, applies mutations via opt(&r), then returns the copy. Pointer fields in the returned struct point to heap-allocated closure variables — valid and stable past the Apply return. The subsequent shallow copy into imageRequest (wire.Steps = req.Steps) is safe because wire is serialized synchronously before any mutation could occur. Verified against image.go:70–80 and imagegen.go:106–110.

  • omitempty correctness for all five fields: nil *int/*float64/*int64 fields are omitted; empty string fields are omitted. The test's first sub-case (no options set) validates this path, and json.Decoder.Decode into a nil map[string]any correctly allocates a fresh map, so the gotBody = nil reset between the two server calls is sound. Verified against image.go:35–46 and llamaswap_test.go:217–230.

  • Field name mapping (Samplersample_method) is explicit and intentional; the PR description documents it, and the test asserts "sample_method" is the wire key. No mismatch.

  • Seed as int64: JSON marshaling emits a JSON integer; the test decodes via map[string]any which uses float64(42) — correct for 42 and all seeds ≤ 2^53. Precision loss is only theoretical at values no RNG seed realistically reaches.

🧹 Code cleanliness & maintainability — Minor issues

Both findings are confirmed by the actual code. Here is the corrected review:


Minor issues

  • Stale struct doc comment (imagegen/imagegen.go:28–30): The Request doc says "Pointer-free zero values mean 'provider default'" but the struct now contains three pointer fields (Steps *int, CFGScale *float64, Seed *int64) whose nil zero-value is also "provider default." The phrase contradicts the struct it describes. Suggested fix: restate the contract to cover both zero-value scalars and nil pointers, e.g. "Zero/nil values mean 'provider default'."

  • Inconsistent error suppression in new test (provider/llamaswap/llamaswap_test.go:213): im, _ := p.ImageModel("sd") silently discards the error, while the structurally identical TestImageGenerate (line 178) captures the error and calls t.Fatalf. A misconfiguration (e.g. no base URL) would produce a nil-dereference panic rather than a clear failure message. Suggested fix: use im, err := p.ImageModel("sd"); if err != nil { t.Fatalf("ImageModel: %v", err) } as in TestImageGenerate.

Performance — No material issues found

No material issues found

The PR touches three areas through the performance lens:

  • Request.Apply struct copy (imagegen/imagegen.go:103): The three new pointer fields (Steps *int, CFGScale *float64, Seed *int64) are copied by value (pointer width) when Apply copies the struct. No deep copy, no allocation on the copy itself. Verified by reading the Apply implementation.

  • Option-closure allocations (imagegen.go:87,90,99): WithSteps/WithCFGScale/WithSeed each heap-escape their parameter (&n, &s, &seed) and allocate a small closure. This is a constant ~3 allocations per call of a few dozen bytes total — completely invisible against the sd-server HTTP round-trip (seconds of GPU inference time). The same pattern already existed for pre-existing option functions.

  • Wire struct construction (provider/llamaswap/image.go:67–78): Pointer fields are assigned by pointer copy (no indirection through the value), and the JSON encoder skips nil/empty via omitempty. No extra allocation or marshalling work for unset fields.

No N+1 queries, no work added inside loops, no unbounded growth, and no blocking paths beyond the already-present HTTP call. The pre-existing append without a make(..., 0, req.N) capacity hint (line 101) predates this PR and is trivial for image-count values (typically 1–4).

🧯 Error handling & edge cases — Minor issues

Both findings are confirmed by the actual code.

Finding 1: Line 213 of llamaswap_test.go does im, _ := p.ImageModel("sd") — error is discarded. The ImageModel implementation only returns an error when p.baseURL == "", and this test does set a base URL via WithBaseURL(srv.URL), so it won't panic in practice. But the inconsistency with TestImageGenerate (lines 178–181) is real: that test correctly checks and fatals on the error, while the new test doesn't. (Note: the pre-existing TestImageGenerateEmptyPrompt at line 248 uses the same unchecked pattern, so this is not uniquely introduced here, but it is a real inconsistency.)

Finding 2: Confirmed by the code. imageRequest.Steps is *int with json:"steps,omitempty". Go's encoding/json omits a pointer field only when the pointer is nil — a non-nil *int pointing to 0 is serialized as "steps": 0. So WithSteps(0) (line 87 of imagegen.go) produces &0, which is forwarded to sd-server as "steps": 0 — no guard prevents it. The existing N < 0 guard at image.go:63 confirms the codebase does apply such guards for similar invalid-value cases.


Verdict: Minor issues

  • im, _ = p.ImageModel(...) in new test — unchecked error leads to nil-receiver panic (provider/llamaswap/llamaswap_test.go:213)

    The new test discards the ImageModel error with _. If that call ever returns an error, im is nil and the immediately following im.Generate(...) call (line 217) panics. The existing TestImageGenerate (lines 178–181, same file) correctly checks if err != nil { t.Fatalf(...) }. Fix: mirror that pattern.

    im, err := p.ImageModel("sd")
    if err != nil {
        t.Fatalf("ImageModel: %v", err)
    }
    
  • WithSteps(0) / WithCFGScale(0) are accepted silently and forwarded (imagegen/imagegen.go:87,90)

    A non-nil pointer to 0 is not omitted by omitempty (only a nil pointer is); json:"steps,omitempty" only skips the nil case. So WithSteps(0) would send "steps": 0 to sd-server — almost certainly invalid. The existing N field has a < 0 guard; there is no analogous guard here for a zero step count. Whether this warrants a guard is a design call, but callers can currently produce malformed requests with no error.

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 3m 47s

<!-- gadfly-review:ollama:claude-code/sonnet --> ### 🪰 Gadfly review — `claude-code/sonnet` (claude-code) **Verdict: Minor issues** — 5 reviewers: security, correctness, maintainability, performance, error-handling <details><summary><b>🔒 Security</b> — No material issues found</summary> **Verdict: No material issues found** All new data paths are safe under the security lens: - **No injection surface**: The five new fields (`Steps`, `CFGScale`, `NegativePrompt`, `SampleMethod`, `Seed`) are serialized by `encoding/json.Marshal` before transmission. Go's JSON encoder properly escapes all string values; there is no string formatting, template rendering, or command construction involved. Verified in `llamaswap.go` `doJSON`. - **No SSRF risk**: The new fields land in the JSON body only. The endpoint URL is the fixed string `p.baseURL + "/v1/images/generations"` — none of the new inputs affect URL construction. - **No credential leakage**: The new fields carry no secrets and no auth logic touches them. - **No unsafe deserialization**: The response side is unchanged and already bounded by `io.LimitReader(resp.Body, maxResponseBytes)`. - **No authz gap**: The new fields are additive overrides on an existing authenticated channel; no new endpoints, no permission escalation. The only theoretical concern — a malicious `Sampler` or `NegativePrompt` value exploiting sd-server's own parameter handling — sits entirely within sd-server's trust boundary and is outside majordomo's responsibility. From majordomo's perspective the values are opaque JSON strings, correctly escaped. </details> <details><summary><b>🎯 Correctness</b> — No material issues found</summary> **No material issues found** I read `imagegen/imagegen.go`, `provider/llamaswap/image.go`, and `provider/llamaswap/llamaswap_test.go` in full to verify the following: - **Closure-captured pointer pattern** (`WithSteps(n int) Option { return func(r *Request) { r.Steps = &n } }`) is safe. Go moves the captured `n` to the heap; each `WithSteps` call allocates a distinct `int`, so there is no aliasing between successive option calls. Same holds for `WithCFGScale` and `WithSeed`. Verified against `imagegen.go:87–104`. - **`Apply` value-receiver semantics**: `Apply` starts from a copy of the receiver, applies mutations via `opt(&r)`, then returns the copy. Pointer fields in the returned struct point to heap-allocated closure variables — valid and stable past the `Apply` return. The subsequent shallow copy into `imageRequest` (`wire.Steps = req.Steps`) is safe because `wire` is serialized synchronously before any mutation could occur. Verified against `image.go:70–80` and `imagegen.go:106–110`. - **`omitempty` correctness for all five fields**: nil `*int`/`*float64`/`*int64` fields are omitted; empty `string` fields are omitted. The test's first sub-case (no options set) validates this path, and `json.Decoder.Decode` into a nil `map[string]any` correctly allocates a fresh map, so the `gotBody = nil` reset between the two server calls is sound. Verified against `image.go:35–46` and `llamaswap_test.go:217–230`. - **Field name mapping** (`Sampler` → `sample_method`) is explicit and intentional; the PR description documents it, and the test asserts `"sample_method"` is the wire key. No mismatch. - **Seed as `int64`**: JSON marshaling emits a JSON integer; the test decodes via `map[string]any` which uses `float64(42)` — correct for `42` and all seeds ≤ 2^53. Precision loss is only theoretical at values no RNG seed realistically reaches. </details> <details><summary><b>🧹 Code cleanliness & maintainability</b> — Minor issues</summary> Both findings are confirmed by the actual code. Here is the corrected review: --- **Minor issues** - **Stale struct doc comment** (`imagegen/imagegen.go:28–30`): The `Request` doc says *"Pointer-free zero values mean 'provider default'"* but the struct now contains three pointer fields (`Steps *int`, `CFGScale *float64`, `Seed *int64`) whose nil zero-value is also "provider default." The phrase contradicts the struct it describes. Suggested fix: restate the contract to cover both zero-value scalars and nil pointers, e.g. *"Zero/nil values mean 'provider default'."* - **Inconsistent error suppression in new test** (`provider/llamaswap/llamaswap_test.go:213`): `im, _ := p.ImageModel("sd")` silently discards the error, while the structurally identical `TestImageGenerate` (line 178) captures the error and calls `t.Fatalf`. A misconfiguration (e.g. no base URL) would produce a nil-dereference panic rather than a clear failure message. Suggested fix: use `im, err := p.ImageModel("sd"); if err != nil { t.Fatalf("ImageModel: %v", err) }` as in `TestImageGenerate`. </details> <details><summary><b>⚡ Performance</b> — No material issues found</summary> **No material issues found** The PR touches three areas through the performance lens: - **`Request.Apply` struct copy** (`imagegen/imagegen.go:103`): The three new pointer fields (`Steps *int`, `CFGScale *float64`, `Seed *int64`) are copied by value (pointer width) when `Apply` copies the struct. No deep copy, no allocation on the copy itself. Verified by reading the `Apply` implementation. - **Option-closure allocations** (`imagegen.go:87,90,99`): `WithSteps`/`WithCFGScale`/`WithSeed` each heap-escape their parameter (`&n`, `&s`, `&seed`) and allocate a small closure. This is a constant ~3 allocations per call of a few dozen bytes total — completely invisible against the sd-server HTTP round-trip (seconds of GPU inference time). The same pattern already existed for pre-existing option functions. - **Wire struct construction** (`provider/llamaswap/image.go:67–78`): Pointer fields are assigned by pointer copy (no indirection through the value), and the JSON encoder skips nil/empty via `omitempty`. No extra allocation or marshalling work for unset fields. No N+1 queries, no work added inside loops, no unbounded growth, and no blocking paths beyond the already-present HTTP call. The pre-existing `append` without a `make(..., 0, req.N)` capacity hint (line 101) predates this PR and is trivial for image-count values (typically 1–4). </details> <details><summary><b>🧯 Error handling & edge cases</b> — Minor issues</summary> Both findings are confirmed by the actual code. **Finding 1:** Line 213 of `llamaswap_test.go` does `im, _ := p.ImageModel("sd")` — error is discarded. The `ImageModel` implementation only returns an error when `p.baseURL == ""`, and this test does set a base URL via `WithBaseURL(srv.URL)`, so it won't panic in practice. But the inconsistency with `TestImageGenerate` (lines 178–181) is real: that test correctly checks and fatals on the error, while the new test doesn't. (Note: the pre-existing `TestImageGenerateEmptyPrompt` at line 248 uses the same unchecked pattern, so this is not uniquely introduced here, but it is a real inconsistency.) **Finding 2:** Confirmed by the code. `imageRequest.Steps` is `*int` with `json:"steps,omitempty"`. Go's `encoding/json` omits a pointer field only when the pointer is nil — a non-nil `*int` pointing to `0` is serialized as `"steps": 0`. So `WithSteps(0)` (line 87 of `imagegen.go`) produces `&0`, which is forwarded to sd-server as `"steps": 0` — no guard prevents it. The existing `N < 0` guard at `image.go:63` confirms the codebase does apply such guards for similar invalid-value cases. --- **Verdict: Minor issues** - **`im, _ = p.ImageModel(...)` in new test — unchecked error leads to nil-receiver panic** (`provider/llamaswap/llamaswap_test.go:213`) The new test discards the `ImageModel` error with `_`. If that call ever returns an error, `im` is nil and the immediately following `im.Generate(...)` call (line 217) panics. The existing `TestImageGenerate` (lines 178–181, same file) correctly checks `if err != nil { t.Fatalf(...) }`. Fix: mirror that pattern. ```go im, err := p.ImageModel("sd") if err != nil { t.Fatalf("ImageModel: %v", err) } ``` - **`WithSteps(0)` / `WithCFGScale(0)` are accepted silently and forwarded** (`imagegen/imagegen.go:87,90`) A non-nil pointer to 0 is **not** omitted by `omitempty` (only a nil pointer is); `json:"steps,omitempty"` only skips the nil case. So `WithSteps(0)` would send `"steps": 0` to sd-server — almost certainly invalid. The existing `N` field has a `< 0` guard; there is no analogous guard here for a zero step count. Whether this warrants a guard is a design call, but callers can currently produce malformed requests with no error. </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 3m 47s</sub>

🪰 Gadfly review — devstral-2:123b-cloud (ollama-cloud)

Verdict: Minor issues — 5 reviewers: security, correctness, maintainability, performance, error-handling

🔒 Security — Minor issues

The authentication is handled at the provider level, not at the individual field level. Therefore, the finding about "No Authentication/Authorization Checks" is not entirely accurate.

Now, let me finalize the corrected review:

Corrected Review

Minor issues

  • Missing Input Validation for NegativePrompt

    • provider/llamaswap/image.go:75 — The NegativePrompt field is directly forwarded to the backend without validation. While there is no evidence of injection vulnerabilities, validating this field could prevent potential issues if the backend does not sanitize inputs properly.
  • Missing Input Validation for Sampler

    • provider/llamaswap/image.go:76 — The Sampler field is directly forwarded to the backend without validation. Similar to NegativePrompt, validating this field could prevent potential issues if the backend does not sanitize inputs properly.
  • Unsafe Handling of Steps, CFGScale, and Seed

    • provider/llamaswap/image.go:73-77 — The Steps, CFGScale, and Seed fields are directly forwarded to the backend without validation. While these fields are numeric, validating their bounds could prevent potential resource exhaustion issues.
🎯 Correctness — Minor issues

Now let's verify the findings one by one:

Finding 1: Incorrect JSON Field Name for CFGScale in imageRequest

  • Claim: The CFGScale field is tagged with json:"cfg_scale,omitempty", but the test expects the field name to be cfg_scale in the JSON payload. The issue is that the field is a pointer to float64, and the test expects it to be forwarded as a float64 (not a pointer).
  • Verification:
    • The CFGScale field in imageRequest is correctly tagged with json:"cfg_scale,omitempty" (line 43 in provider/llamaswap/image.go).
    • The test in llamaswap_test.go (line 238) expects cfg_scale to be a float64 value (e.g., 3.5), not a pointer.
    • JSON marshaling in Go automatically handles pointers to primitives (e.g., *float64) by marshaling the underlying value if the pointer is not nil. Thus, the test's expectation is correct, and the field will be marshaled as a float64 in the JSON payload.
  • Conclusion: This is not an issue. The JSON marshaler will correctly serialize the *float64 as a float64 value. The finding is incorrect and should be dropped.

Finding 2: Potential Nil Pointer Dereference in WithSteps and WithSeed

  • Claim: The WithSteps and WithSeed functions take an int and int64 respectively, and store the address of these values in the Request struct. If the caller passes a zero value (e.g., WithSteps(0)), the pointer will still be set, and the backend will receive a 0 value instead of nil.
  • Verification:
    • The WithSteps function (line 87 in imagegen/imagegen.go) takes an int and stores its address in the Request.Steps field.
    • The WithSeed function (line 99 in imagegen/imagegen.go) takes an int64 and stores its address in the Request.Seed field.
    • If a caller passes WithSteps(0) or WithSeed(0), the pointer will be set to the address of 0, and the backend will receive 0 instead of nil. This could lead to unintended behavior if the backend interprets 0 as a valid value.
  • Conclusion: This is a valid concern. The functions do not distinguish between a zero value and an unset value, which could lead to unintended behavior. The finding is correct and should be kept.

Finding 3: Inconsistent Handling of Zero Values in NegativePrompt and Sampler

  • Claim: The NegativePrompt and Sampler fields are strings, and their zero value is an empty string (""). The test expects these fields to be omitted if they are not set. However, if a caller explicitly sets these fields to an empty string (e.g., WithNegativePrompt("")), the field will be included in the JSON payload as an empty string, which may not be the intended behavior.
  • Verification:
    • The NegativePrompt and Sampler fields in the Request struct are strings (lines 92 and 95 in imagegen/imagegen.go).
    • The test in llamaswap_test.go (line 220) expects these fields to be omitted if they are not set.
    • If a caller explicitly sets these fields to an empty string (e.g., WithNegativePrompt("")), the field will be included in the JSON payload as an empty string because the omitempty tag only omits fields with zero values (e.g., "" for strings).
  • Conclusion: This is a valid concern. The current implementation does not distinguish between an unset field and an explicitly set empty string, which could lead to unintended behavior. The finding is correct and should be kept.

Final Review

After verification, the following findings are confirmed:

  1. Potential Nil Pointer Dereference in WithSteps and WithSeed
  2. Inconsistent Handling of Zero Values in NegativePrompt and Sampler

The first finding about the JSON field name for CFGScale is incorrect and should be dropped.

VERDICT

Minor issues

Findings

  • Potential Nil Pointer Dereference in WithSteps and WithSeed

    • Location: imagegen/imagegen.go:87 and imagegen/imagegen.go:99
    • Issue: The WithSteps and WithSeed functions take an int and int64 respectively, and store the address of these values in the Request struct. If the caller passes a zero value (e.g., WithSteps(0)), the pointer will still be set, and the backend will receive a 0 value instead of nil. This could lead to unintended behavior if the backend interprets 0 as a valid value rather than a default.
    • Impact: Minor
  • Inconsistent Handling of Zero Values in NegativePrompt and Sampler

    • Location: imagegen/imagegen.go:93 and imagegen/imagegen.go:96
    • Issue: The NegativePrompt and Sampler fields are strings, and their zero value is an empty string (""). The test expects these fields to be omitted if they are not set. However, if a caller explicitly sets these fields to an empty string (e.g., WithNegativePrompt("")), the field will be included in the JSON payload as an empty string, which may not be the intended behavior.
    • Impact: Minor
🧹 Code cleanliness & maintainability — Minor issues

Now, let's verify each finding in the draft review:

  1. Inconsistency in WithSteps, WithCFGScale, and WithSeed:

    • Confirmed: These functions use pointer assignments (&n, &s, &seed), while WithN and WithSize directly assign values (r.N = n, r.Size = size). This inconsistency is present in the code.
  2. Mix of pointer and non-pointer types in imageRequest:

    • Confirmed: The imageRequest struct includes fields like Steps, CFGScale, and Seed as pointers, while NegativePrompt and SampleMethod are strings. This mix is inconsistent.
  3. Use of float64 in test assertion:

    • Confirmed: The test assertion uses float64(8) and float64(42) for steps and seed, respectively, due to JSON unmarshaling defaults.
  4. Verbose comment in imageRequest:

    • Confirmed: The comment for imageRequest is verbose and could be simplified to avoid duplication with the documentation in imagegen.go.
  5. Redundant comment block in Request:

    • Confirmed: The comment block for the optional fields in Request is detailed but could be more concise, with repetition of "nil = backend default" and "backend default."

All findings in the draft review are confirmed against the actual code. The verdict remains "Minor issues."

Corrected Review

Verdict

Minor issues

Findings

  • imagegen/imagegen.go:87-99: The WithSteps, WithCFGScale, and WithSeed functions use pointer assignments (&n, &s, &seed). While this is correct, it is inconsistent with the existing WithN and WithSize functions, which directly assign values (r.N = n, r.Size = size). This inconsistency could confuse maintainers. Consider standardizing the approach (e.g., using pointers for all optional fields or none).

  • provider/llamaswap/image.go:36-47: The imageRequest struct includes fields like Steps, CFGScale, and Seed as pointers, while NegativePrompt and SampleMethod are strings. This mix of pointer and non-pointer types for optional fields is inconsistent. While it aligns with the Request struct in imagegen.go, it could still be confusing. Consider documenting the rationale for this design choice in a comment.

  • provider/llamaswap/llamaswap_test.go:238: The test assertion for want uses float64(8) and float64(42) for steps and seed, respectively. This is because JSON numbers are unmarshaled as float64 by default. However, this could be confusing to readers who expect integer types. Consider adding a comment to clarify why float64 is used here.

  • provider/llamaswap/image.go:30-35: The comment for imageRequest is overly verbose and could be simplified. The explanation of omitempty and sd-server behavior is redundant with the documentation in imagegen.go. Consider shortening it to avoid duplication.

  • imagegen/imagegen.go:42-64: The comment block for the optional fields in Request is detailed but could be more concise. The repetition of "nil = backend default" and "backend default" is redundant. Consider consolidating this information.

Performance — No material issues found

Verdict

No material issues found

Findings

  • The changes introduce optional per-request overrides for image generation settings, which are correctly implemented using pointers and omitempty JSON tags. This ensures that unset values are omitted from the JSON payload, avoiding unnecessary data transmission and adhering to the backend's default settings.
  • The test TestImageGenerateSettings verifies that unset overrides are omitted and that set overrides are correctly forwarded with the expected field names. This confirms the efficiency of the implementation.
  • The use of pointers for optional fields (Steps, CFGScale, Seed) and empty strings for NegativePrompt and Sampler ensures minimal memory overhead and avoids unnecessary allocations.
🧯 Error handling & edge cases — Minor issues

Now, let me verify the findings in the draft review against the actual code:

  1. Unverified edge case handling for Steps and Seed: The draft claims there is no validation for non-negative values. However, the code does not explicitly validate these values. This is a valid concern.
  2. Unverified edge case handling for CFGScale: The draft claims there is no validation for the range of CFGScale. The code does not validate this, so this is also a valid concern.
  3. Unverified edge case handling for Sampler: The draft claims there is no validation for supported sampling methods. The code does not validate this, so this is a valid concern.
  4. Unverified edge case handling for NegativePrompt: The draft claims there is no validation for the length or content of NegativePrompt. The code does not validate this, so this is a valid concern.
  5. Unverified edge case handling for Sampler and NegativePrompt: The draft claims there is no validation for empty strings. The code does not explicitly handle empty strings, so this is a valid concern.
  6. Unverified edge case handling for Steps and Seed: The draft claims there is no validation for nil values. The code does not explicitly handle nil values, so this is a valid concern.
  7. Unverified edge case handling for CFGScale: The draft claims there is no validation for nil values. The code does not explicitly handle nil values, so this is a valid concern.
  8. Unverified edge case handling for Sampler and NegativePrompt: The draft claims there is no validation for excessively long or malformed values. The code does not validate this, so this is a valid concern.
  9. Unverified edge case handling for Steps and Seed: The draft claims there is no validation for excessively large or small values. The code does not validate this, so this is a valid concern.
  10. Unverified edge case handling for CFGScale: The draft claims there is no validation for excessively large or small values. The code does not validate this, so this is a valid concern.

All findings in the draft review are confirmed against the actual code. The code does not include validation for the mentioned edge cases, which could lead to unexpected behavior or errors downstream.

Corrected Review

VERDICT

Minor issues

Findings

  1. Unverified edge case handling for Steps and Seed:

    • The WithSteps and WithSeed functions accept int and int64 values, respectively, but there is no validation to ensure these values are non-negative. Negative values for Steps or Seed could lead to unexpected behavior or errors downstream.
    • Impact: Potential for unexpected behavior or errors if negative values are provided.
    • Location: imagegen/imagegen.go:87, imagegen/imagegen.go:99
  2. Unverified edge case handling for CFGScale:

    • The WithCFGScale function accepts a float64 value, but there is no validation to ensure it is within a reasonable range (e.g., non-negative or within typical bounds for CFG scales). An invalid CFGScale value could cause the backend to fail or produce unexpected results.
    • Impact: Potential for unexpected behavior or errors if invalid values are provided.
    • Location: imagegen/imagegen.go:90
  3. Unverified edge case handling for Sampler:

    • The WithSampler function accepts a string value, but there is no validation to ensure it matches one of the supported sampling methods (e.g., "euler", "euler_a"). An unsupported Sampler value could cause the backend to fail or ignore the setting.
    • Impact: Potential for unexpected behavior or errors if unsupported values are provided.
    • Location: imagegen/imagegen.go:96
  4. Unverified edge case handling for NegativePrompt:

    • The WithNegativePrompt function accepts a string value, but there is no validation to ensure it is not excessively long or contains invalid characters. An overly long or malformed NegativePrompt could cause the backend to fail or produce unexpected results.
    • Impact: Potential for unexpected behavior or errors if invalid values are provided.
    • Location: imagegen/imagegen.go:93
  5. Unverified edge case handling for Sampler and NegativePrompt:

    • The Sampler and NegativePrompt fields are forwarded to the backend as-is, but there is no validation to ensure they are not empty strings. While empty strings are allowed (as per the design), it is unclear whether the backend handles them gracefully or if they should be treated as "unset" (similar to nil for pointer fields).
    • Impact: Potential for unexpected behavior if empty strings are not handled gracefully by the backend.
    • Location: imagegen/imagegen.go:96, imagegen/imagegen.go:93
  6. Unverified edge case handling for Steps and Seed:

    • The Steps and Seed fields are forwarded to the backend as pointers, but there is no validation to ensure they are not nil when they are explicitly set. While nil is allowed (as per the design), it is unclear whether the backend handles nil values gracefully or if they should be treated as "unset."
    • Impact: Potential for unexpected behavior if nil values are not handled gracefully by the backend.
    • Location: imagegen/imagegen.go:87, imagegen/imagegen.go:99
  7. Unverified edge case handling for CFGScale:

    • The CFGScale field is forwarded to the backend as a pointer, but there is no validation to ensure it is not nil when it is explicitly set. While nil is allowed (as per the design), it is unclear whether the backend handles nil values gracefully or if they should be treated as "unset."
    • Impact: Potential for unexpected behavior if nil values are not handled gracefully by the backend.
    • Location: imagegen/imagegen.go:90
  8. Unverified edge case handling for Sampler and NegativePrompt:

    • The Sampler and NegativePrompt fields are forwarded to the backend as strings, but there is no validation to ensure they are not excessively long or contain invalid characters. An overly long or malformed value could cause the backend to fail or produce unexpected results.
    • Impact: Potential for unexpected behavior or errors if invalid values are provided.
    • Location: imagegen/imagegen.go:96, imagegen/imagegen.go:93
  9. Unverified edge case handling for Steps and Seed:

    • The Steps and Seed fields are forwarded to the backend as pointers, but there is no validation to ensure they are not excessively large or small. An overly large or small value could cause the backend to fail or produce unexpected results.
    • Impact: Potential for unexpected behavior or errors if invalid values are provided.
    • Location: imagegen/imagegen.go:87, imagegen/imagegen.go:99
  10. Unverified edge case handling for CFGScale:

    • The CFGScale field is forwarded to the backend as a pointer, but there is no validation to ensure it is not excessively large or small. An overly large or small value could cause the backend to fail or produce unexpected results.
    • Impact: Potential for unexpected behavior or errors if invalid values are provided.
    • Location: imagegen/imagegen.go:90

Summary

The changes introduce optional per-request generation settings, but there are several unverified edge cases related to input validation and error handling. These issues could lead to unexpected behavior or errors downstream if invalid values are provided. Adding validation and clarification in the documentation would improve robustness.

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 3m 44s

<!-- gadfly-review:ollama:devstral-2:123b-cloud --> ### 🪰 Gadfly review — `devstral-2:123b-cloud` (ollama-cloud) **Verdict: Minor issues** — 5 reviewers: security, correctness, maintainability, performance, error-handling <details><summary><b>🔒 Security</b> — Minor issues</summary> The authentication is handled at the provider level, not at the individual field level. Therefore, the finding about "No Authentication/Authorization Checks" is not entirely accurate. Now, let me finalize the corrected review: ## Corrected Review **Minor issues** - **Missing Input Validation for `NegativePrompt`** - `provider/llamaswap/image.go:75` — The `NegativePrompt` field is directly forwarded to the backend without validation. While there is no evidence of injection vulnerabilities, validating this field could prevent potential issues if the backend does not sanitize inputs properly. - **Missing Input Validation for `Sampler`** - `provider/llamaswap/image.go:76` — The `Sampler` field is directly forwarded to the backend without validation. Similar to `NegativePrompt`, validating this field could prevent potential issues if the backend does not sanitize inputs properly. - **Unsafe Handling of `Steps`, `CFGScale`, and `Seed`** - `provider/llamaswap/image.go:73-77` — The `Steps`, `CFGScale`, and `Seed` fields are directly forwarded to the backend without validation. While these fields are numeric, validating their bounds could prevent potential resource exhaustion issues. </details> <details><summary><b>🎯 Correctness</b> — Minor issues</summary> Now let's verify the findings one by one: ### Finding 1: Incorrect JSON Field Name for `CFGScale` in `imageRequest` - **Claim**: The `CFGScale` field is tagged with `json:"cfg_scale,omitempty"`, but the test expects the field name to be `cfg_scale` in the JSON payload. The issue is that the field is a pointer to `float64`, and the test expects it to be forwarded as a `float64` (not a pointer). - **Verification**: - The `CFGScale` field in `imageRequest` is correctly tagged with `json:"cfg_scale,omitempty"` (line 43 in `provider/llamaswap/image.go`). - The test in `llamaswap_test.go` (line 238) expects `cfg_scale` to be a `float64` value (e.g., `3.5`), not a pointer. - JSON marshaling in Go automatically handles pointers to primitives (e.g., `*float64`) by marshaling the underlying value if the pointer is not `nil`. Thus, the test's expectation is correct, and the field will be marshaled as a `float64` in the JSON payload. - **Conclusion**: This is not an issue. The JSON marshaler will correctly serialize the `*float64` as a `float64` value. The finding is incorrect and should be dropped. ### Finding 2: Potential Nil Pointer Dereference in `WithSteps` and `WithSeed` - **Claim**: The `WithSteps` and `WithSeed` functions take an `int` and `int64` respectively, and store the address of these values in the `Request` struct. If the caller passes a zero value (e.g., `WithSteps(0)`), the pointer will still be set, and the backend will receive a `0` value instead of `nil`. - **Verification**: - The `WithSteps` function (line 87 in `imagegen/imagegen.go`) takes an `int` and stores its address in the `Request.Steps` field. - The `WithSeed` function (line 99 in `imagegen/imagegen.go`) takes an `int64` and stores its address in the `Request.Seed` field. - If a caller passes `WithSteps(0)` or `WithSeed(0)`, the pointer will be set to the address of `0`, and the backend will receive `0` instead of `nil`. This could lead to unintended behavior if the backend interprets `0` as a valid value. - **Conclusion**: This is a valid concern. The functions do not distinguish between a zero value and an unset value, which could lead to unintended behavior. The finding is correct and should be kept. ### Finding 3: Inconsistent Handling of Zero Values in `NegativePrompt` and `Sampler` - **Claim**: The `NegativePrompt` and `Sampler` fields are strings, and their zero value is an empty string (`""`). The test expects these fields to be omitted if they are not set. However, if a caller explicitly sets these fields to an empty string (e.g., `WithNegativePrompt("")`), the field will be included in the JSON payload as an empty string, which may not be the intended behavior. - **Verification**: - The `NegativePrompt` and `Sampler` fields in the `Request` struct are strings (lines 92 and 95 in `imagegen/imagegen.go`). - The test in `llamaswap_test.go` (line 220) expects these fields to be omitted if they are not set. - If a caller explicitly sets these fields to an empty string (e.g., `WithNegativePrompt("")`), the field will be included in the JSON payload as an empty string because the `omitempty` tag only omits fields with zero values (e.g., `""` for strings). - **Conclusion**: This is a valid concern. The current implementation does not distinguish between an unset field and an explicitly set empty string, which could lead to unintended behavior. The finding is correct and should be kept. ### Final Review After verification, the following findings are confirmed: 1. **Potential Nil Pointer Dereference in `WithSteps` and `WithSeed`** 2. **Inconsistent Handling of Zero Values in `NegativePrompt` and `Sampler`** The first finding about the JSON field name for `CFGScale` is incorrect and should be dropped. ## VERDICT Minor issues ## Findings - **Potential Nil Pointer Dereference in `WithSteps` and `WithSeed`** - **Location**: `imagegen/imagegen.go:87` and `imagegen/imagegen.go:99` - **Issue**: The `WithSteps` and `WithSeed` functions take an `int` and `int64` respectively, and store the address of these values in the `Request` struct. If the caller passes a zero value (e.g., `WithSteps(0)`), the pointer will still be set, and the backend will receive a `0` value instead of `nil`. This could lead to unintended behavior if the backend interprets `0` as a valid value rather than a default. - **Impact**: Minor - **Inconsistent Handling of Zero Values in `NegativePrompt` and `Sampler`** - **Location**: `imagegen/imagegen.go:93` and `imagegen/imagegen.go:96` - **Issue**: The `NegativePrompt` and `Sampler` fields are strings, and their zero value is an empty string (`""`). The test expects these fields to be omitted if they are not set. However, if a caller explicitly sets these fields to an empty string (e.g., `WithNegativePrompt("")`), the field will be included in the JSON payload as an empty string, which may not be the intended behavior. - **Impact**: Minor </details> <details><summary><b>🧹 Code cleanliness & maintainability</b> — Minor issues</summary> Now, let's verify each finding in the draft review: 1. **Inconsistency in `WithSteps`, `WithCFGScale`, and `WithSeed`**: - Confirmed: These functions use pointer assignments (`&n`, `&s`, `&seed`), while `WithN` and `WithSize` directly assign values (`r.N = n`, `r.Size = size`). This inconsistency is present in the code. 2. **Mix of pointer and non-pointer types in `imageRequest`**: - Confirmed: The `imageRequest` struct includes fields like `Steps`, `CFGScale`, and `Seed` as pointers, while `NegativePrompt` and `SampleMethod` are strings. This mix is inconsistent. 3. **Use of `float64` in test assertion**: - Confirmed: The test assertion uses `float64(8)` and `float64(42)` for `steps` and `seed`, respectively, due to JSON unmarshaling defaults. 4. **Verbose comment in `imageRequest`**: - Confirmed: The comment for `imageRequest` is verbose and could be simplified to avoid duplication with the documentation in `imagegen.go`. 5. **Redundant comment block in `Request`**: - Confirmed: The comment block for the optional fields in `Request` is detailed but could be more concise, with repetition of "nil = backend default" and "backend default." All findings in the draft review are confirmed against the actual code. The verdict remains "Minor issues." ## Corrected Review ## Verdict Minor issues ## Findings - **`imagegen/imagegen.go:87-99`**: The `WithSteps`, `WithCFGScale`, and `WithSeed` functions use pointer assignments (`&n`, `&s`, `&seed`). While this is correct, it is inconsistent with the existing `WithN` and `WithSize` functions, which directly assign values (`r.N = n`, `r.Size = size`). This inconsistency could confuse maintainers. Consider standardizing the approach (e.g., using pointers for all optional fields or none). - **`provider/llamaswap/image.go:36-47`**: The `imageRequest` struct includes fields like `Steps`, `CFGScale`, and `Seed` as pointers, while `NegativePrompt` and `SampleMethod` are strings. This mix of pointer and non-pointer types for optional fields is inconsistent. While it aligns with the `Request` struct in `imagegen.go`, it could still be confusing. Consider documenting the rationale for this design choice in a comment. - **`provider/llamaswap/llamaswap_test.go:238`**: The test assertion for `want` uses `float64(8)` and `float64(42)` for `steps` and `seed`, respectively. This is because JSON numbers are unmarshaled as `float64` by default. However, this could be confusing to readers who expect integer types. Consider adding a comment to clarify why `float64` is used here. - **`provider/llamaswap/image.go:30-35`**: The comment for `imageRequest` is overly verbose and could be simplified. The explanation of `omitempty` and sd-server behavior is redundant with the documentation in `imagegen.go`. Consider shortening it to avoid duplication. - **`imagegen/imagegen.go:42-64`**: The comment block for the optional fields in `Request` is detailed but could be more concise. The repetition of "nil = backend default" and "backend default" is redundant. Consider consolidating this information. </details> <details><summary><b>⚡ Performance</b> — No material issues found</summary> ## Verdict No material issues found ## Findings - The changes introduce optional per-request overrides for image generation settings, which are correctly implemented using pointers and `omitempty` JSON tags. This ensures that unset values are omitted from the JSON payload, avoiding unnecessary data transmission and adhering to the backend's default settings. - The test `TestImageGenerateSettings` verifies that unset overrides are omitted and that set overrides are correctly forwarded with the expected field names. This confirms the efficiency of the implementation. - The use of pointers for optional fields (`Steps`, `CFGScale`, `Seed`) and empty strings for `NegativePrompt` and `Sampler` ensures minimal memory overhead and avoids unnecessary allocations. </details> <details><summary><b>🧯 Error handling & edge cases</b> — Minor issues</summary> Now, let me verify the findings in the draft review against the actual code: 1. **Unverified edge case handling for `Steps` and `Seed`**: The draft claims there is no validation for non-negative values. However, the code does not explicitly validate these values. This is a valid concern. 2. **Unverified edge case handling for `CFGScale`**: The draft claims there is no validation for the range of `CFGScale`. The code does not validate this, so this is also a valid concern. 3. **Unverified edge case handling for `Sampler`**: The draft claims there is no validation for supported sampling methods. The code does not validate this, so this is a valid concern. 4. **Unverified edge case handling for `NegativePrompt`**: The draft claims there is no validation for the length or content of `NegativePrompt`. The code does not validate this, so this is a valid concern. 5. **Unverified edge case handling for `Sampler` and `NegativePrompt`**: The draft claims there is no validation for empty strings. The code does not explicitly handle empty strings, so this is a valid concern. 6. **Unverified edge case handling for `Steps` and `Seed`**: The draft claims there is no validation for `nil` values. The code does not explicitly handle `nil` values, so this is a valid concern. 7. **Unverified edge case handling for `CFGScale`**: The draft claims there is no validation for `nil` values. The code does not explicitly handle `nil` values, so this is a valid concern. 8. **Unverified edge case handling for `Sampler` and `NegativePrompt`**: The draft claims there is no validation for excessively long or malformed values. The code does not validate this, so this is a valid concern. 9. **Unverified edge case handling for `Steps` and `Seed`**: The draft claims there is no validation for excessively large or small values. The code does not validate this, so this is a valid concern. 10. **Unverified edge case handling for `CFGScale`**: The draft claims there is no validation for excessively large or small values. The code does not validate this, so this is a valid concern. All findings in the draft review are confirmed against the actual code. The code does not include validation for the mentioned edge cases, which could lead to unexpected behavior or errors downstream. ## Corrected Review ## VERDICT Minor issues ## Findings 1. **Unverified edge case handling for `Steps` and `Seed`**: - The `WithSteps` and `WithSeed` functions accept `int` and `int64` values, respectively, but there is no validation to ensure these values are non-negative. Negative values for `Steps` or `Seed` could lead to unexpected behavior or errors downstream. - **Impact**: Potential for unexpected behavior or errors if negative values are provided. - **Location**: `imagegen/imagegen.go:87`, `imagegen/imagegen.go:99` 2. **Unverified edge case handling for `CFGScale`**: - The `WithCFGScale` function accepts a `float64` value, but there is no validation to ensure it is within a reasonable range (e.g., non-negative or within typical bounds for CFG scales). An invalid `CFGScale` value could cause the backend to fail or produce unexpected results. - **Impact**: Potential for unexpected behavior or errors if invalid values are provided. - **Location**: `imagegen/imagegen.go:90` 3. **Unverified edge case handling for `Sampler`**: - The `WithSampler` function accepts a string value, but there is no validation to ensure it matches one of the supported sampling methods (e.g., "euler", "euler_a"). An unsupported `Sampler` value could cause the backend to fail or ignore the setting. - **Impact**: Potential for unexpected behavior or errors if unsupported values are provided. - **Location**: `imagegen/imagegen.go:96` 4. **Unverified edge case handling for `NegativePrompt`**: - The `WithNegativePrompt` function accepts a string value, but there is no validation to ensure it is not excessively long or contains invalid characters. An overly long or malformed `NegativePrompt` could cause the backend to fail or produce unexpected results. - **Impact**: Potential for unexpected behavior or errors if invalid values are provided. - **Location**: `imagegen/imagegen.go:93` 5. **Unverified edge case handling for `Sampler` and `NegativePrompt`**: - The `Sampler` and `NegativePrompt` fields are forwarded to the backend as-is, but there is no validation to ensure they are not empty strings. While empty strings are allowed (as per the design), it is unclear whether the backend handles them gracefully or if they should be treated as "unset" (similar to `nil` for pointer fields). - **Impact**: Potential for unexpected behavior if empty strings are not handled gracefully by the backend. - **Location**: `imagegen/imagegen.go:96`, `imagegen/imagegen.go:93` 6. **Unverified edge case handling for `Steps` and `Seed`**: - The `Steps` and `Seed` fields are forwarded to the backend as pointers, but there is no validation to ensure they are not `nil` when they are explicitly set. While `nil` is allowed (as per the design), it is unclear whether the backend handles `nil` values gracefully or if they should be treated as "unset." - **Impact**: Potential for unexpected behavior if `nil` values are not handled gracefully by the backend. - **Location**: `imagegen/imagegen.go:87`, `imagegen/imagegen.go:99` 7. **Unverified edge case handling for `CFGScale`**: - The `CFGScale` field is forwarded to the backend as a pointer, but there is no validation to ensure it is not `nil` when it is explicitly set. While `nil` is allowed (as per the design), it is unclear whether the backend handles `nil` values gracefully or if they should be treated as "unset." - **Impact**: Potential for unexpected behavior if `nil` values are not handled gracefully by the backend. - **Location**: `imagegen/imagegen.go:90` 8. **Unverified edge case handling for `Sampler` and `NegativePrompt`**: - The `Sampler` and `NegativePrompt` fields are forwarded to the backend as strings, but there is no validation to ensure they are not excessively long or contain invalid characters. An overly long or malformed value could cause the backend to fail or produce unexpected results. - **Impact**: Potential for unexpected behavior or errors if invalid values are provided. - **Location**: `imagegen/imagegen.go:96`, `imagegen/imagegen.go:93` 9. **Unverified edge case handling for `Steps` and `Seed`**: - The `Steps` and `Seed` fields are forwarded to the backend as pointers, but there is no validation to ensure they are not excessively large or small. An overly large or small value could cause the backend to fail or produce unexpected results. - **Impact**: Potential for unexpected behavior or errors if invalid values are provided. - **Location**: `imagegen/imagegen.go:87`, `imagegen/imagegen.go:99` 10. **Unverified edge case handling for `CFGScale`**: - The `CFGScale` field is forwarded to the backend as a pointer, but there is no validation to ensure it is not excessively large or small. An overly large or small value could cause the backend to fail or produce unexpected results. - **Impact**: Potential for unexpected behavior or errors if invalid values are provided. - **Location**: `imagegen/imagegen.go:90` ## Summary The changes introduce optional per-request generation settings, but there are several unverified edge cases related to input validation and error handling. These issues could lead to unexpected behavior or errors downstream if invalid values are provided. Adding validation and clarification in the documentation would improve robustness. </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 3m 44s</sub>

🪰 Gadfly review — kimi-k2.6:cloud (ollama-cloud)

Verdict: No material issues found — 5 reviewers: security, correctness, maintainability, performance, error-handling

🔒 Security — No material issues found

No material issues found

From the security lens, this diff is clean:

  • All new user-controllable fields (Prompt, NegativePrompt, Sampler, Steps, CFGScale, Seed, Size, N) are serialized via Go's standard json.Marshal into a JSON request body (provider/llamaswap/image.go:67-78llamaswap.go:193-198). No string interpolation into URLs, headers, commands, or SQL.
  • NegativePrompt is user-supplied text but is JSON-escaped before transmission; there is no path where it is rendered into a template, executed, or reflected without encoding.
  • No new secrets/credentials are introduced or logged.
  • doJSON constructs the request with http.NewRequestWithContext, using a fixed path (/v1/images/generations) and the configured baseURL; no user input influences the destination URL, so SSRF is not introduced by these changes.
  • The imageRequest struct uses omitempty for optional fields (Steps, CFGScale, NegativePrompt, SampleMethod, Seed), ensuring unset values are omitted rather than leaking zero-value defaults. Verified in llamaswap_test.go:220-224.
  • No unsafe deserialization; the response is unmarshaled into a well-typed imageResponse struct.

Nothing in this change expands the attack surface in a security-relevant way.

🎯 Correctness — No material issues found

No material issues found

  • The new pointer/string fields with omitempty correctly implement the "unset = backend default" semantics described in the PR. nil pointers are omitted by encoding/json, and empty strings are omitted for NegativePrompt/Sampler, so callers that don't set them see unchanged behavior.
  • The mapping from imagegen.Request.Sampler → wire sample_method is correctly wired in provider/llamaswap/image.go:76.
  • The test correctly handles JSON number decoding into map[string]any by expecting float64 for integer fields.
  • Option closures (WithSteps, etc.) capture the address of their respective value parameters; each call gets a distinct parameter allocation, so the returned pointers are independent.
  • Only one provider (llamaswap) implements imagegen.Model, so there are no cross-provider compile or interface-breakage issues.
🧹 Code cleanliness & maintainability — No material issues found

No material issues found.

The change is clean and follows existing patterns well:

  • New With* option helpers (WithSteps, WithCFGScale, WithNegativePrompt, WithSampler, WithSeed) mirror the existing WithN/WithSize style in imagegen/imagegen.go:86-99.
  • Pointer vs string semantics are applied consistently: pointer fields for numeric overrides where zero is meaningful (Steps *int, CFGScale *float64, Seed *int64), string fields where empty means default (NegativePrompt, Sampler), matching the existing Size string pattern.
  • The imageRequest struct in provider/llamaswap/image.go correctly uses omitempty tags and pointer types so unset values are omitted entirely, keeping the wire contract clean.
  • Test in provider/llamaswap/llamaswap_test.go:204-244 cleanly separates the "unset values omitted" and "set values forwarded" assertions with explicit comments.
  • No dead code, no unnecessary duplication, no naming inconsistencies.
Performance — No material issues found

No material issues found.

The change is purely additive: five new optional fields (three pointer-backed numeric values, two strings) and corresponding With* option functions. The pointer indirection is required to distinguish "unset" from zero, and the omitempty JSON tags keep wire payloads unchanged for callers that don't use the new fields. Nothing in this PR introduces allocations on hot paths, quadratic behavior, unbounded buffering, or additional network round-trips. This is a cold-path image-generation API where the dominant cost is the backend diffusion model, not the small struct being serialized.

🧯 Error handling & edge cases — No material issues found

No material issues found

  • Reviewed the new With* option constructors in imagegen/imagegen.go — each safely heap-allocates its captured value so distinct calls produce independent pointers; no aliasing or reuse bugs.
  • Verified Request.Apply uses a value receiver and mutates a stack copy, so pointer fields are safely copied per-request.
  • Checked provider/llamaswap/image.go — the new fields are forwarded with omitempty JSON tags, so nil/empty values are correctly omitted and the backend falls back to defaults.
  • Confirmed the production Generate method does not swallow errors; all JSON decode and base64-decode errors are returned.
  • The test handler ignores Decode/Write errors, but that is benign test-only code and does not affect production error handling.
  • No new panic paths introduced (no nil-pointer dereferences on the new pointer fields because they are only accessed by assignment to the wire struct).
  • Edge cases (nil pointers, empty strings, zero values) are explicitly designed to map to backend defaults via omitempty.

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 5m 33s

<!-- gadfly-review:ollama:kimi-k2.6:cloud --> ### 🪰 Gadfly review — `kimi-k2.6:cloud` (ollama-cloud) **Verdict: No material issues found** — 5 reviewers: security, correctness, maintainability, performance, error-handling <details><summary><b>🔒 Security</b> — No material issues found</summary> **No material issues found** From the security lens, this diff is clean: - All new user-controllable fields (`Prompt`, `NegativePrompt`, `Sampler`, `Steps`, `CFGScale`, `Seed`, `Size`, `N`) are serialized via Go's standard `json.Marshal` into a JSON request body (`provider/llamaswap/image.go:67-78` → `llamaswap.go:193-198`). No string interpolation into URLs, headers, commands, or SQL. - `NegativePrompt` is user-supplied text but is JSON-escaped before transmission; there is no path where it is rendered into a template, executed, or reflected without encoding. - No new secrets/credentials are introduced or logged. - `doJSON` constructs the request with `http.NewRequestWithContext`, using a fixed path (`/v1/images/generations`) and the configured `baseURL`; no user input influences the destination URL, so SSRF is not introduced by these changes. - The `imageRequest` struct uses `omitempty` for optional fields (`Steps`, `CFGScale`, `NegativePrompt`, `SampleMethod`, `Seed`), ensuring unset values are omitted rather than leaking zero-value defaults. Verified in `llamaswap_test.go:220-224`. - No unsafe deserialization; the response is unmarshaled into a well-typed `imageResponse` struct. Nothing in this change expands the attack surface in a security-relevant way. </details> <details><summary><b>🎯 Correctness</b> — No material issues found</summary> **No material issues found** - The new pointer/string fields with `omitempty` correctly implement the "unset = backend default" semantics described in the PR. `nil` pointers are omitted by `encoding/json`, and empty strings are omitted for `NegativePrompt`/`Sampler`, so callers that don't set them see unchanged behavior. - The mapping from `imagegen.Request.Sampler` → wire `sample_method` is correctly wired in `provider/llamaswap/image.go:76`. - The test correctly handles JSON number decoding into `map[string]any` by expecting `float64` for integer fields. - Option closures (`WithSteps`, etc.) capture the address of their respective value parameters; each call gets a distinct parameter allocation, so the returned pointers are independent. - Only one provider (`llamaswap`) implements `imagegen.Model`, so there are no cross-provider compile or interface-breakage issues. </details> <details><summary><b>🧹 Code cleanliness & maintainability</b> — No material issues found</summary> No material issues found. The change is clean and follows existing patterns well: - New `With*` option helpers (`WithSteps`, `WithCFGScale`, `WithNegativePrompt`, `WithSampler`, `WithSeed`) mirror the existing `WithN`/`WithSize` style in `imagegen/imagegen.go:86-99`. - Pointer vs string semantics are applied consistently: pointer fields for numeric overrides where zero is meaningful (`Steps *int`, `CFGScale *float64`, `Seed *int64`), string fields where empty means default (`NegativePrompt`, `Sampler`), matching the existing `Size string` pattern. - The `imageRequest` struct in `provider/llamaswap/image.go` correctly uses `omitempty` tags and pointer types so unset values are omitted entirely, keeping the wire contract clean. - Test in `provider/llamaswap/llamaswap_test.go:204-244` cleanly separates the "unset values omitted" and "set values forwarded" assertions with explicit comments. - No dead code, no unnecessary duplication, no naming inconsistencies. </details> <details><summary><b>⚡ Performance</b> — No material issues found</summary> No material issues found. The change is purely additive: five new optional fields (three pointer-backed numeric values, two strings) and corresponding `With*` option functions. The pointer indirection is required to distinguish "unset" from zero, and the `omitempty` JSON tags keep wire payloads unchanged for callers that don't use the new fields. Nothing in this PR introduces allocations on hot paths, quadratic behavior, unbounded buffering, or additional network round-trips. This is a cold-path image-generation API where the dominant cost is the backend diffusion model, not the small struct being serialized. </details> <details><summary><b>🧯 Error handling & edge cases</b> — No material issues found</summary> No material issues found - Reviewed the new `With*` option constructors in `imagegen/imagegen.go` — each safely heap-allocates its captured value so distinct calls produce independent pointers; no aliasing or reuse bugs. - Verified `Request.Apply` uses a value receiver and mutates a stack copy, so pointer fields are safely copied per-request. - Checked `provider/llamaswap/image.go` — the new fields are forwarded with `omitempty` JSON tags, so nil/empty values are correctly omitted and the backend falls back to defaults. - Confirmed the production `Generate` method does not swallow errors; all JSON decode and base64-decode errors are returned. - The test handler ignores `Decode`/`Write` errors, but that is benign test-only code and does not affect production error handling. - No new panic paths introduced (no nil-pointer dereferences on the new pointer fields because they are only accessed by assignment to the wire struct). - Edge cases (nil pointers, empty strings, zero values) are explicitly designed to map to backend defaults via `omitempty`. </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 5m 33s</sub>

🪰 Gadfly review — qwen3.5:397b-cloud (ollama-cloud)

Verdict: Minor issues — 5 reviewers: security, correctness, maintainability, performance, error-handling

🔒 Security — No material issues found

VERDICT: No material issues found

After reviewing the change through the security lens and verifying against the checked-out code:

  • No injection vectors introduced: The new fields (Steps, CFGScale, NegativePrompt, Sampler, Seed) are serialized as JSON and sent to the sd-server backend via llama-swap. They do not touch SQL, shell commands, file paths, or templates at this layer.

  • No authn/authz gaps: The change does not modify authentication or authorization logic. The existing doJSON helper continues to apply the bearer token (p.token) to all requests as before.

  • No secret leakage: The new fields contain only generation parameters, not credentials. No logging or error messages expose these values.

  • Input validation: The string fields (NegativePrompt, Sampler) are passed through without sanitization. However, this is a pass-through layer to a trusted backend (sd-server); the threat model assumes callers are trusted internal services, not direct user input. Backend validation is the appropriate layer for value constraints.

  • Numeric bounds: Steps, CFGScale, and Seed have no range validation. Again, this is intentional per the design ("nil = backend default") and the backend is expected to reject invalid values.

The change is additive and maintains the existing security posture. No material security issues found in this lens.

🎯 Correctness — No material issues found

VERDICT: No material issues found

After reviewing through the Correctness lens and verifying against the checked-out code:

  • Field definitions (imagegen/imagegen.go:47-63): The new optional override fields (Steps *int, CFGScale *float64, NegativePrompt string, Sampler string, Seed *int64) are correctly typed. Pointer types for numeric fields allow nil to mean "unset"; empty strings for NegativePrompt and Sampler mean "none" and "backend default" respectively — semantics match the documented behavior.

  • Option functions (imagegen/imagegen.go:86-99): Correctly assign values — pointers for Steps, CFGScale, Seed; direct assignment for strings.

  • Wire mapping (provider/llamaswap/image.go:67-78): Fields map correctly to sd-server's expected JSON field names:

    • Stepssteps
    • CFGScalecfg_scale
    • NegativePromptnegative_prompt
    • Samplersample_method (verified: SampleMethod struct field with json:"sample_method")
    • Seedseed
  • omitempty behavior: Verified correct — nil pointers and empty strings are omitted, allowing sd-server to use per-model defaults.

  • Test coverage (provider/llamaswap/llamaswap_test.go:204-244): Test verifies both that unset fields are omitted and that set overrides are forwarded with correct values. JSON number unmarshaling to float64 is handled correctly in the test expectations.

  • Interface compatibility: The imagegen.Provider interface change is purely additive; existing providers without ImageModel are not broken (only llamaswap implements it currently, verified via grep).

No logic bugs, semantic errors, or incorrect mappings found.

🧹 Code cleanliness & maintainability — No material issues found

VERDICT: No material issues found

After reviewing through the code cleanliness & maintainability lens and verifying against the checked-out code:

  • Pattern consistency: The new Request fields (Steps *int, CFGScale *float64, NegativePrompt string, Sampler string, Seed *int64) follow the existing optional-field pattern (pointers for numerics, plain strings for text). The five new With* option functions mirror the established WithN/WithSize pattern exactly.

  • Naming: Field names are clear and self-documenting. The wire-format mapping (Samplersample_method as SampleMethod) is explicitly documented in the imageRequest struct comment.

  • Comments: The added comments explain semantics precisely (nil/"" = backend default) and provide useful context (e.g., CFGScale architecture sensitivity). The imageRequest comment documents the forward-compatibility assumption about sd-server field recognition.

  • Test structure: TestImageGenerateSettings follows the same httptest pattern as TestImageGenerate — clear two-phase test (unset vs. set overrides), appropriate error handling, and correct JSON type expectations (float64 for decoded numbers).

  • No duplication: The option functions are repetitive by design (idiomatic Go option pattern), matching existing code. No copy-paste beyond the established convention.

  • ADR update: Concise and accurately reflects the code changes.

Performance — No material issues found

VERDICT: No material issues found

Performance lens findings:

The changes are additive optional fields with omitempty JSON tags — this is the efficient pattern for optional overrides:

  • Pointer allocations (WithSteps, WithCFGScale, WithSeed): Each call allocates one small value on the heap to distinguish "unset" from "zero". This is the standard Go idiom for optional numeric parameters and is negligible compared to the HTTP request and image generation work that follows.

  • Struct copying in Apply: The Request copy now includes 5 more fields (3 pointers, 2 strings). This is a shallow copy of ~40-80 extra bytes per request — trivial relative to the JSON serialization and network I/O.

  • JSON payload: The omitempty tags ensure unset fields are not transmitted, so default-case requests have no payload growth. When set, the additional fields are small scalars.

  • No hot-path changes: The Generate method's structure is unchanged — same single HTTP call, same decoding loop. No new iterations, no blocking calls added, no unbounded growth.

Verified by reading imagegen/imagegen.go:86-107 (option functions and Apply) and provider/llamaswap/image.go:67-78 (struct literal construction). No performance regressions detected within this lens.

🧯 Error handling & edge cases — Minor issues

Verdict: Minor issues found

Findings:

  1. provider/llamaswap/image.go:58-78 — No validation on new numeric override fields
    The Generate function validates req.N < 0 (line 63-65) but does not validate the newly added Steps, CFGScale, or Seed fields. A caller can pass WithSteps(-1), WithCFGScale(-10), or WithSeed(-999) and these invalid values will be forwarded to sd-server unchecked.

    • Impact: Backend may reject the request, produce garbage output, or behave unpredictably. The existing validation pattern for N shows this is recognized as important, but the new fields were missed.
    • Fix: Add validation after req.Apply(opts...) similar to the N check — e.g., reject negative Steps and CFGScale, and document acceptable ranges.
  2. imagegen/imagegen.go:55-56NegativePrompt zero-value semantics are ambiguous
    The comment says "" = none, but with omitempty on the JSON field, an empty string is omitted entirely. This means there's no way for a caller to explicitly send an empty negative prompt if the backend distinguishes between "omit field" vs "send empty string".

    • Impact: If sd-server treats "field absent" differently from "field present but empty", callers cannot force the latter. This is likely fine (most backends treat them identically), but it's an edge case the current design cannot handle.
    • Fix: If this distinction matters, change NegativePrompt to *string like the other optional fields. If not, the current design is acceptable but the limitation should be documented.
  3. provider/llamaswap/llamaswap_test.go:204-245 — Test covers happy path only
    TestImageGenerateSettings verifies that unset fields are omitted and set fields are forwarded, but does not test edge cases like zero/negative values, extremely large step counts, or error responses from the backend when invalid values are sent.

    • Impact: No regression protection if validation is added later, and no documentation of expected backend behavior on invalid input.
    • Fix: Add test cases for boundary/invalid values once validation is implemented.

Summary: The core design (pointers + omitempty) correctly handles the "omit if unset" case. The main gap is missing input validation on the new numeric fields, which is inconsistent with the existing N < 0 check and leaves the door open for invalid requests to reach the backend.

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 3m 16s

<!-- gadfly-review:ollama:qwen3.5:397b-cloud --> ### 🪰 Gadfly review — `qwen3.5:397b-cloud` (ollama-cloud) **Verdict: Minor issues** — 5 reviewers: security, correctness, maintainability, performance, error-handling <details><summary><b>🔒 Security</b> — No material issues found</summary> **VERDICT: No material issues found** After reviewing the change through the security lens and verifying against the checked-out code: - **No injection vectors introduced**: The new fields (`Steps`, `CFGScale`, `NegativePrompt`, `Sampler`, `Seed`) are serialized as JSON and sent to the sd-server backend via llama-swap. They do not touch SQL, shell commands, file paths, or templates at this layer. - **No authn/authz gaps**: The change does not modify authentication or authorization logic. The existing `doJSON` helper continues to apply the bearer token (`p.token`) to all requests as before. - **No secret leakage**: The new fields contain only generation parameters, not credentials. No logging or error messages expose these values. - **Input validation**: The string fields (`NegativePrompt`, `Sampler`) are passed through without sanitization. However, this is a pass-through layer to a trusted backend (sd-server); the threat model assumes callers are trusted internal services, not direct user input. Backend validation is the appropriate layer for value constraints. - **Numeric bounds**: `Steps`, `CFGScale`, and `Seed` have no range validation. Again, this is intentional per the design ("nil = backend default") and the backend is expected to reject invalid values. The change is additive and maintains the existing security posture. No material security issues found in this lens. </details> <details><summary><b>🎯 Correctness</b> — No material issues found</summary> **VERDICT: No material issues found** After reviewing through the Correctness lens and verifying against the checked-out code: - **Field definitions** (`imagegen/imagegen.go:47-63`): The new optional override fields (`Steps *int`, `CFGScale *float64`, `NegativePrompt string`, `Sampler string`, `Seed *int64`) are correctly typed. Pointer types for numeric fields allow nil to mean "unset"; empty strings for `NegativePrompt` and `Sampler` mean "none" and "backend default" respectively — semantics match the documented behavior. - **Option functions** (`imagegen/imagegen.go:86-99`): Correctly assign values — pointers for `Steps`, `CFGScale`, `Seed`; direct assignment for strings. - **Wire mapping** (`provider/llamaswap/image.go:67-78`): Fields map correctly to sd-server's expected JSON field names: - `Steps` → `steps` - `CFGScale` → `cfg_scale` - `NegativePrompt` → `negative_prompt` - `Sampler` → `sample_method` (verified: `SampleMethod` struct field with `json:"sample_method"`) - `Seed` → `seed` - **`omitempty` behavior**: Verified correct — nil pointers and empty strings are omitted, allowing sd-server to use per-model defaults. - **Test coverage** (`provider/llamaswap/llamaswap_test.go:204-244`): Test verifies both that unset fields are omitted and that set overrides are forwarded with correct values. JSON number unmarshaling to `float64` is handled correctly in the test expectations. - **Interface compatibility**: The `imagegen.Provider` interface change is purely additive; existing providers without `ImageModel` are not broken (only `llamaswap` implements it currently, verified via grep). No logic bugs, semantic errors, or incorrect mappings found. </details> <details><summary><b>🧹 Code cleanliness & maintainability</b> — No material issues found</summary> **VERDICT: No material issues found** After reviewing through the code cleanliness & maintainability lens and verifying against the checked-out code: - **Pattern consistency**: The new `Request` fields (`Steps *int`, `CFGScale *float64`, `NegativePrompt string`, `Sampler string`, `Seed *int64`) follow the existing optional-field pattern (pointers for numerics, plain strings for text). The five new `With*` option functions mirror the established `WithN`/`WithSize` pattern exactly. - **Naming**: Field names are clear and self-documenting. The wire-format mapping (`Sampler` → `sample_method` as `SampleMethod`) is explicitly documented in the `imageRequest` struct comment. - **Comments**: The added comments explain semantics precisely (nil/"" = backend default) and provide useful context (e.g., CFGScale architecture sensitivity). The `imageRequest` comment documents the forward-compatibility assumption about sd-server field recognition. - **Test structure**: `TestImageGenerateSettings` follows the same httptest pattern as `TestImageGenerate` — clear two-phase test (unset vs. set overrides), appropriate error handling, and correct JSON type expectations (`float64` for decoded numbers). - **No duplication**: The option functions are repetitive by design (idiomatic Go option pattern), matching existing code. No copy-paste beyond the established convention. - **ADR update**: Concise and accurately reflects the code changes. </details> <details><summary><b>⚡ Performance</b> — No material issues found</summary> **VERDICT: No material issues found** **Performance lens findings:** The changes are additive optional fields with `omitempty` JSON tags — this is the efficient pattern for optional overrides: - **Pointer allocations** (`WithSteps`, `WithCFGScale`, `WithSeed`): Each call allocates one small value on the heap to distinguish "unset" from "zero". This is the standard Go idiom for optional numeric parameters and is negligible compared to the HTTP request and image generation work that follows. - **Struct copying in `Apply`**: The Request copy now includes 5 more fields (3 pointers, 2 strings). This is a shallow copy of ~40-80 extra bytes per request — trivial relative to the JSON serialization and network I/O. - **JSON payload**: The `omitempty` tags ensure unset fields are not transmitted, so default-case requests have no payload growth. When set, the additional fields are small scalars. - **No hot-path changes**: The `Generate` method's structure is unchanged — same single HTTP call, same decoding loop. No new iterations, no blocking calls added, no unbounded growth. Verified by reading `imagegen/imagegen.go:86-107` (option functions and Apply) and `provider/llamaswap/image.go:67-78` (struct literal construction). No performance regressions detected within this lens. </details> <details><summary><b>🧯 Error handling & edge cases</b> — Minor issues</summary> **Verdict: Minor issues found** **Findings:** 1. **`provider/llamaswap/image.go:58-78` — No validation on new numeric override fields** The `Generate` function validates `req.N < 0` (line 63-65) but does **not** validate the newly added `Steps`, `CFGScale`, or `Seed` fields. A caller can pass `WithSteps(-1)`, `WithCFGScale(-10)`, or `WithSeed(-999)` and these invalid values will be forwarded to sd-server unchecked. - **Impact**: Backend may reject the request, produce garbage output, or behave unpredictably. The existing validation pattern for `N` shows this is recognized as important, but the new fields were missed. - **Fix**: Add validation after `req.Apply(opts...)` similar to the `N` check — e.g., reject negative `Steps` and `CFGScale`, and document acceptable ranges. 2. **`imagegen/imagegen.go:55-56` — `NegativePrompt` zero-value semantics are ambiguous** The comment says `"" = none`, but with `omitempty` on the JSON field, an empty string is omitted entirely. This means there's no way for a caller to explicitly send an empty negative prompt if the backend distinguishes between "omit field" vs "send empty string". - **Impact**: If sd-server treats "field absent" differently from "field present but empty", callers cannot force the latter. This is likely fine (most backends treat them identically), but it's an edge case the current design cannot handle. - **Fix**: If this distinction matters, change `NegativePrompt` to `*string` like the other optional fields. If not, the current design is acceptable but the limitation should be documented. 3. **`provider/llamaswap/llamaswap_test.go:204-245` — Test covers happy path only** `TestImageGenerateSettings` verifies that unset fields are omitted and set fields are forwarded, but does not test edge cases like zero/negative values, extremely large step counts, or error responses from the backend when invalid values are sent. - **Impact**: No regression protection if validation is added later, and no documentation of expected backend behavior on invalid input. - **Fix**: Add test cases for boundary/invalid values once validation is implemented. --- **Summary**: The core design (pointers + `omitempty`) correctly handles the "omit if unset" case. The main gap is missing input validation on the new numeric fields, which is inconsistent with the existing `N < 0` check and leaves the door open for invalid requests to reach the backend. </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 3m 16s</sub>
steve added 1 commit 2026-06-28 23:05:51 +00:00
feat(imagegen): optional per-request generation settings
CI / Tidy (pull_request) Successful in 9m27s
CI / Build & Test (pull_request) Successful in 9m47s
a744cdc335
Add Steps, CFGScale, NegativePrompt, Sampler, Seed to imagegen.Request
(pointer/empty = leave the backend's per-model default), with mirror
options, and forward them in the llamaswap wire payload as the
stable-diffusion.cpp fields (steps/cfg_scale/negative_prompt/
sample_method/seed). Unset fields are omitted so sd-server keeps its
baked defaults.

Lets callers (e.g. mort drawbots) override only what they explicitly set.
steve force-pushed feat/imagegen-settings from db8d455bd8 to a744cdc335 2026-06-28 23:05:51 +00:00 Compare
steve merged commit 531fe1922e into main 2026-06-29 02:00:40 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: steve/majordomo#9