feat(generate-image): add Atlas Cloud as an optional provider - #1618
Conversation
`codev generate-image` is wired to the @google/genai SDK, so it needs a Google AI Studio key. Atlas Cloud serves the same Nano Banana Pro model over its own submit-then-poll REST API, so this adds it behind `-p/--provider atlas`. The default stays `gemini` and its code path is untouched. Measured against the endpoint rather than assumed: - `-a/--aspect` maps to Atlas's `aspect_ratio` and is honoured: 1:1 returns 1024x1024 and 16:9 returns 1376x768. - `-r/--resolution` has no equivalent field there. Rather than silently dropping it, a note is printed saying the model's default resolution comes back. - `--ref` is not supported on this path (text-to-image only) and exits with an error that points at `--provider gemini`, instead of generating something that quietly ignores the reference images. - The model returns JPEG. Writing those bytes into the default `output.png` would mislabel the file, so the output is named after its actual bytes and the rename is logged. The Gemini path keeps its existing behaviour. No new dependency: the Atlas path uses global fetch (the package already requires Node >= 20). Tests: 5 new vitest cases covering the missing credential, an unknown provider, the refused `--ref`, a full submit/poll/download with the JPEG rename and the explicit User-Agent, and a failed prediction. `vitest run src/__tests__/generate-image.test.ts` is 19 passed. `tsc --noEmit` reports nothing for the touched files (the pre-existing errors in `src/agent-farm` and `src/lib/github.ts` come from unbuilt workspace deps). One provider detail worth having in the file: api.atlascloud.ai rejects some clients' default User-Agent with 403 (error code 1010), which is what the constant and its comment are for. Signed-off-by: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com>
waleedkadous
left a comment
There was a problem hiding this comment.
Welcome, and thank you — this is a well-made first contribution: the flag-mapping table is measured rather than assumed, --resolution/--ref fail honestly instead of silently, the API key appears only in the Atlas Authorization header and never reaches the CDN fetch, and the default gemini path is untouched (all 14 existing tests pass unmodified). A few things need fixing before merge, and since they're all mechanical we'll push them onto your branch ourselves rather than send you a list (maintainer edits are enabled — thank you); your commits and authorship stay as they are. If you'd rather do them yourself, say so and we'll hold off.
What we'll change, in the spirit of the repo's fail-fast rule:
- Skill docs: the repo's own tracked
.claude/skills/generate-image/SKILL.mdand.codex/skills/generate-image/SKILL.mdneed the same edit as the two skeleton copies (all four are meant to stay byte-identical;packages/codev/skeleton/is generated and correctly untouched). - Per-request timeouts:
ATLAS_TIMEOUT_MSbounds the poll loop but no individualfetch— a stalled submit, poll, or CDN download hangs forever.signal: AbortSignal.timeout(...)on all three. - Unknown status fails fast: the loop only exits on
completed/failed, socancelled,error, or an undefined status polls for the full 300s and then reports a misleading timeout. Allowlist the in-progress statuses and error immediately on anything else, naming it. - Unrecognized download bytes must not be written:
withDetectedExtensioncurrently passes unknown magic bytes through, so an HTML error body with a 200 lands inoutput.pngunder a green "Image saved". Fail instead. - Tests: the two polling tests sleep for real (measured 10.01s for the file;
useRealTimerswith nouseFakeTimers) — inject the poll interval; restore the env var; and add the one assertion worth locking down:Authorizationis sent to Atlas and NOT to the CDN URL.
Minor, may or may not fold in: a typeof url === 'string' guard on outputs[0], and ATLASCLOUD_API_KEY in codev/resources/cloud-instances.md for discoverability.
Review fixes on top of @binyangzhu000-sudo's contribution, pushed to the branch rather than sent back as a list. The provider design, the measured flag mapping and the contributor's commit are unchanged. Skill docs: `.claude/` and `.codex/skills/generate-image/SKILL.md` get the same edit as the two skeleton copies, so all four are byte-identical again. Their frontmatter also lists ATLASCLOUD_API_KEY, since that string is what drives skill selection. Fail-fast fixes, in the spirit of the repo rule: - Every request is bounded. ATLAS_TIMEOUT_MS capped the poll loop but no individual fetch, so a stalled submit, poll or download hung forever. `atlasRequest` puts connect, stream and parse under one AbortSignal.timeout: the signal stays armed while the body streams, so a poll answering 200 with an HTML error page, or a download aborted mid-stream, now reports a clear error instead of escaping as a raw SyntaxError or TimeoutError stack. - Unknown statuses fail immediately, naming the status. The loop only exited on completed/failed, so `cancelled` or an undefined status polled for the full 300s and then reported a misleading timeout. The in-progress allowlist is `processing` alone: that is what https://www.atlascloud.ai/docs/en/predictions documents, and inventing plausible extras would be guessing at a contract. - Unrecognized download bytes are never written. `withDetectedExtension` passed them through, so an HTML error body with a 200 landed in output.png under a green "Image saved". It now exits; renamed `targetPathForImageBytes`, since the old name no longer described it. - The prediction id gets the same `typeof` guard as the image URL, and the overall deadline is checked after the sleep rather than before, so a budget that expires mid-sleep does not buy one more request. - `--ref` is refused before the credential check, so `-p atlas --ref x` without a key names the flag rather than the key. Tests: 19 -> 35 cases, and the file runs in 181ms instead of ~10s (the poll cadence and overall budget are injectable; the two polling tests no longer sleep for real). ATLASCLOUD_API_KEY is explicitly cleared per test. New coverage for submit non-ok, poll non-ok, unknown status, missing and non-string prediction id, missing and non-string image URL, unrecognized bytes, an HTML body served as JSON, a mid-stream abort, and the overall timeout. The security assertion the review asked for is there: Authorization IS sent to Atlas and is NOT sent to the CDN, read through `Headers` so a Headers instance cannot hide a leak behind an empty object. Each fix was checked by reverting it and confirming a test fails. `pnpm --filter @cluesmith/codev build` and `tsc --noEmit` are clean; the package suite is 278 files / 5571 passed.
Records the maintainer-edit approach, the CMAP round, and the three findings that were declined along with the reasoning.
|
Thanks again for this — it's a genuinely nice first contribution, and the parts that were hardest to get right you already got right: the flag mapping is measured against the endpoint rather than assumed, As offered in the review, I've pushed the mechanical fixes onto your branch rather than sending you a list. Your commit and authorship are unchanged — nothing was rebased or squashed, mine just sit on top. Here's exactly what changed and why: 1. Skill docs (all four copies). 2. Per-request timeouts. One thing worth knowing, because it isn't obvious: the signal stays armed while the body streams, not just until the headers arrive. So the body reads had to move inside the guard too. Otherwise a poll answering 3. Unknown statuses fail immediately. The loop only exited on 4. Unrecognized bytes are never written. This was the one real hazard. 5. Tests: 19 → 35, and the file runs in 181ms instead of ~10s. The two polling tests were sleeping for real. The poll cadence and overall budget are now parameters with the old constants as defaults, so tests can pass Minor: the Build and One thing I deliberately did not change, because it's your call and the maintainers': a reviewer argued that Since this is a PR from a fork, CI needs a maintainer to approve the workflow run before it executes — that's a GitHub setting for first-time contributors, not anything about your code. Nothing else needed from you. Thanks for the care you put into this, especially measuring the endpoint instead of guessing at it and writing up what you found. That made the review much easier. Hope to see more from you. |
waleedkadous
left a comment
There was a problem hiding this comment.
Re-verified after the maintainer-side commits: all four tracked SKILL.md copies byte-identical; every Atlas fetch goes through one AbortSignal.timeout-guarded helper (including the response-body read, which CMAP caught was outside the guard); the in-progress status allowlist is sourced from Atlas's docs and anything else fails fast naming the status; unrecognized download bytes now refuse to write; 35 tests running in milliseconds, including the key-sent-to-Atlas-never-to-CDN assertion. On --resolution 2K/4K printing a note rather than erroring on Atlas: that was your deliberate, documented design and it stands. CI 7/7 on bfcc2bb. Approving and merging — welcome aboard, @binyangzhu000-sudo, and thank you.
Summary
codev generate-imagegoes through the@google/genaiSDK, so it needs a Google AI Studio key.Atlas Cloud serves the same Nano Banana Pro model over its own submit-then-poll REST API, so
this adds it behind
-p/--provider atlas. The default staysgeminiand its code path is untouched.No new dependency — the Atlas path uses global
fetch(the package already requires Node >= 20).Every mapping was measured, not assumed
--provider atlas-a/--aspectaspect_ratio, honoured1:1→ 1024×1024,16:9→ 1376×768-r/--resolution--ref--provider geminiThe two behaviours I deliberately did not paper over:
--resolutionis not silently dropped. A note is printed, because a user asking for 4K andgetting the default resolution with no explanation is worse than being told.
--referrors instead of generating. Ignoring reference images while still producing an imagewould look like success.
output.png→output.jpg) rather than writing JPEG bytes into a.png. The Gemini path is unaffected.Validation
aspect: '16:9'→ a real 685 KB JPEG at1376×768, saved as
.jpgwith the note printed. That run is also what confirms theaspect_ratiomapping above.ATLASCLOUD_API_KEY, unknown provider, refused--ref,a full submit → poll → download asserting the submitted
aspect_ratio, the prediction URL, theexplicit
User-Agentand the JPEG rename, plus a failed prediction.vitest run src/__tests__/generate-image.test.ts→ 19 passed.tsc --noEmitreports nothing for the touched files. (There are pre-existing errors insrc/agent-farm/*,src/commands/doctor.tsandsrc/lib/github.tsfrom unbuilt workspace deps inmy sandbox — untouched by this PR.)
While writing the test I hit a fixture trap worth mentioning:
Buffer.from([...]).bufferis apooled ArrayBuffer with a non-zero offset, so slicing it from 0 hands back the wrong bytes and the
JPEG-detection assertion failed for the wrong reason. The test uses
new Uint8Array([...])and sayswhy in a comment.
Docs
Both skeleton copies (
.claudeand.codex) get the new flag in the flag list plus a short"Alternative provider" section with the measured differences. The key is an environment-variable
placeholder; no credentials in the diff.
🤝 Partnership & contact
This PR comes from the Atlas Cloud team. Beyond the integration above, we'd love to explore a closer collaboration with codev — for example co-marketing or a featured integration.
If that sounds interesting, reach out anytime:
And of course, happy to revise this PR to match your project's conventions — just leave a comment. 🙌