ima2-gen 3.16.1 → 3.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +29 -0
  2. package/bin/commands/edit.js +6 -2
  3. package/bin/commands/gen.js +7 -2
  4. package/bin/commands/multimode.js +6 -2
  5. package/bin/commands/node.js +5 -0
  6. package/bin/lib/error-hints.js +1 -1
  7. package/bin/lib/model-aliases.js +1 -0
  8. package/docs/API.md +13 -0
  9. package/docs/CLI.md +14 -0
  10. package/docs/FAQ.ko.md +1 -0
  11. package/docs/FAQ.md +1 -0
  12. package/docs/FAQ.zh-CN.md +1 -0
  13. package/docs/FAQ.zh-TW.md +1 -0
  14. package/docs/README.ja.md +1 -0
  15. package/docs/README.ko.md +1 -0
  16. package/docs/README.zh-CN.md +1 -0
  17. package/docs/README.zh-TW.md +1 -0
  18. package/docs/migration/runtime-test-inventory.md +10 -1
  19. package/integrations/comfyui/ima2_gen_bridge/nodes.py +1 -1
  20. package/lib/capabilities.js +2 -1
  21. package/lib/generatePipeline.js +9 -3
  22. package/lib/historyList.js +1 -0
  23. package/lib/imageMetadata.js +1 -0
  24. package/lib/imageModels.js +18 -1
  25. package/lib/multimodePipeline.js +11 -3
  26. package/lib/nodeGeneration.js +9 -3
  27. package/lib/oauthNormalize.js +22 -3
  28. package/lib/promptBuilder/constants.js +1 -1
  29. package/lib/providerOptions.js +11 -3
  30. package/lib/providers/adapters/openaiExecution.js +7 -7
  31. package/lib/providers/adapters/openaiOperations.js +22 -3
  32. package/lib/providers/registry.js +2 -0
  33. package/package.json +3 -2
  34. package/routes/edit.js +9 -3
  35. package/ui/dist/.vite/manifest.json +61 -61
  36. package/ui/dist/assets/AgentWorkspace-DdQ7EVuV.js +3 -0
  37. package/ui/dist/assets/App-lx7RIWUB.js +9 -0
  38. package/ui/dist/assets/{AssetGenWorkspace-C8eIs-bS.js → AssetGenWorkspace-9cFuwMY0.js} +2 -2
  39. package/ui/dist/assets/{AssetsWorkspace-BcbAM8uC.js → AssetsWorkspace--OGFfh8y.js} +1 -1
  40. package/ui/dist/assets/{CardNewsWorkspace-CM5hwbRV.js → CardNewsWorkspace-CDLRdair.js} +1 -1
  41. package/ui/dist/assets/{GenerationRequestLogPanel-CTs8OymS.js → GenerationRequestLogPanel-BmhuuHj-.js} +1 -1
  42. package/ui/dist/assets/{HomeWorkspace-KGUtIZyG.js → HomeWorkspace-Dzkva2Mr.js} +1 -1
  43. package/ui/dist/assets/{KeyingPanel-D8ZHmWcC.js → KeyingPanel-YsPilwZ1.js} +1 -1
  44. package/ui/dist/assets/{NodeCanvas-D2N3m-1s.js → NodeCanvas-BqkCtDtr.js} +1 -1
  45. package/ui/dist/assets/{PromptBuilderPanel-BolZnOa7.js → PromptBuilderPanel-d_g5JLDo.js} +1 -1
  46. package/ui/dist/assets/{PromptImportDialog-DBpe7Vkj.js → PromptImportDialog-CqG-vRqd.js} +2 -2
  47. package/ui/dist/assets/{PromptImportDiscoverySection-Dk6v6lUZ.js → PromptImportDiscoverySection-DdkPKdK7.js} +1 -1
  48. package/ui/dist/assets/{PromptImportFolderSection-BE7Nfq9Y.js → PromptImportFolderSection-Bi3Uve7i.js} +1 -1
  49. package/ui/dist/assets/{PromptLibraryPanel-Qdujpb4L.js → PromptLibraryPanel-ByIAu1g9.js} +2 -2
  50. package/ui/dist/assets/{SettingsWorkspace-Dm4wezu_.js → SettingsWorkspace-_ND9Fgfz.js} +1 -1
  51. package/ui/dist/assets/{SpriteRecipeWorkspace-D9do9V7F.js → SpriteRecipeWorkspace-B8sLq0Nn.js} +1 -1
  52. package/ui/dist/assets/index-BL_H_Z8O.css +1 -0
  53. package/ui/dist/assets/{index-CfGRmfFv.js → index-Bl__p_6_.js} +7 -7
  54. package/ui/dist/assets/index-DZNGW1wK.js +5 -0
  55. package/ui/dist/assets/{pptxgen.es-BLI_YKez.js → pptxgen.es-DZuurLP8.js} +1 -1
  56. package/ui/dist/assets/{promptBuilderStore-BMiudG9n.js → promptBuilderStore-2KCBiZVN.js} +1 -1
  57. package/ui/dist/assets/useAgentDialogFocus-BPzq1JAp.js +1 -0
  58. package/ui/dist/index.html +2 -2
  59. package/ui/dist/assets/AgentWorkspace-DlLaTsPK.js +0 -3
  60. package/ui/dist/assets/App-BfpvLzSs.js +0 -9
  61. package/ui/dist/assets/index-CfrOcJIe.js +0 -5
  62. package/ui/dist/assets/index-D0NhOovz.css +0 -1
  63. package/ui/dist/assets/useAgentDialogFocus-CCj5EYE9.js +0 -1
@@ -41,9 +41,9 @@ export async function runNodeGeneration(req, res, ctx) {
41
41
  const cancelController = new AbortController();
42
42
  const referencePayload = summarizeReferencePayload(body.references);
43
43
  try {
44
- const { prompt: rawPrompt, quality: rawQuality = "medium", size = "1024x1024", format = "png", moderation = "low", references = [], externalSrc = null, mode: promptMode = "auto", contextMode: rawContextMode = "parent-plus-refs", searchMode: rawSearchMode = "on", model: rawModel, reasoningEffort: rawReasoningEffort, } = body;
44
+ const { prompt: rawPrompt, quality: rawQuality = "medium", size = "1024x1024", format = "png", moderation = "low", references = [], externalSrc = null, mode: promptMode = "auto", contextMode: rawContextMode = "parent-plus-refs", searchMode: rawSearchMode = "on", model: rawModel, imageToolModel: rawImageToolModel, reasoningEffort: rawReasoningEffort, } = body;
45
45
  const { provider = "oauth" } = body;
46
- const { quality, warnings: qualityWarnings } = normalizeOAuthParams({ provider, quality: rawQuality });
46
+ const { quality, warnings: qualityWarnings } = normalizeOAuthParams({ provider, quality: rawQuality, imageToolModel: rawImageToolModel });
47
47
  const normalizedPromptMode = promptMode === "direct" ? "direct" : "auto";
48
48
  const contextMode = rawContextMode === "parent-only" ? "parent-only"
49
49
  : rawContextMode === "ancestry" ? "ancestry" : "parent-plus-refs";
@@ -65,6 +65,7 @@ export async function runNodeGeneration(req, res, ctx) {
65
65
  const providerOptions = resolveProviderOptions(ctx, {
66
66
  provider,
67
67
  rawModel,
68
+ rawImageToolModel,
68
69
  rawReasoningEffort,
69
70
  rawSize: size,
70
71
  rawWebSearchEnabled: body.webSearchEnabled,
@@ -80,6 +81,7 @@ export async function runNodeGeneration(req, res, ctx) {
80
81
  });
81
82
  }
82
83
  const imageModel = providerOptions.model;
84
+ const imageToolModel = providerOptions.imageToolModel;
83
85
  const reasoningEffort = providerOptions.reasoningEffort;
84
86
  const effectiveSize = providerOptions.size;
85
87
  const webSearchEnabled = providerOptions.webSearchEnabled;
@@ -233,6 +235,7 @@ export async function runNodeGeneration(req, res, ctx) {
233
235
  clientNodeId,
234
236
  quality,
235
237
  model: effectiveImageModel,
238
+ ...(imageToolModel ? { imageToolModel } : {}),
236
239
  size: effectiveSize,
237
240
  moderation,
238
241
  refs: refsForRequest.length,
@@ -267,7 +270,7 @@ export async function runNodeGeneration(req, res, ctx) {
267
270
  signal: cancelController.signal, prompt: generationPrompt, rawPrompt: prompt,
268
271
  references: refCheck.refDetails, sourceImage: parentB64, contextMode, searchMode,
269
272
  partialImages: emitProgress ? 2 : 0,
270
- options: { model: effectiveImageModel, quality, size: effectiveSize, moderation,
273
+ options: { model: effectiveImageModel, imageToolModel, quality, size: effectiveSize, moderation,
271
274
  mode: normalizedPromptMode, reasoningEffort, webSearchEnabled },
272
275
  nai: activeProvider === "nai" ? readNaiOptions(req.body) : {},
273
276
  }, emitProgress ? {
@@ -293,6 +296,7 @@ export async function runNodeGeneration(req, res, ctx) {
293
296
  parentNodeId,
294
297
  clientNodeId,
295
298
  model: effectiveImageModel,
299
+ ...(imageToolModel ? { imageToolModel } : {}),
296
300
  moderation,
297
301
  quality,
298
302
  size: effectiveSize,
@@ -374,6 +378,7 @@ export async function runNodeGeneration(req, res, ctx) {
374
378
  promptMode: normalizedPromptMode,
375
379
  options: { quality, size: effectiveSize, format: resultFormat, moderation },
376
380
  model: effectiveImageModel,
381
+ ...(imageToolModel ? { imageToolModel } : {}),
377
382
  reasoningEffort,
378
383
  createdAt: Date.now(),
379
384
  createdAtIso: new Date().toISOString(),
@@ -424,6 +429,7 @@ export async function runNodeGeneration(req, res, ctx) {
424
429
  webSearchEnabled,
425
430
  provider: activeProvider,
426
431
  model: effectiveImageModel,
432
+ ...(imageToolModel ? { imageToolModel } : {}),
427
433
  reasoningEffort,
428
434
  size: effectiveSize,
429
435
  format: resultFormat,
@@ -1,15 +1,34 @@
1
1
  // 0.09.12.2 — Keep supported image tool qualities intact for OAuth.
2
- // The exposed app contract accepts low/medium/high; normalize only malformed input so
3
- // the API response, UI metadata, and actual image_generation payload stay aligned.
2
+ // OAuth keeps low/medium/high; only explicitly selected API 2.5 tools add qualities.
3
+ // Keep the API response, UI metadata, and image_generation payload aligned.
4
4
  export const DEFAULT_IMAGE_QUALITY = "medium";
5
5
  export const VALID_IMAGE_QUALITIES = new Set(["low", "medium", "high"]);
6
+ export const API_IMAGE_TOOL_MODELS = ["gpt-image-2.5-sunburst", "gpt-image-2.5-flare"];
7
+ export const API_IMAGE_25_QUALITIES = new Set([...VALID_IMAGE_QUALITIES, "xhigh", "max"]);
8
+ export function isApiImageToolModel(value) {
9
+ return typeof value === "string" && API_IMAGE_TOOL_MODELS.some((model) => model === value);
10
+ }
11
+ export function normalizeImageToolModel(provider, value) {
12
+ if (provider !== "api" || value === undefined || value === null || value === "") {
13
+ return { imageToolModel: undefined };
14
+ }
15
+ if (isApiImageToolModel(value))
16
+ return { imageToolModel: value };
17
+ return {
18
+ error: `imageToolModel must be one of: ${API_IMAGE_TOOL_MODELS.join(", ")}`,
19
+ code: "INVALID_IMAGE_TOOL_MODEL",
20
+ status: 400,
21
+ };
22
+ }
6
23
  /**
7
24
  * @param {{ provider?: string, quality?: string }} input
8
25
  * @returns {{ quality: string, warnings: Array<{code:string,field:string,normalizedTo:string,reason:string}> }}
9
26
  */
10
27
  export function normalizeOAuthParams(input) {
11
28
  const requested = typeof input?.quality === "string" ? input.quality : DEFAULT_IMAGE_QUALITY;
12
- if (VALID_IMAGE_QUALITIES.has(requested)) {
29
+ const valid = input?.provider === "api" && isApiImageToolModel(input.imageToolModel)
30
+ ? API_IMAGE_25_QUALITIES : VALID_IMAGE_QUALITIES;
31
+ if (valid.has(requested)) {
13
32
  return { quality: requested, warnings: [] };
14
33
  }
15
34
  return {
@@ -1,5 +1,5 @@
1
1
  export const PROMPT_BUILDER_BACKENDS = ["auto", "oauth", "grok", "api", "grok-api"];
2
- const GPT_MODELS = ["gpt-5.6-luna", "gpt-5.6-terra", "gpt-5.6-sol", "gpt-5.5", "gpt-5.4", "gpt-5.4-mini"];
2
+ const GPT_MODELS = ["gpt-5.6-luna", "gpt-6-astra", "gpt-5.6-terra", "gpt-5.6-sol", "gpt-5.5", "gpt-5.4", "gpt-5.4-mini"];
3
3
  const grokModels = ["grok-4.3", "grok-4.6", "grok-4.5"];
4
4
  export const PROMPT_BUILDER_MODELS = {
5
5
  auto: ["auto"],
@@ -1,6 +1,7 @@
1
+ import { normalizeImageToolModel } from "./oauthNormalize.js";
1
2
  import { ATLASCLOUD_TEXT_TO_IMAGE_MODEL } from "./atlasCloudImageAdapter.js";
2
- import { FALLBACK_IMAGE_MODEL, normalizeImageModel, normalizeReasoningEffort, normalizeGrokImageModel, normalizeGeminiApiModel, normalizeMinimaxImageModel, normalizeNaiImageModel, normalizeComfyWorkflowModel } from "./imageModels.js";
3
- export function resolveProviderOptions(ctx, { provider = "oauth", rawModel, rawReasoningEffort, rawSize = "1024x1024", rawWebSearchEnabled = true, searchMode = "on", } = {}) {
3
+ import { FALLBACK_IMAGE_MODEL, coerceReasoningEffortForModel, normalizeImageModel, normalizeReasoningEffort, normalizeGrokImageModel, normalizeGeminiApiModel, normalizeMinimaxImageModel, normalizeNaiImageModel, normalizeComfyWorkflowModel } from "./imageModels.js";
4
+ export function resolveProviderOptions(ctx, { provider = "oauth", rawModel, rawImageToolModel, rawReasoningEffort, rawSize = "1024x1024", rawWebSearchEnabled = true, searchMode = "on", } = {}) {
4
5
  if (provider === "agy") {
5
6
  return {
6
7
  provider: "agy",
@@ -117,6 +118,9 @@ export function resolveProviderOptions(ctx, { provider = "oauth", rawModel, rawR
117
118
  };
118
119
  }
119
120
  const activeProvider = provider === "api" ? "api" : "oauth";
121
+ const toolModelCheck = normalizeImageToolModel(activeProvider, rawImageToolModel);
122
+ if (toolModelCheck.error)
123
+ return { error: toolModelCheck.error, code: toolModelCheck.code, status: toolModelCheck.status };
120
124
  const apiConfig = ctx?.config?.apiProvider || {};
121
125
  const modelInput = activeProvider === "api"
122
126
  ? (rawModel || apiConfig.defaultImageModel || FALLBACK_IMAGE_MODEL)
@@ -131,6 +135,9 @@ export function resolveProviderOptions(ctx, { provider = "oauth", rawModel, rawR
131
135
  if (reasoningCheck.error) {
132
136
  return { error: reasoningCheck.error, code: reasoningCheck.code, status: reasoningCheck.status };
133
137
  }
138
+ // Applied after validation so an unsupported model/effort pairing cannot reach
139
+ // the upstream request. See coerceReasoningEffortForModel.
140
+ const effort = coerceReasoningEffortForModel(modelCheck.model, reasoningCheck.effort);
134
141
  const size = activeProvider === "api" && (typeof rawSize !== "string" || rawSize.length === 0)
135
142
  ? (apiConfig.defaultSize || "1024x1024")
136
143
  : rawSize;
@@ -139,8 +146,9 @@ export function resolveProviderOptions(ctx, { provider = "oauth", rawModel, rawR
139
146
  : rawWebSearchEnabled !== false && searchMode !== "off";
140
147
  return {
141
148
  provider: activeProvider,
149
+ imageToolModel: toolModelCheck.imageToolModel,
142
150
  model: modelCheck.model,
143
- reasoningEffort: reasoningCheck.effort,
151
+ reasoningEffort: effort,
144
152
  size,
145
153
  webSearchEnabled,
146
154
  };
@@ -7,7 +7,7 @@ export function isOpenaiRequest(request) {
7
7
  }
8
8
  function prepareOpenaiClassic(ctx, request, _progress) {
9
9
  const { provider: activeProvider, prompt: generationPrompt, requestId, background: backgroundParams } = request;
10
- const { model: imageModel, quality, size: effectiveSize, moderation, mode: normalizedPromptMode, reasoningEffort, webSearchEnabled } = request.options;
10
+ const { model: imageModel, imageToolModel, quality, size: effectiveSize, moderation, mode: normalizedPromptMode, reasoningEffort, webSearchEnabled } = request.options;
11
11
  // Scalars are captured at prepare; references, signal and ctx stay live per attempt.
12
12
  const generateOne = async () => {
13
13
  const MAX_RETRIES = 1;
@@ -15,7 +15,7 @@ function prepareOpenaiClassic(ctx, request, _progress) {
15
15
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
16
16
  try {
17
17
  const r = await generateViaResponses(activeProvider, generationPrompt, quality, effectiveSize, moderation, request.references, requestId, normalizedPromptMode, ctx, {
18
- model: imageModel,
18
+ model: imageModel, imageToolModel,
19
19
  reasoningEffort,
20
20
  webSearchEnabled,
21
21
  signal: request.signal,
@@ -50,21 +50,21 @@ function prepareOpenaiClassic(ctx, request, _progress) {
50
50
  }
51
51
  async function executeOpenaiNode(ctx, request, progress) {
52
52
  const { provider, sourceImage: parentB64, prompt: generationPrompt, references, requestId, signal, searchMode, options } = request;
53
- const { model, size, quality, moderation, mode, reasoningEffort, webSearchEnabled } = options;
53
+ const { model, imageToolModel, size, quality, moderation, mode, reasoningEffort, webSearchEnabled } = options;
54
54
  const refsForRequest = request.contextMode === "parent-only" ? [] : references;
55
55
  return parentB64
56
56
  ? await editViaResponses(provider, generationPrompt, parentB64, quality, size, moderation, mode, ctx, requestId, {
57
- model, references: refsForRequest, searchMode, reasoningEffort, webSearchEnabled, signal,
57
+ model, imageToolModel, references: refsForRequest, searchMode, reasoningEffort, webSearchEnabled, signal,
58
58
  })
59
59
  : await generateViaResponses(provider, generationPrompt, quality, size, moderation, refsForRequest, requestId, mode, ctx, {
60
- model, reasoningEffort, webSearchEnabled, signal,
60
+ model, imageToolModel, reasoningEffort, webSearchEnabled, signal,
61
61
  partialImages: request.partialImages,
62
62
  onPartialImage: progress?.onPartialImage ?? null,
63
63
  });
64
64
  }
65
65
  async function executeOpenaiEdit(ctx, request) {
66
66
  const { provider, rawPrompt, sourceImage, signal, requestId, options } = request;
67
- const result = await editViaResponses(provider, rawPrompt, sourceImage, options.quality, options.size, options.moderation, options.mode, ctx, requestId, { model: options.model, reasoningEffort: options.reasoningEffort,
67
+ const result = await editViaResponses(provider, rawPrompt, sourceImage, options.quality, options.size, options.moderation, options.mode, ctx, requestId, { model: options.model, imageToolModel: options.imageToolModel, reasoningEffort: options.reasoningEffort,
68
68
  webSearchEnabled: options.webSearchEnabled,
69
69
  ...(request.mask !== null ? { mask: request.mask } : {}), signal });
70
70
  // Preserve native retry metadata; only Responses had these caller defaults.
@@ -72,7 +72,7 @@ async function executeOpenaiEdit(ctx, request) {
72
72
  }
73
73
  async function executeOpenaiMultimode(ctx, request, progress) {
74
74
  const { provider, prompt, references, signal, requestId, options, maxImages } = request;
75
- return generateMultimodeViaResponses(provider, prompt, options.quality, options.size, options.moderation, references, requestId, options.mode, ctx, { model: options.model, maxImages, reasoningEffort: options.reasoningEffort,
75
+ return generateMultimodeViaResponses(provider, prompt, options.quality, options.size, options.moderation, references, requestId, options.mode, ctx, { model: options.model, imageToolModel: options.imageToolModel, maxImages, reasoningEffort: options.reasoningEffort,
76
76
  webSearchEnabled: options.webSearchEnabled,
77
77
  ...(progress.onPartialImage !== undefined ? { onPartialImage: progress.onPartialImage } : {}),
78
78
  ...(progress.onFinalImage !== undefined ? { onFinalImage: progress.onFinalImage } : {}), signal });
@@ -5,6 +5,7 @@ import { imageToolChoice, imageToolChoiceKind, tools, toolTypes, } from "../../r
5
5
  import { emptyResponseError } from "../../responsesErrors.js";
6
6
  import { retryPromptOnlyJsonImage } from "../../responsesFallback.js";
7
7
  import { AUTO_PROMPT_FIDELITY_SUFFIX, DIRECT_PROMPT_FIDELITY_SUFFIX, EDIT_DEVELOPER_PROMPT, EDIT_NO_SEARCH_DEVELOPER_PROMPT, GENERATE_DEVELOPER_PROMPT, GENERATE_NO_SEARCH_DEVELOPER_PROMPT, MULTIMODE_DEVELOPER_PROMPT, MULTIMODE_NO_SEARCH_DEVELOPER_PROMPT, buildEditTextPrompt, buildMultimodeSequencePrompt, buildUserTextPrompt, } from "../../oauthProxy.js";
8
+ import { normalizeOAuthParams, normalizeImageToolModel } from "../../oauthNormalize.js";
8
9
  import { postResponses } from "../../responsesTransport.js";
9
10
  function normalizeRef(ref) {
10
11
  const b64 = typeof ref === "string" ? ref : ref?.b64;
@@ -23,7 +24,13 @@ export async function generateViaResponses(provider, prompt, quality, size, mode
23
24
  const ctx = requireRuntimeContext(ctxRaw);
24
25
  const model = options.model || ctx.config?.imageModels?.default || "gpt-5.6-luna";
25
26
  const webSearchEnabled = options.webSearchEnabled !== false && options.searchMode !== "off";
26
- const requestTools = tools(webSearchEnabled, {
27
+ const toolModel = normalizeImageToolModel(provider ?? "oauth", options.imageToolModel);
28
+ if (toolModel.error)
29
+ throw Object.assign(new Error(toolModel.error), { code: toolModel.code, status: toolModel.status });
30
+ if (quality === "xhigh" || quality === "max") {
31
+ quality = normalizeOAuthParams({ provider, quality, imageToolModel: toolModel.imageToolModel }).quality;
32
+ }
33
+ const requestTools = tools(webSearchEnabled, { ...(toolModel.imageToolModel ? { model: toolModel.imageToolModel } : {}),
27
34
  quality,
28
35
  size,
29
36
  moderation,
@@ -105,7 +112,13 @@ export async function generateMultimodeViaResponses(provider, prompt, quality, s
105
112
  const maxImages = Math.min(maxGeneratedImages, Math.max(1, Math.trunc(Number(options.maxImages) || 1)));
106
113
  const model = options.model || ctx.config?.imageModels?.default || "gpt-5.6-luna";
107
114
  const webSearchEnabled = options.webSearchEnabled !== false && options.searchMode !== "off";
108
- const requestTools = tools(webSearchEnabled, { quality, size, moderation, ...(options.partialImages ? { partial_images: options.partialImages } : {}) });
115
+ const toolModel = normalizeImageToolModel(provider ?? "oauth", options.imageToolModel);
116
+ if (toolModel.error)
117
+ throw Object.assign(new Error(toolModel.error), { code: toolModel.code, status: toolModel.status });
118
+ if (quality === "xhigh" || quality === "max") {
119
+ quality = normalizeOAuthParams({ provider, quality, imageToolModel: toolModel.imageToolModel }).quality;
120
+ }
121
+ const requestTools = tools(webSearchEnabled, { ...(toolModel.imageToolModel ? { model: toolModel.imageToolModel } : {}), quality, size, moderation, ...(options.partialImages ? { partial_images: options.partialImages } : {}) });
109
122
  const userText = buildMultimodeSequencePrompt(mode === "direct"
110
123
  ? `${prompt}${DIRECT_PROMPT_FIDELITY_SUFFIX}`
111
124
  : `${prompt}${webSearchEnabled ? "" : ""}${AUTO_PROMPT_FIDELITY_SUFFIX}`, maxImages, { webSearchEnabled, size });
@@ -139,7 +152,13 @@ export async function editViaResponses(provider, prompt, imageB64, quality, size
139
152
  const ctx = requireRuntimeContext(ctxRaw);
140
153
  const model = options.model || ctx.config?.imageModels?.default || "gpt-5.6-luna";
141
154
  const webSearchEnabled = options.webSearchEnabled !== false && options.searchMode !== "off";
142
- const requestTools = tools(webSearchEnabled, { quality, size, moderation });
155
+ const toolModel = normalizeImageToolModel(provider ?? "oauth", options.imageToolModel);
156
+ if (toolModel.error)
157
+ throw Object.assign(new Error(toolModel.error), { code: toolModel.code, status: toolModel.status });
158
+ if (quality === "xhigh" || quality === "max") {
159
+ quality = normalizeOAuthParams({ provider, quality, imageToolModel: toolModel.imageToolModel }).quality;
160
+ }
161
+ const requestTools = tools(webSearchEnabled, { ...(toolModel.imageToolModel ? { model: toolModel.imageToolModel } : {}), quality, size, moderation });
143
162
  const toolChoice = imageToolChoice(options.forceImageToolChoice ?? ctx.config?.oauth?.forceImageToolChoice !== false);
144
163
  const toolChoiceKind = imageToolChoiceKind(toolChoice);
145
164
  const imageForRequest = await compressReferenceB64ForOAuth(imageB64, {
@@ -26,6 +26,7 @@ export const REGISTRY = [
26
26
  { id: "gpt-5.6-sol", aliases: ["sol"], kind: "image", supports: RESPONSES },
27
27
  { id: "gpt-5.6-terra", aliases: ["terra"], kind: "image", supports: RESPONSES },
28
28
  { id: "gpt-5.6-luna", aliases: ["luna"], kind: "image", supports: RESPONSES },
29
+ { id: "gpt-6-astra", aliases: ["astra"], kind: "image", supports: RESPONSES },
29
30
  { id: "gpt-5.3-codex-spark", aliases: ["spark"], kind: "image", supports: UNSUPPORTED },
30
31
  ],
31
32
  referenceLimits: {},
@@ -52,6 +53,7 @@ export const REGISTRY = [
52
53
  { id: "gpt-5.6-sol", aliases: ["sol"], kind: "image", supports: RESPONSES },
53
54
  { id: "gpt-5.6-terra", aliases: ["terra"], kind: "image", supports: RESPONSES },
54
55
  { id: "gpt-5.6-luna", aliases: ["luna"], kind: "image", supports: RESPONSES },
56
+ { id: "gpt-6-astra", aliases: ["astra"], kind: "image", supports: RESPONSES },
55
57
  ],
56
58
  referenceLimits: {},
57
59
  elementTaxonomy: "gpt",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ima2-gen",
3
- "version": "3.16.1",
3
+ "version": "3.17.0",
4
4
  "packageManager": "npm@11.18.0",
5
5
  "description": "Local-first visual generation runtime and studio for people and coding agents, with reproducible image and video workflows across multiple providers.",
6
6
  "type": "module",
@@ -95,6 +95,7 @@
95
95
  "@openai/codex": "0.153.4",
96
96
  "better-sqlite3": "^13.0.3",
97
97
  "dotenv": "^17.4.2",
98
+ "electron-updater": "6.8.9",
98
99
  "express": "^5.1.0",
99
100
  "google-auth-library": "^11.0.1",
100
101
  "openai": "^7.4.0",
@@ -125,5 +126,5 @@
125
126
  "typescript": "^5.9.3",
126
127
  "yaml": "2.9.0"
127
128
  },
128
- "gitHead": "5f33e44a7886580330ee03ea58a71a4d842014e5"
129
+ "gitHead": "bfb83da57b1497e9581cf51bfd7213272d79c9e2"
129
130
  }
package/routes/edit.js CHANGED
@@ -90,11 +90,12 @@ export function registerEditRoutes(app, ctxRaw) {
90
90
  let jobOwned = false;
91
91
  const cancelController = new AbortController();
92
92
  try {
93
- const { prompt, image: imageB64, mask: rawMask, quality: rawQuality = "medium", size = "1024x1024", moderation = "low", provider = "oauth", mode: promptMode = "auto", model: rawModel, reasoningEffort: rawReasoningEffort, webSearchEnabled: rawWebSearchEnabled = true, } = req.body;
94
- const { quality, warnings: qualityWarnings } = normalizeOAuthParams({ provider, quality: rawQuality });
93
+ const { prompt, image: imageB64, mask: rawMask, quality: rawQuality = "medium", size = "1024x1024", moderation = "low", provider = "oauth", mode: promptMode = "auto", model: rawModel, imageToolModel: rawImageToolModel, reasoningEffort: rawReasoningEffort, webSearchEnabled: rawWebSearchEnabled = true, } = req.body;
94
+ const { quality, warnings: qualityWarnings } = normalizeOAuthParams({ provider, quality: rawQuality, imageToolModel: rawImageToolModel });
95
95
  const providerOptions = resolveProviderOptions(ctx, {
96
96
  provider,
97
97
  rawModel,
98
+ rawImageToolModel,
98
99
  rawReasoningEffort,
99
100
  rawSize: size,
100
101
  rawWebSearchEnabled,
@@ -106,6 +107,7 @@ export function registerEditRoutes(app, ctxRaw) {
106
107
  return res.status(providerOptions.status).json({ error: providerOptions.error, code: providerOptions.code });
107
108
  }
108
109
  const imageModel = providerOptions.model;
110
+ const imageToolModel = providerOptions.imageToolModel;
109
111
  const reasoningEffort = providerOptions.reasoningEffort;
110
112
  const effectiveSize = providerOptions.size;
111
113
  const webSearchEnabled = providerOptions.webSearchEnabled;
@@ -133,6 +135,7 @@ export function registerEditRoutes(app, ctxRaw) {
133
135
  sessionId,
134
136
  quality,
135
137
  model: imageModel,
138
+ ...(imageToolModel ? { imageToolModel } : {}),
136
139
  size: effectiveSize,
137
140
  },
138
141
  });
@@ -203,6 +206,7 @@ export function registerEditRoutes(app, ctxRaw) {
203
206
  provider: activeProvider,
204
207
  quality,
205
208
  model: imageModel,
209
+ ...(imageToolModel ? { imageToolModel } : {}),
206
210
  size: effectiveSize,
207
211
  moderation,
208
212
  sessionId,
@@ -218,7 +222,7 @@ export function registerEditRoutes(app, ctxRaw) {
218
222
  surface: "edit", provider: activeProvider, requestId,
219
223
  signal: cancelController.signal, prompt, rawPrompt: prompt,
220
224
  sourceImage: imageB64, mask: maskCheck.mask ?? null, references: [],
221
- options: { model: imageModel, quality, size: effectiveSize, moderation,
225
+ options: { model: imageModel, imageToolModel, quality, size: effectiveSize, moderation,
222
226
  mode: normalizedPromptMode, reasoningEffort, webSearchEnabled },
223
227
  })).execute();
224
228
  const resultB64 = r.b64;
@@ -270,6 +274,7 @@ export function registerEditRoutes(app, ctxRaw) {
270
274
  size: effectiveSize,
271
275
  moderation,
272
276
  model: imageModel,
277
+ ...(imageToolModel ? { imageToolModel } : {}),
273
278
  reasoningEffort,
274
279
  elapsed,
275
280
  format: editExt,
@@ -304,6 +309,7 @@ export function registerEditRoutes(app, ctxRaw) {
304
309
  usage,
305
310
  provider: activeProvider,
306
311
  model: activeProvider === "grok" ? resolveGrokQualityModel(imageModel, quality) : imageModel,
312
+ ...(imageToolModel ? { imageToolModel } : {}),
307
313
  moderation,
308
314
  warnings: qualityWarnings,
309
315
  revisedPrompt: revisedPrompt || null,