Skip to content

Phase 1: API Key Config & i2i Variations — Worker#44

Open
mcarlson wants to merge 7 commits into
stagefrom
feat/api-key-config-i2i
Open

Phase 1: API Key Config & i2i Variations — Worker#44
mcarlson wants to merge 7 commits into
stagefrom
feat/api-key-config-i2i

Conversation

@mcarlson

Copy link
Copy Markdown
Collaborator

Phase 1: API Key Config & i2i Variations — Worker

Adds the GPU/worker side for user-supplied image-generation endpoints: a dedicated user-endpoint queue that dispatches jobs to OpenAI- or FAL-compatible providers using the user's decrypted key, then attaches the result to the dream.

What's in this PR

  • OpenAI adapter — t2i (/images/generations) and i2i (/images/edits) against a user's OpenAI-compatible endpoint.
  • FAL adapter — submit + poll (queue.fal.run) flow for Flux Schnell / Flux Pro etc.
  • user-endpoint queue + job router — registered alongside existing workers (Bull Board included); routes by provider type, validates required job-data fields, uploads the generated image to R2 and attaches it to the dream.

Correctness / hardening

  • Fails the job on upload failureuploadGeneratedImage returns a boolean; the handler now throws on false so the dream is marked failed instead of silently "completed" with no image.
  • Per-request timeouts on all external provider calls (OpenAI gen/edits 120s, source-image download 30s; FAL submit 30s, polls 10s).
  • FAL result-shape fallback (images ?? output.images) and parseFalSize passthrough for named sizes.
  • SSRF guard (url-safety.ts): https-only, rejects loopback / RFC1918 / link-local+metadata / IPv6 ULA / NAT64 / IPv4-mapped-IPv6 (dotted + hex via a proper IPv6 expander); IPv6 literals validated directly. OpenAI calls pin the validated IP (maxRedirects: 0); the FAL submit is pinned, and the fixed-host queue.fal.run polls are re-validated. The R2 presigned source-image download (our own storage) is intentionally not pinned.
  • url-safety check script covering the adversarial BLOCK/ALLOW table + the pinned-lookup contract.

Cross-repo dependency

This is 1 of 3 PRs and is not functional alone:

  • backend: entity/CRUD/encryption + routes the user-endpoint jobs to this queue
  • frontend: endpoint management UI + studio integration

Job-data contract (dream_uuid, userEndpointDecryptedKey, userEndpointUrl, userEndpointProvider, userEndpointModelId, prompt, image, size, n) is verified key-by-key against the backend producer. Merge the three together.

Verification

npm run build (tsc) ✓ · url-safety checks ✓

Plan & design: docs/superpowers/plans/2026-06-12-api-key-config-i2i-variations.md (metarepo).

🤖 Generated with Claude Code

mcarlson and others added 7 commits June 13, 2026 16:46
Handles text-to-image (JSON POST to /images/generations) and
image-to-image (multipart/form-data POST to /images/edits). Downloads
source images as buffers, uploads results to R2, supports b64_json
responses. Installs form-data package.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Handles both sync (images returned directly) and async (request_id
polling via queue.fal.run) FAL API responses. Parses size as WxH
object, maps queue position to job progress, uploads results to R2.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Routes jobs to OpenAI or FAL adapter based on userEndpointProvider.
Validates required fields, calls adapter, then updates the dream via
videoServiceClient.uploadGeneratedImage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds WorkerFactory.createWorker for user-endpoint, creates the Queue
instance, and adds it to Bull Board adapters.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… re-validate poll URLs to close rebinding TOCTOU
…ion, allow IPv6 literals, fix FAL comments, add url-safety tests

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant