Skip to content

feat: previousJobId and previousImage for follow-up media edits#927

Open
jherr wants to merge 8 commits into
mainfrom
feat/media-edit-from
Open

feat: previousJobId and previousImage for follow-up media edits#927
jherr wants to merge 8 commits into
mainfrom
feat/media-edit-from

Conversation

@jherr

@jherr jherr commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add first-class previousJobId on generateVideo so callers always pass the prior job id; 'job'-kind adapters (Sora remix, Gemini Omni) reference it server-side, 'media'-kind adapters (Grok, fal) resolve the clip via getVideoUrl (fal generate→edit sibling routing included). Omni’s previous_interaction_id is removed from video modelOptions.
  • Add previousImage on generateImage to prepend a prior GeneratedImage (or array / result) into the prompt for models that accept image inputs.
  • Docs, skill, examples, unit tests, and e2e coverage for image/video follow-up edits.

Test plan

  • pnpm test:pr
  • pnpm --filter @tanstack/ai-e2e test:e2e (353 passed; video-edit / image-edit specs green)
  • Spot-check docs: docs/media/video-generation.md (previousJobId) and docs/media/image-generation.md (previousImage)

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Added follow-up editing for generated images using a previous image as input.
    • Added follow-up editing and remixing for supported generated videos using a previous job.
    • Added edit controls and prompts to image and video examples.
    • Added end-to-end coverage for image and video editing workflows.
  • Documentation

    • Documented image and video editing options, supported models, and usage examples.
    • Updated media-generation guidance to use the new follow-up editing flow.
  • Chores

    • Added local AgentsRoom data files and ignore rules.

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

19 package(s) bumped directly, 26 bumped as dependents.

🟥 Major bumps

Package Version Reason
@tanstack/ai-angular 0.2.3 → 1.0.0 Changeset
@tanstack/ai-anthropic 0.16.1 → 1.0.0 Changeset
@tanstack/ai-bedrock 0.1.2 → 1.0.0 Changeset
@tanstack/ai-fal 0.9.10 → 1.0.0 Changeset
@tanstack/ai-gemini 0.19.1 → 1.0.0 Changeset
@tanstack/ai-grok 0.14.7 → 1.0.0 Changeset
@tanstack/ai-groq 0.5.1 → 1.0.0 Changeset
@tanstack/ai-mistral 0.2.1 → 1.0.0 Changeset
@tanstack/ai-ollama 0.8.14 → 1.0.0 Changeset
@tanstack/ai-openai 0.16.0 → 1.0.0 Changeset
@tanstack/ai-openrouter 0.15.8 → 1.0.0 Changeset
@tanstack/ai-preact 0.10.3 → 1.0.0 Changeset
@tanstack/ai-react 0.16.4 → 1.0.0 Changeset
@tanstack/ai-sandbox 0.2.2 → 1.0.0 Changeset
@tanstack/ai-solid 0.14.3 → 1.0.0 Changeset
@tanstack/ai-svelte 0.14.3 → 1.0.0 Changeset
@tanstack/ai-vue 0.14.3 → 1.0.0 Changeset
@tanstack/ai-acp 0.2.1 → 1.0.0 Dependent
@tanstack/ai-claude-code 0.2.1 → 1.0.0 Dependent
@tanstack/ai-code-mode 0.3.6 → 1.0.0 Dependent
@tanstack/ai-code-mode-skills 0.3.9 → 1.0.0 Dependent
@tanstack/ai-codex 0.2.1 → 1.0.0 Dependent
@tanstack/ai-elevenlabs 0.2.32 → 1.0.0 Dependent
@tanstack/ai-grok-build 0.2.1 → 1.0.0 Dependent
@tanstack/ai-isolate-node 0.1.45 → 1.0.0 Dependent
@tanstack/ai-isolate-quickjs 0.1.45 → 1.0.0 Dependent
@tanstack/ai-opencode 0.2.1 → 1.0.0 Dependent
@tanstack/ai-react-ui 0.8.13 → 1.0.0 Dependent
@tanstack/ai-sandbox-cloudflare 0.2.2 → 1.0.0 Dependent
@tanstack/ai-sandbox-daytona 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-docker 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-local-process 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-sprites 0.2.1 → 1.0.0 Dependent
@tanstack/ai-sandbox-vercel 0.2.0 → 1.0.0 Dependent
@tanstack/ai-solid-ui 0.7.12 → 1.0.0 Dependent
@tanstack/openai-base 0.9.7 → 1.0.0 Dependent

🟨 Minor bumps

Package Version Reason
@tanstack/ai 0.40.0 → 0.41.0 Changeset
@tanstack/ai-client 0.20.0 → 0.21.0 Changeset

🟩 Patch bumps

Package Version Reason
@tanstack/ai-devtools-core 0.4.22 → 0.4.23 Dependent
@tanstack/ai-isolate-cloudflare 0.2.36 → 0.2.37 Dependent
@tanstack/ai-mcp 0.2.3 → 0.2.4 Dependent
@tanstack/ai-vue-ui 0.2.31 → 0.2.32 Dependent
@tanstack/preact-ai-devtools 0.1.65 → 0.1.66 Dependent
@tanstack/react-ai-devtools 0.2.65 → 0.2.66 Dependent
@tanstack/solid-ai-devtools 0.2.65 → 0.2.66 Dependent

@nx-cloud

nx-cloud Bot commented Jul 11, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 49e9d02

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 1m 59s View ↗

☁️ Nx Cloud last updated this comment at 2026-07-11 03:10:47 UTC

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds first-class follow-up editing for generated images through previousImage and videos through previousJobId, with compile-time capability gating, provider-specific adapter routing, example UI support, documentation, and end-to-end coverage.

Changes

Media editing

Layer / File(s) Summary
Image follow-up editing
packages/ai/src/activities/generateImage/*, packages/ai/src/utilities/media-prompt.ts, packages/ai-client/src/generation-types.ts, packages/ai/tests/*image*
Previous images are converted into prompt image parts, prepended to prompts, gated by model capabilities, and tested for runtime and type behavior.
Video edit contract and orchestration
packages/ai/src/activities/generateVideo/*, packages/ai/src/types.ts, packages/ai-client/src/generation-types.ts, packages/ai/tests/*video*
Video adapters expose edit kinds, previousJobId is conditionally typed and validated, and both streaming and non-streaming flows forward it to adapters.
Provider edit implementations
packages/ai-openai/*, packages/ai-gemini/*, packages/ai-grok/*, packages/ai-fal/*
OpenAI remix, Gemini interaction chaining, Grok video edits, and fal video-to-video routing are implemented with provider-specific metadata and tests.
Example application editing flows
examples/ts-react-media/*
Example server functions and UI components support image and video follow-up edits, editable-model flags, and polling of adapter-resolved models.
End-to-end edit flows
testing/e2e/*
Image-edit and video-edit features are registered, routed through mock providers, exposed in UI controls, and covered by Playwright scenarios.
Documentation and repository support
docs/media/*, packages/ai/skills/*, .changeset/*, .agentsroom/*, docs/config.json
Editing behavior, provider constraints, public API changes, release metadata, and local AgentsRoom configuration are updated.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant MediaActivity
  participant Adapter
  participant ProviderAPI
  Client->>MediaActivity: previousImage or previousJobId
  MediaActivity->>Adapter: normalize, validate, and forward edit input
  Adapter->>ProviderAPI: submit provider-specific edit request
  ProviderAPI-->>Adapter: edited media job or result
  Adapter-->>MediaActivity: generated media result
  MediaActivity-->>Client: edited image or video
Loading

Possibly related PRs

  • TanStack/ai#742: Introduced the Grok video adapter functionality extended here with previousJobId editing.
  • TanStack/ai#886: Introduced the Gemini Omni Flash Interactions flow extended here with top-level previousJobId chaining.

Suggested reviewers: AlemTuzlak, tombeckenham

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.07% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding previousJobId and previousImage support for follow-up media edits.
Description check ✅ Passed The description covers the main changes and testing, but it does not follow the repo template headings or include the checklist and release impact sections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/media-edit-from

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

testing/e2e/global-setup.ts

Parsing error: "parserOptions.project" has been provided for @typescript-eslint/parser.
The file was not found in any of the provided project(s): testing/e2e/global-setup.ts

testing/e2e/tests/image-edit.spec.ts

Parsing error: "parserOptions.project" has been provided for @typescript-eslint/parser.
The file was not found in any of the provided project(s): testing/e2e/tests/image-edit.spec.ts

testing/e2e/tests/video-edit.spec.ts

Parsing error: "parserOptions.project" has been provided for @typescript-eslint/parser.
The file was not found in any of the provided project(s): testing/e2e/tests/video-edit.spec.ts


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jul 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@927

@tanstack/ai-acp

npm i https://pkg.pr.new/@tanstack/ai-acp@927

@tanstack/ai-angular

npm i https://pkg.pr.new/@tanstack/ai-angular@927

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@927

@tanstack/ai-bedrock

npm i https://pkg.pr.new/@tanstack/ai-bedrock@927

@tanstack/ai-claude-code

npm i https://pkg.pr.new/@tanstack/ai-claude-code@927

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@927

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@927

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/@tanstack/ai-code-mode-skills@927

@tanstack/ai-codex

npm i https://pkg.pr.new/@tanstack/ai-codex@927

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@927

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@927

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@927

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@927

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@927

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@927

@tanstack/ai-grok-build

npm i https://pkg.pr.new/@tanstack/ai-grok-build@927

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@927

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@927

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@927

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@927

@tanstack/ai-mcp

npm i https://pkg.pr.new/@tanstack/ai-mcp@927

@tanstack/ai-mistral

npm i https://pkg.pr.new/@tanstack/ai-mistral@927

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@927

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@927

@tanstack/ai-opencode

npm i https://pkg.pr.new/@tanstack/ai-opencode@927

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@927

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@927

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@927

@tanstack/ai-react-ui

npm i https://pkg.pr.new/@tanstack/ai-react-ui@927

@tanstack/ai-sandbox

npm i https://pkg.pr.new/@tanstack/ai-sandbox@927

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-sandbox-cloudflare@927

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/@tanstack/ai-sandbox-daytona@927

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/@tanstack/ai-sandbox-docker@927

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/@tanstack/ai-sandbox-local-process@927

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/@tanstack/ai-sandbox-sprites@927

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/@tanstack/ai-sandbox-vercel@927

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@927

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/@tanstack/ai-solid-ui@927

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@927

@tanstack/ai-utils

npm i https://pkg.pr.new/@tanstack/ai-utils@927

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@927

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/@tanstack/ai-vue-ui@927

@tanstack/openai-base

npm i https://pkg.pr.new/@tanstack/openai-base@927

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@927

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@927

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@927

commit: 49e9d02

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
testing/e2e/src/lib/server-functions.ts (1)

46-56: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the previousImage normalization into a shared helper.

The same url-or-b64Json narrowing logic is duplicated in api.image.stream.ts (lines 34-39) and api.image.ts (lines 34-39). If the wire shape or generateImage's previousImage contract changes, all three copies need updating in sync.

♻️ Optional: shared helper
+// e.g. in media-providers.ts or a new test-utils file
+export function normalizePreviousImage(
+  raw: { url?: string; b64Json?: string } | undefined,
+): { url: string } | { b64Json: string } | undefined {
+  if (raw?.url != null) return { url: raw.url }
+  if (raw?.b64Json != null) return { b64Json: raw.b64Json }
+  return undefined
+}

Then in each call site:

-    const previousImage =
-      data.previousImage?.url != null
-        ? { url: data.previousImage.url }
-        : data.previousImage?.b64Json != null
-          ? { b64Json: data.previousImage.b64Json }
-          : undefined
+    const previousImage = normalizePreviousImage(data.previousImage)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@testing/e2e/src/lib/server-functions.ts` around lines 46 - 56, Extract the
duplicated previousImage URL-or-b64Json normalization into a shared helper, then
replace the inline logic in server-functions.ts, api.image.stream.ts, and
api.image.ts with calls to that helper. Preserve the existing undefined behavior
and generateImage-compatible shape, and place the helper where all three call
sites can import it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/media/image-generation.md`:
- Around line 219-239: Extend the image-generation example with a minimal server
endpoint matching the client request, validating the wire-friendly previousImage
shape, narrowing it to a GeneratedImage, and passing it to generateImage.
Reference the route handler and generateImage usage, and show handling for
invalid payloads and both URL and base64 image variants.

In `@docs/media/video-generation.md`:
- Around line 531-536: Update handleEdit to use the hook-provided job identifier
rather than result?.jobId: destructure jobId (or videoStatus.jobId) from the
hook and use it for the guard and as generate’s previousJobId value.
- Around line 520-536: Add the corresponding server endpoint example after the
client snippet, defining a minimal route that validates the incoming prompt and
optional previousJobId, then passes both values to generateVideo. Show the
response returned to the client and ensure the route’s symbols clearly
demonstrate forwarding previousJobId.

In `@examples/ts-react-media/src/components/ImageGenerator.tsx`:
- Around line 362-379: Remove the unnecessary non-null assertions from
modelResult.result in both handleEditImage calls within the success block, while
retaining the images[0]! assertion. Update both the Enter-key handler and button
onClick handler.

In `@packages/ai-fal/src/adapters/video.ts`:
- Around line 237-243: The polling path must use the model submitted for the job
rather than this.model. Update getVideoStatus and getVideoUrl, along with their
callers and job state as needed, to preserve submitModel from the queue
submission and pass it to fal.queue.status and fal.queue.result for edit jobs.

In `@testing/e2e/tests/video-edit.spec.ts`:
- Around line 17-20: Add a runtime guard wherever the video-edit test looks up
EXPECTED_EDITED_SRC by provider, including the assertions around the affected
test cases, and fail clearly if the provider is missing instead of relying on a
non-null assertion. Use the provider value to validate the map entry before
passing it to toHaveAttribute, while preserving the existing expected regex
behavior.

---

Nitpick comments:
In `@testing/e2e/src/lib/server-functions.ts`:
- Around line 46-56: Extract the duplicated previousImage URL-or-b64Json
normalization into a shared helper, then replace the inline logic in
server-functions.ts, api.image.stream.ts, and api.image.ts with calls to that
helper. Preserve the existing undefined behavior and generateImage-compatible
shape, and place the helper where all three call sites can import it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ccfc2443-dc3d-4ced-81d9-5cf40ba1e607

📥 Commits

Reviewing files that changed from the base of the PR and between 5fcaf90 and 49e9d02.

📒 Files selected for processing (56)
  • .agentsroom/.gitignore
  • .agentsroom/agents.json
  • .agentsroom/prompts.json
  • .changeset/media-edit-from.md
  • docs/config.json
  • docs/media/image-generation.md
  • docs/media/video-generation.md
  • examples/ts-react-media/src/components/ImageGenerator.tsx
  • examples/ts-react-media/src/components/OmniStudio.tsx
  • examples/ts-react-media/src/components/VideoGenerator.tsx
  • examples/ts-react-media/src/lib/models.ts
  • examples/ts-react-media/src/lib/server-functions.ts
  • packages/ai-client/src/generation-types.ts
  • packages/ai-fal/src/adapters/video.ts
  • packages/ai-fal/src/index.ts
  • packages/ai-fal/src/model-meta.ts
  • packages/ai-fal/tests/video-adapter.test.ts
  • packages/ai-gemini/src/adapters/video.ts
  • packages/ai-gemini/src/index.ts
  • packages/ai-gemini/src/video/video-provider-options.ts
  • packages/ai-gemini/tests/video-adapter.test.ts
  • packages/ai-grok/src/adapters/video.ts
  • packages/ai-grok/src/index.ts
  • packages/ai-grok/src/video/video-provider-options.ts
  • packages/ai-grok/tests/video-adapter.test.ts
  • packages/ai-openai/src/adapters/video.ts
  • packages/ai-openai/src/index.ts
  • packages/ai-openai/src/video/video-provider-options.ts
  • packages/ai-openai/tests/video-adapter.test.ts
  • packages/ai/skills/ai-core/media-generation/SKILL.md
  • packages/ai/src/activities/generateImage/index.ts
  • packages/ai/src/activities/generateVideo/adapter.ts
  • packages/ai/src/activities/generateVideo/index.ts
  • packages/ai/src/activities/index.ts
  • packages/ai/src/client.ts
  • packages/ai/src/index.ts
  • packages/ai/src/types.ts
  • packages/ai/src/utilities/media-prompt.ts
  • packages/ai/tests/generate-image-previous-image.test.ts
  • packages/ai/tests/generate-video-previous-job-id.test.ts
  • packages/ai/tests/stream-generation.test.ts
  • testing/e2e/global-setup.ts
  • testing/e2e/src/components/ImageGenUI.tsx
  • testing/e2e/src/components/VideoGenUI.tsx
  • testing/e2e/src/lib/feature-support.ts
  • testing/e2e/src/lib/features.ts
  • testing/e2e/src/lib/media-providers.ts
  • testing/e2e/src/lib/server-functions.ts
  • testing/e2e/src/lib/types.ts
  • testing/e2e/src/routes/$provider/$feature.tsx
  • testing/e2e/src/routes/api.image.stream.ts
  • testing/e2e/src/routes/api.image.ts
  • testing/e2e/src/routes/api.video.stream.ts
  • testing/e2e/src/routes/api.video.ts
  • testing/e2e/tests/image-edit.spec.ts
  • testing/e2e/tests/video-edit.spec.ts

Comment on lines +219 to +239
**Client** — the hook's `ImageGenerateInput.previousImage` is a wire-friendly
`{ url? }` / `{ b64Json? }` shape; your server route should narrow it back
to a `GeneratedImage` before calling `generateImage`:

```tsx
import { useGenerateImage, fetchServerSentEvents } from '@tanstack/ai-react'

function ImageEditor() {
const { generate, result, isLoading } = useGenerateImage({
connection: fetchServerSentEvents('/api/generate/image'),
})

const handleEdit = () => {
const image = result?.images[0]
if (!image) return
void generate({
prompt: 'Same scene, but make it rain',
previousImage: image.url
? { url: image.url }
: { b64Json: image.b64Json },
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Include the server endpoint half of this example.

The client snippet sends a wire-friendly { url? } / { b64Json? } value and explicitly says the server must narrow it, but no route showing that conversion is provided. Add a minimal server endpoint that validates the payload and passes a GeneratedImage to generateImage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/media/image-generation.md` around lines 219 - 239, Extend the
image-generation example with a minimal server endpoint matching the client
request, validating the wire-friendly previousImage shape, narrowing it to a
GeneratedImage, and passing it to generateImage. Reference the route handler and
generateImage usage, and show handling for invalid payloads and both URL and
base64 image variants.

Source: Coding guidelines

Comment on lines +520 to +536
**Client** — pass the completed job's id through the hook; your server
forwards it to `generateVideo`:

```tsx
import { useGenerateVideo, fetchServerSentEvents } from '@tanstack/ai-react'

function VideoEditor() {
const { generate, result, isLoading } = useGenerateVideo({
connection: fetchServerSentEvents('/api/generate/video'),
})

const handleEdit = () => {
if (!result?.jobId) return
void generate({
prompt: 'Make it rain',
previousJobId: result.jobId,
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the server endpoint half of the client example.

The example says the server forwards previousJobId but does not show that endpoint. Add a minimal route that validates the client input and forwards the job ID to generateVideo.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/media/video-generation.md` around lines 520 - 536, Add the corresponding
server endpoint example after the client snippet, defining a minimal route that
validates the incoming prompt and optional previousJobId, then passes both
values to generateVideo. Show the response returned to the client and ensure the
route’s symbols clearly demonstrate forwarding previousJobId.

Source: Coding guidelines

Comment on lines +531 to +536
const handleEdit = () => {
if (!result?.jobId) return
void generate({
prompt: 'Make it rain',
previousJobId: result.jobId,
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the hook’s job id instead of result.jobId.

The documented hook exposes the job identifier separately (jobId/videoStatus.jobId), while the completed result contains the video result. As written, result?.jobId can be undefined and the edit handler silently returns. Destructure jobId and pass that value as previousJobId.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/media/video-generation.md` around lines 531 - 536, Update handleEdit to
use the hook-provided job identifier rather than result?.jobId: destructure
jobId (or videoStatus.jobId) from the hook and use it for the guard and as
generate’s previousJobId value.

Comment on lines +362 to +379
onKeyDown={(e) => {
if (e.key === 'Enter')
handleEditImage(
modelId,
modelResult.result!.images[0]!,
)
}}
placeholder="Describe an edit — e.g. 'make it night time'..."
disabled={isLoading}
className="flex-1 px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-white text-sm placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent disabled:opacity-50"
/>
<button
onClick={() =>
handleEditImage(
modelId,
modelResult.result!.images[0]!,
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the redundant modelResult.result! non-null assertion (ESLint error).

Inside the status === 'success' && modelResult.result && ... block, modelResult.result is already narrowed to non-null, so the ! after result is unnecessary and is flagged as an error by @typescript-eslint/no-unnecessary-type-assertion (lines 366 and 377), which will fail lint. Keep the ! on images[0] only.

🧹 Proposed fix
                             onKeyDown={(e) => {
                               if (e.key === 'Enter')
                                 handleEditImage(
                                   modelId,
-                                  modelResult.result!.images[0]!,
+                                  modelResult.result.images[0]!,
                                 )
                             }}
                           onClick={() =>
                             handleEditImage(
                               modelId,
-                              modelResult.result!.images[0]!,
+                              modelResult.result.images[0]!,
                             )
                           }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onKeyDown={(e) => {
if (e.key === 'Enter')
handleEditImage(
modelId,
modelResult.result!.images[0]!,
)
}}
placeholder="Describe an edit — e.g. 'make it night time'..."
disabled={isLoading}
className="flex-1 px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-white text-sm placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent disabled:opacity-50"
/>
<button
onClick={() =>
handleEditImage(
modelId,
modelResult.result!.images[0]!,
)
}
onKeyDown={(e) => {
if (e.key === 'Enter')
handleEditImage(
modelId,
modelResult.result.images[0]!,
)
}}
placeholder="Describe an edit — e.g. 'make it night time'..."
disabled={isLoading}
className="flex-1 px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-white text-sm placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent disabled:opacity-50"
/>
<button
onClick={() =>
handleEditImage(
modelId,
modelResult.result.images[0]!,
)
}
🧰 Tools
🪛 ESLint

[error] 366-366: This assertion is unnecessary since the receiver accepts the original type of the expression.

(@typescript-eslint/no-unnecessary-type-assertion)


[error] 377-377: This assertion is unnecessary since the receiver accepts the original type of the expression.

(@typescript-eslint/no-unnecessary-type-assertion)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/ts-react-media/src/components/ImageGenerator.tsx` around lines 362 -
379, Remove the unnecessary non-null assertions from modelResult.result in both
handleEditImage calls within the success block, while retaining the images[0]!
assertion. Update both the Enter-key handler and button onClick handler.

Source: Linters/SAST tools

Comment on lines +237 to +243
const { request_id } = await fal.queue.submit(submitModel, {
input,
})

return {
jobId: request_id,
model: this.model,
model: submitModel,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if fal.queue.status/result require the correct model or work with just requestId.
# Inspect the fal SDK types for queue status and result method signatures.
fd -t f 'client' packages/ai-fal/node_modules/@fal-ai/client/dist --exec cat -n {} 2>/dev/null | head -200
# Also check the SDK's type definitions for queue methods
fd -e d.ts . packages/ai-fal/node_modules/@fal-ai/client --exec cat -n {} 2>/dev/null | rg -C5 'queue\.(status|result)' | head -60

Repository: TanStack/ai

Length of output: 149


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the adapter file and inspect the relevant symbols.
ast-grep outline packages/ai-fal/src/adapters/video.ts --view expanded

# Read the relevant section around submit/status/result.
sed -n '180,340p' packages/ai-fal/src/adapters/video.ts

# Find all references to the edit-sibling mapping and polling calls.
rg -n "FAL_VIDEO_EDIT_BY_SOURCE|getVideoStatus|getVideoUrl|queue\.(submit|status|result)|submitModel|previousJobId" packages/ai-fal/src/adapters/video.ts packages/ai-fal/src -S

Repository: TanStack/ai

Length of output: 11238


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the fal package version and any local type references.
sed -n '1,220p' packages/ai-fal/package.json
rg -n "`@fal-ai/client`|queue\.(status|result)|requestId|model:" packages/ai-fal -S

# Look for lockfile entries that reveal the installed SDK version.
rg -n '"`@fal-ai/client`"|`@fal-ai/client`' package-lock.json pnpm-lock.yaml yarn.lock -S

Repository: TanStack/ai

Length of output: 14728


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the video adapter tests around edit jobs and polling.
sed -n '240,470p' packages/ai-fal/tests/video-adapter.test.ts

# Inspect any local mock of `@fal-ai/client` to see how queue.status/result are exercised.
sed -n '1,120p' packages/ai-fal/tests/video-adapter.test.ts

Repository: TanStack/ai

Length of output: 10623


🌐 Web query:

@fal-ai/client queue.status queue.result signature requestId model

💡 Result:

In the @fal-ai/client (and the corresponding Python fal_client), the queue-based workflow allows for asynchronous inference [1][2]. When you submit a request to a model, you receive a request ID, which is then used to track the status and retrieve the final result [1][2]. Queue Methods: 1. queue.submit: Initiates an asynchronous request to a model [1][2]. It accepts the model identifier (e.g., 'username/app-name') and an input object, returning a response that includes the request_id [1]. 2. queue.status: Used to poll the state of a submitted request using its request_id [1][3]. You can optionally enable logs by setting the logs parameter to true [1][3]. The status indicates whether the request is IN_QUEUE, IN_PROGRESS, or COMPLETED [2][3]. If in queue, it provides the queue position [2][3]. 3. queue.result: Retrieves the final output of the model once the request has transitioned to the COMPLETED state [1][2]. This method is called with the model identifier and the request_id [1][3]. Example Usage (JavaScript): const { request_id } = await fal.queue.submit("your-username/your-app-name", { input: { prompt: "a sunset over mountains" }, }); // Check status const status = await fal.queue.status("your-username/your-app-name", { requestId: request_id, logs: true, }); // Get result when ready const result = await fal.queue.result("your-username/your-app-name", { requestId: request_id, });

Citations:


Poll edit jobs against submitModel
getVideoStatus and getVideoUrl still call fal.queue.status/fal.queue.result with this.model, but previousJobId flows submit to the mapped edit sibling. That can hit the wrong queue endpoint for edit jobs; pass the submitted model through the polling path as well.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-fal/src/adapters/video.ts` around lines 237 - 243, The polling
path must use the model submitted for the job rather than this.model. Update
getVideoStatus and getVideoUrl, along with their callers and job state as
needed, to preserve submitModel from the queue submission and pass it to
fal.queue.status and fal.queue.result for edit jobs.

Comment on lines +17 to +20
const EXPECTED_EDITED_SRC: Record<string, RegExp> = {
openai: /guitar-store-remixed\.mp4$/,
gemini: /^data:video\/mp4;base64,AAAAIGZ0eXBpc29tAAACAGVkaXRlZA==$/,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Guard against missing provider in EXPECTED_EDITED_SRC.

If providersFor('video-edit') returns a provider not in the map, the ! assertion silently passes undefined to toHaveAttribute, producing a confusing failure. A runtime guard gives a clear error instead.

🛡️ Proposed guard
 const EXPECTED_EDITED_SRC: Record<string, RegExp> = {
   openai: /guitar-store-remixed\.mp4$/,
   gemini: /^data:video\/mp4;base64,AAAAIGZ0eXBpc29tAAACAGVkaXRlZA==$/,
 }

 for (const provider of providersFor('video-edit')) {
+  const expectedEditedSrc = EXPECTED_EDITED_SRC[provider]
+  if (!expectedEditedSrc) {
+    throw new Error(`No expected edited src configured for provider: ${provider}`)
+  }
   test.describe(`${provider} -- video-edit`, () => {
     test('sse -- edits a completed generation via previousJobId', async ({
       // ...
       await expect(video).toHaveAttribute(
         'src',
-        EXPECTED_EDITED_SRC[provider]!,
+        expectedEditedSrc,
         { timeout: 60_000 },
       )
       // ...
     })
     test('fetcher -- edits a completed generation via server function', async ({
       // ...
       await expect(video).toHaveAttribute(
         'src',
-        EXPECTED_EDITED_SRC[provider]!,
+        expectedEditedSrc,
         { timeout: 60_000 },
       )
     })
   })
 }

Also applies to: 47-47, 75-75

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@testing/e2e/tests/video-edit.spec.ts` around lines 17 - 20, Add a runtime
guard wherever the video-edit test looks up EXPECTED_EDITED_SRC by provider,
including the assertions around the affected test cases, and fail clearly if the
provider is missing instead of relying on a non-null assertion. Use the provider
value to validate the map entry before passing it to toHaveAttribute, while
preserving the existing expected regex behavior.

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