Skip to content

Commit 27a84f3

Browse files
committed
feat(core): stronger background wording for image_generate, chat-only subagent models
1 parent 0f305e9 commit 27a84f3

4 files changed

Lines changed: 36 additions & 6 deletions

File tree

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const EXTENSIONS: Record<string, string> = {
3535
const PREFERRED_FAMILIES = ["gpt-image", "gemini-flash", "gemini-pro", "grok", "flux"]
3636

3737
export const description =
38-
"Generate or edit an image. Pass images to edit them or to guide style and subject. To make several images, call the tool once per image with distinct prompts. Omit model to use the default. Runs in the background: the call returns right away and you are notified when the image is saved; then use the read tool on its path to view it, and mention the path to the user. Don't poll or wait for it. Each call costs money, so don't regenerate unless asked or the result is clearly wrong."
38+
"Generate or edit an image. Pass images to edit them or to guide style and subject. To make several images, call the tool once per image with distinct prompts. Omit model to use the default. Runs in the background: the call returns right away and you are notified automatically when the image is saved or fails. DO NOT sleep, poll, or check whether the file exists while it runs; continue with other work or end your response. After the notice, use the read tool on the path to view the image and mention the path to the user. Each call costs money, so don't regenerate unless asked or the result is clearly wrong."
3939

4040
export const Input = Schema.Struct({
4141
prompt: Schema.String.check(Schema.isMinLength(1)).annotate({
@@ -336,7 +336,12 @@ export const Plugin = {
336336
yield* notifyWhenDone(context.sessionID, job.id, requested)
337337
return {
338338
output: { path: requested, model: ref },
339-
content: `Image request started in the background with ${ref}. Output path: ${requested}. You will be notified when it finishes or fails. Do not poll; use the read tool on the path after completion.${resolved.note ?? ""}`,
339+
content: [
340+
`Image request started in the background with ${ref}. It will be saved to: ${requested}${resolved.note ?? ""}`,
341+
"You will be notified automatically when it finishes or fails.",
342+
"DO NOT sleep, poll, check whether the file exists, or request the same image again while it runs.",
343+
"Work on other tasks, or briefly tell the user what you started and end your response.",
344+
].join("\n"),
340345
metadata: { path: requested, model: ref, jobID: job.id },
341346
}
342347
}).pipe(

‎packages/core/src/tool/plugin/subagent.ts‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const Input = Schema.Struct({
3535
prompt: Schema.String.annotate({ description: "The task for the subagent to perform" }),
3636
model: Schema.optionalKey(Schema.String).annotate({
3737
description:
38-
'NEVER set this unless the user explicitly asks for a particular model or variant. The value is written as "providerID/modelID", or "providerID/modelID#variant" to include a variant. Do not guess the ID: look the model up with the models tool, filtering to your own provider first.',
38+
'NEVER set this unless the user explicitly asks for a particular model or variant. It must be a chat model; image and decision models can\'t run subagents. The value is written as "providerID/modelID", or "providerID/modelID#variant" to include a variant. Do not guess the ID: look the model up with the models tool, filtering to your own provider first.',
3939
}),
4040
sessionID: Schema.optionalKey(SessionSchema.ID).annotate({
4141
description:
@@ -83,10 +83,16 @@ export const Plugin = {
8383
const model = (yield* models.available()).find(
8484
(model) => model.providerID === ref.providerID && model.id === ref.id,
8585
)
86-
if (model === undefined)
86+
if (model === undefined) {
87+
const other = (yield* models.available(["image", "decision"])).find(
88+
(model) => model.providerID === ref.providerID && model.id === ref.id,
89+
)
8790
return yield* new ToolFailure({
88-
message: `Model "${ref.providerID}/${ref.id}" is not available. Use the models tool to see what is available.`,
91+
message: other
92+
? `Model "${ref.providerID}/${ref.id}" is ${other.type === "image" ? "an image" : "a decision"} model; subagents need a chat model. Use the models tool to find one.`
93+
: `Model "${ref.providerID}/${ref.id}" is not available. Use the models tool to see what is available.`,
8994
})
95+
}
9096
if (ref.variant !== undefined && !model.variants.some((variant) => variant.id === ref.variant))
9197
return yield* new ToolFailure({
9298
message:

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,12 @@ describe("image_generate", () => {
229229
expect(result.content).toEqual([
230230
{
231231
type: "text",
232-
text: `Image request started in the background with test/painter. Output path: ${file}. You will be notified when it finishes or fails. Do not poll; use the read tool on the path after completion.`,
232+
text: [
233+
`Image request started in the background with test/painter. It will be saved to: ${file}`,
234+
"You will be notified automatically when it finishes or fails.",
235+
"DO NOT sleep, poll, check whether the file exists, or request the same image again while it runs.",
236+
"Work on other tasks, or briefly tell the user what you started and end your response.",
237+
].join("\n"),
233238
},
234239
])
235240
expect(yield* notice(result)).toBe(

‎packages/core/test/tool-subagent.test.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,12 @@ const withSubagent = (location: Location.Ref) =>
184184
model.variants = [{ id: Model.VariantID.make("fast") }]
185185
})
186186
editor.models.update(Provider.ID.make("test"), Model.ID.make("plain"), () => {})
187+
editor.models.update(Provider.ID.make("test"), Model.ID.make("painter"), (model) => {
188+
model.type = "image"
189+
})
190+
editor.models.update(Provider.ID.make("test"), Model.ID.make("classifier"), (model) => {
191+
model.type = "decision"
192+
})
187193
}),
188194
).pipe(Effect.provide(locations.get(location)))
189195
yield* Agent.Service.use((agents) =>
@@ -714,6 +720,14 @@ describe("SubagentTool", () => {
714720
["test/missing", 'Model "test/missing" is not available. Use the models tool to see what is available.'],
715721
["test/override#slow", 'Variant "slow" is not available for "test/override". Available: fast.'],
716722
["test/plain#high", 'Model "test/plain" has no variants. Omit the variant.'],
723+
[
724+
"test/painter",
725+
'Model "test/painter" is an image model; subagents need a chat model. Use the models tool to find one.',
726+
],
727+
[
728+
"test/classifier",
729+
'Model "test/classifier" is a decision model; subagents need a chat model. Use the models tool to find one.',
730+
],
717731
] as const
718732
for (const [model, message] of failures) {
719733
expect(yield* call(`call-${model}`, { model })).toEqual({

0 commit comments

Comments
 (0)