Skip to content

feat(images): Generate images with GPT Image through the AI SDK - #2317

Merged
dcramer merged 2 commits into
mainfrom
feat/image-only-models
Nov 2, 2026
Merged

dcramer merged 2 commits into
mainfrom
feat/image-only-models

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Switch imageGenerate to the AI SDK and make GPT Image the default image model.

The tool now calls generateImage() with gateway.imageModel(modelId) from @ai-sdk/gateway. A prompt-only call generates an image. A call with input images sends them as prompt: { text, images }, which the gateway runs as an edit. The SDK owns the request, response parsing and retries, so the hand-written /v1/chat/completions request is gone.

  • The default imageGenerationModelId / AI_IMAGE_MODEL is now openai/gpt-image-2 (was google/gemini-3-pro-image).
  • AI_IMAGE_MODEL must name an AI Gateway image model. A language model such as Gemini image gets the existing config hint error.
  • Auth uses the shared resolveGatewayCredential(), like webSearch.
  • Prompt enrichment, input-image resizing and artifact delivery do not change.
  • The eval gateway stub now answers /v4/ai/image-model instead of chat completions.

Verification

  • Live, against AI Gateway with the same SDK calls: one generation and one edit on openai/gpt-image-2 returned PNGs (about 11s and 13s). google/gemini-3-pro-image on this path returns "is a language model, not an image model".
  • Unit tests for imageGenerate and chat config pass. Typecheck, oxlint and the file-length check pass.
  • Evals: earlier runs failed in setup because the Roach recording service was not reachable. This needs a rerun after Roach is back.

This replaces the earlier prototype on this branch, which looked up the model type and kept two raw fetch paths.

via greg.pstrucha.

--

View Junior Session [Sentry]

@vercel

vercel Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
junior-docs Ready Ready Preview Nov 2, 2026 10:27pm UTC
junior-example Ready Ready Preview Nov 2, 2026 10:27pm UTC

Request Review

@sentry-evals

sentry-evals Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Sentry Evals

junior-behavioral

Run Status Passed Failed Errored Cost Tokens Duration
Head Error 72 5 1 $0.86 0 590.34s

Base commit 241d24d has no eval run to compare against.

junior-guardian

Run Status Passed Failed Errored Cost Tokens Duration
Head Passed 94 0 0 $0.03 0 25.42s

Base commit 241d24d has no eval run to compare against.

junior-integration

Run Status Passed Failed Errored Cost Tokens Duration
Head Failed 101 1 0 $0.57 0 292.02s

Base commit 241d24d has no eval run to compare against.

@gricha
gricha marked this pull request as ready for review October 29, 2026 03:49
@github-actions github-actions Bot added the risk: high PR risk score: high label Oct 29, 2026
@gricha
gricha requested a review from dcramer October 29, 2026 03:50

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3c0c46a. Configure here.

@sentry-junior
sentry-junior Bot force-pushed the feat/image-only-models branch from ab6df7b to 07159e5 Compare October 29, 2026 04:09
@sentry-junior
sentry-junior Bot force-pushed the feat/image-only-models branch from 9b83d84 to 8f645e3 Compare October 29, 2026 04:17
@sentry-junior
sentry-junior Bot force-pushed the feat/image-only-models branch from 8f645e3 to cd6aa99 Compare October 29, 2026 04:53
@sentry-junior sentry-junior Bot changed the title feat(images): Support image-only models such as GPT Image feat(images): Generate images with GPT Image through the AI SDK Oct 29, 2026
@sentry-junior
sentry-junior Bot force-pushed the feat/image-only-models branch from cd6aa99 to 04fd90a Compare October 29, 2026 04:54
dcramer pushed a commit that referenced this pull request Oct 29, 2026
Junior apps no longer install `@sentry/nitro` themselves.
`@sentry/junior` now depends on it, and `@sentry/junior/nitro` exports
`withSentryConfig`.

The Sentry SDK keeps one client for each SDK version
(`globalThis.__SENTRY__[SDK_VERSION]`). `initSentry()` creates that
client with Junior's `@sentry/node`. If an app pins an `@sentry/nitro`
version that differs from Junior's `@sentry/node`, the Nitro runtime
hooks find no client. The error hook and request spans then do nothing,
and nothing reports it. Before this change, each app had to keep its
`@sentry/nitro` pin equal to the `@sentry/node` version inside
`@sentry/junior` by hand.

Now `@sentry/nitro` and `@sentry/node` are next to each other in the
pnpm catalog, so they get bumped together. Apps write:

```ts
import { juniorNitro, withSentryConfig } from "@sentry/junior/nitro";
```

`withSentryConfig` is a thin wrapper around the SDK function. It sets
one default: the build release name is `getDeploymentServiceVersion()`,
which is `<junior version>+<VERCEL_GIT_COMMIT_SHA>`. This is the same
release that `initSentry()` reports. Without this default,
`@sentry/nitro` names the build release after the bare commit SHA, so
source maps and runtime events would be under two different releases. An
explicit `release.name` still wins. The wrapper types come from
`@sentry/nitro`'s own signature, so `dist/nitro.d.ts` does not inline
Nitro's types.

The `apps/example` app, the `junior init` scaffold, and the
`existing-app` docs use the new import. The package README no longer
tells apps to install `@sentry/node`. That instruction is how
`getsentry/junior-prod` got an unused second copy
(`@sentry/node@10.49.0` next to Junior's 10.65.0).

Why now: `junior-prod` never adopted `withSentryConfig`, so production
source maps are never uploaded. Production stack frames point at
`_libs/@sentry/junior+[...].mjs` around line 226k with `js_no_source`.
getsentry/junior-prod#568 is the draft follow-up that uses this export.

**Trade-off:** every Junior app now installs `@sentry/nitro`,
`@sentry/bundler-plugin-core`, and `@sentry/cli`. `@sentry/junior`
already hard-depends on `@sentry/node`, so this seemed acceptable.
Without a Sentry auth token, the build logs a warning and skips the
upload.

**Not done here:** wrapping the Sentry setup inside `juniorNitro()`
itself. `@sentry/nitro` does not export the module factory for that, and
it must change the config before modules run. Doing it here would mean
copying SDK internals.

**Verification**
- `pnpm --filter @sentry/junior build`: `dist/nitro.js` imports
`@sentry/nitro` as an external dependency, and `dist/nitro.d.ts` stays
small (47 lines).
- `pnpm typecheck` and `pnpm lint` in `packages/junior`, and `pnpm
typecheck` in `apps/example`.
- `vitest run` on `init-cli`, `check-cli`, and `nitro-plugin-module`
passed.
- A temporary test with a mocked `@sentry/nitro` checked the release
default. It passed `0.264.0+deadbeef` when `VERCEL_GIT_COMMIT_SHA` was
set, kept an explicit name, and left the name undefined when no SHA was
set.
- `nitro build` in `apps/example`: one `@sentry/core` (10.65.0) is in
the output, and `scripts/check-vercel-output.mjs` passes.
- `pnpm build:ci` in `junior-prod` linked to this branch: the build
passes with one `@sentry/core` (10.65.0). The upload step ran
`sentry-cli releases new 0.264.0+deadbeef`. The sandbox token was not
authorized, so the step got a 401 and created no release.
- `pnpm release:check` passes.
- The behavioral evals failure also occurs on unrelated PRs (#2339,
#2328, #2317, #2313). The integration evals pass.
- I did not test a real source map upload.

<!-- junior-request-attribution:start -->
via **Francesco Gringl-Novy**.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:slack%3AD0CA8FXFXAS%3A1793266810.973359 -->

--

[View Junior
Session](https://junior-prod.sentry.dev/conversations/slack%3AD0CA8FXFXAS%3A1793266810.973359)
[[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AD0CA8FXFXAS%3A1793266810.973359/?project=4510944073809921)

<!-- junior-session-footer:end -->

---------

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: Francesco Gringl-Novy <francesco.novy@sentry.io>
dcramer added a commit that referenced this pull request Oct 29, 2026
The integration eval `conversation/delivery.eval.ts` > "when asked for a
top-level channel post, explain the limitation instead" fails on `main`
since #2315. This change fixes the Slack delivery rule in `prompt.ts`
that the eval checks.

**Why it fails**

#2315 merged the two Slack delivery rules into one line. The merged line
ties the explanation to tool calls ("explain the limitation instead of
calling a tool"). It also says that assistant text goes to the current
conversation. For `post this to the channel: deploy is unblocked`, a
plain reply with the text calls no tool and lands in the current
conversation, so it looked allowed. Both live samples on #2317 replied
only `deploy is unblocked`.

Before #2315, every recorded live sample passed, and each reply used the
old wording: "I can't create a new top-level channel post…".

**Is the eval correct?**

Yes. The mention is a top-level channel message. Junior can only reply
in the thread under it, so a reply with only the requested text is
wrong. The rubric follows the product rule, and the eval file has not
changed since Oct 8.

**Change**

The delivery rule stays one line, so the #2315 cleanup holds. It now:
- states the fact: a reply is not a channel post, and Junior cannot
create a new top-level channel post
- says what to do for any "post to a channel or anywhere else" request:
explain the limitation, call no tool, and do not reply with only the
requested text or present it as delivered

This PR also removes `tests/unit/prompt.test.ts`. It only checked that
the prompt output contains static strings. Evals own prompt behavior.

**Verification**

- `tsc --noEmit` passes for `@sentry/junior`.
- I could not run evals live locally (no AI Gateway or Roach
credentials). The integration eval workflow does not watch
`packages/junior/src/**`, so this PR has the `trigger-evals-integration`
label. The prompt change makes every model request new, so the whole
integration suite runs live. That gives a real comparison against the
failures on #2317.

**Open question**

Integration and behavioral evals do not run for `packages/junior/src/**`
changes, and no eval suite runs on `main`. That gap let #2315 merge
without this eval. It is out of scope here.

<!-- junior-request-attribution:start -->
via **David Cramer**.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:slack%3AC0B595QDZLL%3A1793251347.186889 -->

--

[View Junior
Session](https://junior-prod.sentry.dev/conversations/slack%3AC0B595QDZLL%3A1793251347.186889)
[[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AC0B595QDZLL%3A1793251347.186889/?project=4510944073809921)

<!-- junior-session-footer:end -->

---------

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: David Cramer <david@sentry.io>
sentry-junior Bot and others added 2 commits November 2, 2026 21:48
Switch imageGenerate to the AI SDK `generateImage()` call with a Gateway
image model. Prompt-only calls generate an image. Calls with input images
edit those images. The SDK owns the request, the response parsing and the
retries, so the hand-written chat completions request goes away.

The default image model is now `openai/gpt-image-2`. AI_IMAGE_MODEL must
name a Gateway image model. A language model such as
`google/gemini-3-pro-image` gets a config hint error.

Co-Authored-By: Greg Pstrucha <greg.pstrucha@sentry.io>
The tool error check rejects a throw hidden behind a helper. Throw the
config error where it happens and move its baseline entry to the new
line. The model id comes from app config, so it stays a plain Error.

Co-Authored-By: Greg Pstrucha <greg.pstrucha@sentry.io>
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

3 screenshot changes — 3 changed · 0 added · 0 removed

Review screenshots in Frameshift

Conversation Child Agent Drawer · Desktop
Conversation Child Agent Drawer · Desktop
Changed
Conversation Child Agent Drawer · Mobile
Conversation Child Agent Drawer · Mobile
Changed
Settings Mcp · Desktop
Settings Mcp · Desktop
Changed

@dcramer
dcramer merged commit d2605d4 into main Nov 2, 2026
51 of 53 checks passed
@dcramer
dcramer deleted the feat/image-only-models branch November 2, 2026 22:40

This branch was successfully deployed

2 active deployments
Production – junior-example — 9092ff8f Deployed Nov 2, 2026 by vercel[bot]
Preview – junior-docs — 9092ff8f Deployed Nov 2, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: high PR risk score: high

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant