Skip to content

Commit d4e48f5

Browse files
authored
feat(core): turn experimental_image_generate on by default (#60258)
1 parent f74b141 commit d4e48f5

8 files changed

Lines changed: 28 additions & 38 deletions

File tree

‎packages/core/src/session/execution/restart.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export const layer = (options?: Options) =>
159159
body,
160160
}),
161161
metadata: {
162-
source: "image_generate",
162+
source: "experimental_image_generate",
163163
jobID: background.id,
164164
state: background.status === "running" ? "cancelled" : background.status,
165165
path: recovery.path,

‎packages/core/src/tool/plugin/image-generate.ts‎

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { SessionMessage } from "../../session/message.js"
2323
import { SessionSchema } from "../../session/schema.js"
2424
import { collectBoundedResponseBody } from "../http-body.js"
2525

26-
export const name = "image_generate"
26+
export const name = "experimental_image_generate"
2727
export const MAX_INPUT_BYTES = 20 * 1024 * 1024
2828
const DOWNLOAD_TIMEOUT = Duration.seconds(30)
2929
const GENERATE_TIMEOUT = Duration.minutes(30)
@@ -224,7 +224,8 @@ export const Plugin = {
224224
Effect.forkIn(scope, { startImmediately: true }),
225225
)
226226

227-
// Opt-in through experimental.image_generate. Internal plugins are not re-run on config changes, so reload the
227+
// On by default. `experimental.image_generate: false` is still honored for configs from when the tool was opt-in;
228+
// permissions are the supported way to turn it off. Internal plugins are not re-run on config changes, so reload the
228229
// tool transform instead.
229230
const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, ctx.tool.reload())
230231

@@ -247,7 +248,7 @@ export const Plugin = {
247248

248249
yield* ctx.tool
249250
.transform((editor) => {
250-
if (!enabled || !optedIn(loaded.entries)) return
251+
if (!enabled || imageGenerate(loaded.entries) === false) return
251252
editor.add({
252253
name,
253254
options: { codemode: false },
@@ -399,11 +400,6 @@ function imageGenerate(entries: readonly Entry[]) {
399400
)?.info.experimental?.image_generate
400401
}
401402

402-
function optedIn(entries: readonly Entry[]) {
403-
const value = imageGenerate(entries)
404-
return value !== undefined && value !== false
405-
}
406-
407403
function configuredModel(entries: readonly Entry[]) {
408404
const value = imageGenerate(entries)
409405
return typeof value === "object" ? value.model : undefined

‎packages/core/test/session-execution.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -549,15 +549,15 @@ describe("SessionRestart background recovery", () => {
549549
const recovery = (path: string) => ({ kind: "image" as const, sessionID, path, model: "test/painter" })
550550
yield* jobs.start({
551551
id: "image_done",
552-
type: "image_generate",
552+
type: "experimental_image_generate",
553553
recovery: recovery("/tmp/done.png"),
554554
run: Effect.succeed("Image request finished. Saved image: /tmp/done.png (test/painter)."),
555555
})
556556
yield* jobs.background("image_done")
557557
yield* jobs.wait({ id: "image_done" })
558558
yield* jobs.start({
559559
id: "image_running",
560-
type: "image_generate",
560+
type: "experimental_image_generate",
561561
recovery: recovery("/tmp/running.png"),
562562
run: Effect.never,
563563
})

‎packages/core/test/tool-image-generate.test.ts‎

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,6 @@ const setup = (providerID = "test") =>
9696
const plugins = yield* Plugin.Service
9797
const sessions = yield* Session.Service
9898
const location = yield* Location.Service
99-
const config = yield* Config.Test
100-
const entries = yield* config.entries()
101-
if (!entries.some((entry) => entry.type === "document" && entry.info.experimental?.image_generate !== undefined))
102-
yield* configure(true)
10399
const host = yield* PluginHost.make(plugins)
104100
yield* ImageGenerateTool.Plugin.effect(host)
105101
yield* OpenCodeTools.Plugin.effect(host)
@@ -167,7 +163,7 @@ const text = (result: { readonly content?: ReadonlyArray<Tool.Content> }) =>
167163
result.content?.[0]?.type === "text" ? result.content[0].text : undefined
168164
const failure = (result: { readonly error?: { readonly message: string } }) => result.error?.message
169165

170-
describe("image_generate", () => {
166+
describe("experimental_image_generate", () => {
171167
it.live("is registered only while an image model is available", () =>
172168
Effect.gen(function* () {
173169
yield* setup()
@@ -180,20 +176,20 @@ describe("image_generate", () => {
180176
}),
181177
)
182178

183-
it.live("is offered only when experimental.image_generate is enabled", () =>
179+
it.live("is on by default and off with experimental.image_generate: false", () =>
184180
Effect.gen(function* () {
185181
yield* imageModel("test", "painter")
186-
yield* configure(false)
187182
yield* setup()
188-
expect(yield* registered).toBe(false)
183+
expect(yield* until(true)).toBe(true)
184+
yield* configure(false)
185+
expect(yield* until(false)).toBe(false)
186+
// Older configs that set true or a model keep working and keep the tool on.
189187
yield* configure(true)
190188
expect(yield* until(true)).toBe(true)
191189
yield* configure(false)
192190
expect(yield* until(false)).toBe(false)
193191
yield* configure({})
194192
expect(yield* until(true)).toBe(true)
195-
yield* configure(undefined)
196-
expect(yield* until(false)).toBe(false)
197193
}),
198194
)
199195

@@ -266,7 +262,7 @@ describe("image_generate", () => {
266262
},
267263
])
268264
expect(fixture.assertions.map((item) => [item.action, item.resources])).toEqual([
269-
["image_generate", ["test/painter"]],
265+
["experimental_image_generate", ["test/painter"]],
270266
["edit", ["out.png"]],
271267
["read", ["in.png"]],
272268
])
@@ -360,7 +356,7 @@ describe("image_generate", () => {
360356
fixture.deny = "edit"
361357
const result = yield* run({ prompt: "x", path: "out.png" })
362358
expect(result.status).toBe("error")
363-
expect(fixture.assertions.map((item) => item.action)).toEqual(["image_generate", "edit"])
359+
expect(fixture.assertions.map((item) => item.action)).toEqual(["experimental_image_generate", "edit"])
364360
expect(fixture.requests).toEqual([])
365361
}),
366362
)
@@ -454,7 +450,7 @@ describe("image_generate", () => {
454450
const result = yield* run({ prompt: "x", images: ["https://example.invalid/cat.png"] })
455451
expect(result.status).toBe("error")
456452
expect(fixture.assertions.map((item) => [item.action, item.resources])).toEqual([
457-
["image_generate", ["test/painter"]],
453+
["experimental_image_generate", ["test/painter"]],
458454
["webfetch", ["https://example.invalid/cat.png"]],
459455
])
460456
expect(fixture.requests).toEqual([])

‎packages/schema/src/config/experimental.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ export class Info extends Schema.Class<Info>("ConfigExperimental.Info")({
2525
Schema.Struct({
2626
model: ConfigModel.Selection.pipe(optional).annotate({
2727
description:
28-
"Default model for the image_generate tool when the agent doesn't choose one, as providerID/modelID. Falls back to automatic selection when it isn't available.",
28+
"Default model for the experimental_image_generate tool when the agent doesn't choose one, as providerID/modelID. Falls back to automatic selection when it isn't available.",
2929
}),
3030
}),
3131
])
3232
.pipe(optional)
3333
.annotate({
3434
description:
35-
"Enable the experimental image_generate tool, offered while a connected provider has an image model. Set to true, or to an object with an optional default model. Defaults to false.",
35+
"Settings for the experimental_image_generate tool, which is offered while a connected provider has an image model.",
3636
}),
3737
schedules: Schema.Boolean.pipe(optional).annotate({
3838
description: "Enable experimental scheduled tasks that wake a session with a prompt. Defaults to false.",

‎services/www/src/docs/content/config.mdx‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ All experimental settings live under the `experimental` key:
627627
"subagent_depth": 2,
628628
"portable_shell_scanner": true,
629629
"background_compaction": true,
630-
"image_generate": true,
630+
"image_generate": { "model": "openai/gpt-image-2" },
631631
"location_idle_timeout": "4 hours",
632632
},
633633
}
@@ -663,11 +663,9 @@ automatic compaction doesn't pause the session. Defaults to `false`. See
663663

664664
### Image generation
665665

666-
Set `image_generate` to `true` to give agents the [`image_generate`](/tools#image-generate) tool for creating and
667-
editing images. It is offered only while a connected provider has an image model. Defaults to `false`.
668-
669-
To set a default model, use an object with `model` as `providerID/modelID`, optionally with `#variant`. When that model
670-
isn't available or can't handle a request, OpenCode picks another image model automatically.
666+
`image_generate` sets the default model for the [`experimental_image_generate`](/tools#image-generate) tool, as
667+
`providerID/modelID`, optionally with `#variant`. When that model isn't available or can't handle a request, OpenCode
668+
picks another image model automatically.
671669

672670
```jsonc
673671
{

‎services/www/src/docs/content/permissions.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ currently use these actions and resources:
118118
| `question` | `*` |
119119
| `webfetch` | Requested URL |
120120
| `websearch` | Search query |
121-
| `image_generate` | Selected image model as `providerID/modelID`, plus `#variant` when set |
121+
| `experimental_image_generate` | Selected image model as `providerID/modelID`, plus `#variant` when set |
122122
| `external_directory` | Canonical external directory boundary, normally ending in `/*` |
123123
| `<server>_<tool>` | `*` for an MCP tool; unsupported characters in both names become `_` |
124124
| `opencode_list_mcp_resources` | MCP server name |

‎services/www/src/docs/content/tools.mdx‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,10 @@ for providers, selection, rate limits, and disabling the tool.
161161

162162
### Image generate
163163

164-
`image_generate` creates a new image from a prompt, or edits existing ones. It
165-
is experimental and off by default; turn it on with
166-
[`experimental.image_generate`](/config#image-generation). It appears only
167-
while a connected provider has an image model.
164+
`experimental_image_generate` creates a new image from a prompt, or edits
165+
existing ones. It is experimental and appears while a connected provider has an
166+
image model. To choose its default model, see
167+
[`experimental.image_generate`](/config#image-generation).
168168

169169
```text
170170
Generate a flat app icon of a paper plane on a blue background and save it to assets/icon.png.
@@ -174,7 +174,7 @@ Give it a `prompt`, and optionally `images` to edit or use as references, a
174174
specific `model`, or a `path` to save to. It runs in the background and
175175
notifies the session with the saved path when the image is ready.
176176

177-
Permission: `image_generate` with the selected model as its resource.
177+
Permission: `experimental_image_generate` with the selected model as its resource.
178178

179179
## Interaction
180180

0 commit comments

Comments
 (0)