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
package/README.md CHANGED
@@ -193,6 +193,7 @@ Grok video generation defaults to canonical `grok-imagine-video-1.5`; `grok-imag
193
193
  Image generation defaults to **`gpt-5.6-luna`** on the GPT lane. Prompt Builder backend selection is separate: **Auto** chooses the first ready supported text backend, while Settings can pin one explicitly. The `via <backend>` badge reports the backend that actually answered.
194
194
 
195
195
  - `gpt-5.6-luna` — current default image model on the GPT lane and the default GPT Builder model when that backend is selected.
196
+ - `gpt-6-astra` — newest GPT image model; selectable, not the default.
196
197
  - `gpt-5.6-terra` / `gpt-5.6-sol` — current GPT-5.6 alternatives when your account exposes them.
197
198
  - `gpt-5.5`, `gpt-5.4`, `gpt-5.4-mini` — supported compatibility choices.
198
199
 
@@ -440,6 +441,34 @@ npm run build
440
441
 
441
442
  `npm run dev` builds the UI and starts the TypeScript server entry with `--watch` and verbose server diagnostics. `npm run typecheck`, `npm run build:server`, and `npm run build:cli` verify the TypeScript migration and package emit path. Node mode and Canvas Mode are part of the packaged UI by default.
442
443
 
444
+ ### Desktop app (Electron)
445
+
446
+ `desktop/` wraps the same local server and UI in a menubar/tray app for macOS, Windows, and Linux. It supervises `server.js` as a child process (attaching to an already-running server on the configured port instead of starting a second one) and adds a native settings window: port, open at login, start hidden, menubar-only (macOS), keep-server-on-close, log level, config directory.
447
+
448
+ ```bash
449
+ cd desktop
450
+ npm install
451
+ npm run prepare:app # builds server + CLI + UI at the repo root
452
+ npm start # run unpackaged
453
+ npm run dist:mac # dmg + zip (arm64, x64)
454
+ npm run dist:win # nsis + zip
455
+ npm run dist:linux # AppImage + deb
456
+ ```
457
+
458
+ Installers are produced by `.github/workflows/desktop.yml` on `desktop-v*` tag pushes, desktop PRs, or manual dispatch. PRs produce unsigned macOS previews without Apple credentials. Trusted macOS builds require `MAC_CSC_LINK`, `MAC_CSC_KEY_PASSWORD`, `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, and `APPLE_TEAM_ID`; missing or invalid authentication fails the build instead of producing an unsigned success.
459
+
460
+ For a macOS-only verification build without publishing, run:
461
+
462
+ ```bash
463
+ gh workflow run desktop.yml --ref <reviewed-branch> -f platform=mac -f publish=false
464
+ ```
465
+
466
+ The workflow verifies Developer ID, team, architecture, hardened runtime, secure timestamp, nested signatures, Gatekeeper and stapled notarization tickets for both arm64 and x64. It checks the apps recovered from the final ZIP and DMG against the original signed content, then exports `ima2-macos-signature-proof` reports and installer SHA-256 hashes. Failed verification blocks installer upload. Manual dispatch with `publish=false` never publishes, even when its ref is a desktop tag; publishing a manual build requires `platform=all`.
467
+
468
+ Signing credential imports run on disposable GitHub-hosted macOS. The builder cleans successfully imported keychains; if import/setup fails before its cleanup registration, runner destruction is the final cleanup boundary. Local recovery should reuse an existing login-keychain identity in the same unlocked session, rather than importing credential packages into a persistent machine. This does not require changing automatic locking or key access rules.
469
+
470
+ Windows Authenticode remains separate and uses `WIN_CSC_LINK` / `WIN_CSC_KEY_PASSWORD`; Windows builds without them are unsigned.
471
+
443
472
  ## Contributors
444
473
 
445
474
  - [@lidge-jun](https://github.com/lidge-jun) — maintainer
@@ -16,6 +16,7 @@ const KNOWN_IMAGE_MODELS = deriveCliImageModelSet();
16
16
  const SPEC = {
17
17
  flags: {
18
18
  prompt: { short: "p", type: "string" },
19
+ "image-tool-model": { type: "string" },
19
20
  quality: { short: "q", type: "string", default: "low" },
20
21
  size: { short: "s", type: "string", default: "1024x1024" },
21
22
  out: { short: "o", type: "string" },
@@ -40,12 +41,13 @@ const HELP = `
40
41
 
41
42
  Options:
42
43
  -p, --prompt <text> Edit instruction (required)
43
- -q, --quality <low|medium|high>
44
+ -q, --quality <low|medium|high|xhigh|max>
44
45
  -s, --size <WxH>
45
46
  -o, --out <file>
46
47
  --json
47
48
  --model <${[...KNOWN_IMAGE_MODELS].join("|")}> Default: gpt-5.6-luna
48
- Aliases: luna, sol, terra, spark
49
+ Aliases: luna, astra, sol, terra, spark
50
+ --image-tool-model <id> API only: gpt-image-2.5-sunburst|gpt-image-2.5-flare
49
51
  --provider <${PROVIDER_VALUES.join("|")}>
50
52
  Provider (oauth = GPT OAuth; grok = xAI Grok; agy/gemini-api = Gemini)
51
53
  --mode <auto|direct> Prompt handling mode. Default: auto
@@ -122,6 +124,8 @@ export default async function editCmd(argv) {
122
124
  sessionId: args.session,
123
125
  requestId,
124
126
  };
127
+ if (args["image-tool-model"])
128
+ editBody.imageToolModel = args["image-tool-model"];
125
129
  if (args["reasoning-effort"])
126
130
  editBody.reasoningEffort = args["reasoning-effort"];
127
131
  if (args.provider)
@@ -30,6 +30,7 @@ const PROVIDER_VALUES = [
30
30
  ];
31
31
  const SPEC = {
32
32
  flags: {
33
+ "image-tool-model": { type: "string" },
33
34
  quality: { short: "q", type: "string", default: "low" },
34
35
  size: { short: "s", type: "string", default: "1024x1024" },
35
36
  "no-size-nudge": { type: "boolean" },
@@ -59,7 +60,7 @@ const HELP = `
59
60
  'ima2 cancel <requestId>'. MCP lanes support -n 1 only.
60
61
 
61
62
  Options:
62
- -q, --quality <low|medium|high> Core lanes only. Default: low
63
+ -q, --quality <low|medium|high|xhigh|max> Core lanes only. Default: low
63
64
  -s, --size <WxH | auto> Core lanes only. Default: 1024x1024
64
65
  --no-size-nudge Do not restate --size in the prompt
65
66
  -n, --count <1..${MAX_GENERATION_COUNT}> MCP lanes: 1 only
@@ -73,7 +74,8 @@ const HELP = `
73
74
  --timeout <sec> Default: 180
74
75
  --server <url> Override server URL
75
76
  --model <model|lane/model> Bare IDs must be unique across lanes
76
- Core aliases: luna, sol, terra, spark
77
+ Core aliases: luna, astra, sol, terra, spark
78
+ --image-tool-model <id> API only: gpt-image-2.5-sunburst|gpt-image-2.5-flare
77
79
  --provider <${PROVIDER_VALUES.join("|")}>
78
80
  'auto' was removed; choose a lane explicitly
79
81
  --mode <auto|direct> Core lanes only. Default: auto
@@ -180,6 +182,7 @@ function generatedFilename(value) {
180
182
  }
181
183
  function rejectUnsupportedMcpFlags(argv, args) {
182
184
  const forbidden = [
185
+ ["--image-tool-model", ["--image-tool-model"]],
183
186
  ["--quality", ["--quality", "-q"]], ["--size", ["--size", "-s"]],
184
187
  ["--no-save", ["--no-save"]], ["--force", ["--force"]], ["--stdin", ["--stdin"]],
185
188
  ["--mode", ["--mode"]], ["--moderation", ["--moderation"]], ["--bg", ["--bg"]],
@@ -299,6 +302,8 @@ async function requestCoreImage(args, context, n, requestId) {
299
302
  body.requestId = requestId;
300
303
  if (args.bg)
301
304
  body.backgroundPreset = String(args.bg);
305
+ if (args["image-tool-model"])
306
+ body.imageToolModel = args["image-tool-model"];
302
307
  if (args["reasoning-effort"])
303
308
  body.reasoningEffort = args["reasoning-effort"];
304
309
  if (args["no-web-search"])
@@ -15,6 +15,7 @@ const PROVIDER_VALUES = ["auto", ...deriveProviderIds()];
15
15
  const KNOWN_IMAGE_MODELS = deriveCliImageModelSet();
16
16
  const SPEC = {
17
17
  flags: {
18
+ "image-tool-model": { type: "string" },
18
19
  quality: { short: "q", type: "string", default: "low" },
19
20
  size: { short: "s", type: "string", default: "1024x1024" },
20
21
  "max-images": { type: "string" },
@@ -44,7 +45,7 @@ const HELP = `
44
45
  Stream multi-image generation via SSE (phase / partial / image / done / error).
45
46
 
46
47
  Options:
47
- -q, --quality <low|medium|high> Default: low
48
+ -q, --quality <low|medium|high|xhigh|max> Default: low
48
49
  -s, --size <WxH> Default: 1024x1024
49
50
  -n, --count <1..${MAX_GENERATION_COUNT}> Default: 4
50
51
  --max-images <1..${MAX_GENERATION_COUNT}> Alias for --count
@@ -52,7 +53,8 @@ const HELP = `
52
53
  -d, --out-dir <dir> Output dir for multiple images
53
54
  --json
54
55
  --model <${[...KNOWN_IMAGE_MODELS].join("|")}> Default: gpt-5.6-luna
55
- Aliases: luna, sol, terra, spark
56
+ Aliases: luna, astra, sol, terra, spark
57
+ --image-tool-model <id> API only: gpt-image-2.5-sunburst|gpt-image-2.5-flare
56
58
  --provider <${PROVIDER_VALUES.join("|")}>
57
59
  Provider (oauth = GPT OAuth; grok = xAI Grok; agy/gemini-api = Gemini)
58
60
  --mode <auto|direct> Prompt handling mode. Default: auto
@@ -128,6 +130,8 @@ export default async function multimodeCmd(argv) {
128
130
  body.model = model;
129
131
  if (args.provider)
130
132
  body.provider = args.provider;
133
+ if (args["image-tool-model"])
134
+ body.imageToolModel = args["image-tool-model"];
131
135
  if (args["reasoning-effort"])
132
136
  body.reasoningEffort = args["reasoning-effort"];
133
137
  if (args["no-web-search"])
@@ -17,10 +17,13 @@ const HELP = `
17
17
  show <nodeId> [--json]
18
18
 
19
19
  Generate options:
20
+ --image-tool-model <id> API only: gpt-image-2.5-sunburst|gpt-image-2.5-flare
21
+ --quality <low|medium|high|xhigh|max> Extended quality requires an API 2.5 model
20
22
  --provider <${PROVIDER_VALUES.join("|")}> Provider for this request
21
23
  ${NAI_CLI_HELP}
22
24
  `;
23
25
  const GEN_FLAGS = {
26
+ "image-tool-model": { type: "string" },
24
27
  quality: { short: "q", type: "string", default: "low" },
25
28
  size: { short: "s", type: "string", default: "1024x1024" },
26
29
  count: { short: "n", type: "string", default: "1" },
@@ -96,6 +99,8 @@ async function generateSub(argv) {
96
99
  body.provider = args.provider;
97
100
  if (args.parent)
98
101
  body.parentNodeId = args.parent;
102
+ if (args["image-tool-model"])
103
+ body.imageToolModel = args["image-tool-model"];
99
104
  if (args["reasoning-effort"])
100
105
  body.reasoningEffort = args["reasoning-effort"];
101
106
  if (args["no-web-search"])
@@ -2,7 +2,7 @@ const HINTS = {
2
2
  SERVER_UNREACHABLE: "Start `ima2 serve`, or pass `--server <url>`.",
3
3
  APIKEY_DISABLED: "API-key generation is supported in current builds; switch providers or update the configured API key.",
4
4
  IMAGE_MODEL_UNSUPPORTED: "This model is visible but cannot generate images here. Use gpt-5.4 or gpt-5.4-mini.",
5
- INVALID_IMAGE_MODEL: "Use one of: gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna.",
5
+ INVALID_IMAGE_MODEL: "Use one of: gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-6-astra.",
6
6
  OAUTH_UNAVAILABLE: "GPT OAuth proxy is unavailable. Check `ima2 doctor` and restart `ima2 serve`.",
7
7
  NETWORK_FAILED: "Network/proxy failed. This is not a moderation refusal.",
8
8
  SAFETY_REFUSAL: "The image backend refused this generation.",
@@ -1,5 +1,6 @@
1
1
  export const IMAGE_MODEL_ALIASES = {
2
2
  luna: "gpt-5.6-luna",
3
+ astra: "gpt-6-astra",
3
4
  sol: "gpt-5.6-sol",
4
5
  terra: "gpt-5.6-terra",
5
6
  spark: "gpt-5.3-codex-spark",
package/docs/API.md CHANGED
@@ -327,6 +327,19 @@ Text-to-image and reference-guided root generation.
327
327
 
328
328
  Supported quality values: `low`, `medium`, `high`.
329
329
 
330
+ For `provider: "api"`, generation, edit, multimode, and node generation also accept
331
+ optional `imageToolModel`: `gpt-image-2.5-sunburst` or `gpt-image-2.5-flare`. It selects
332
+ `tools[].model` for the Responses image-generation tool; `model` still selects the
333
+ outer GPT reasoning model. Omit the field (or send null/empty) for upstream selection
334
+ and the existing defaults. Other providers ignore it. Unknown API values return
335
+ `INVALID_IMAGE_TOOL_MODEL` (400, or the existing synchronous multimode SSE error).
336
+ Only an explicit API 2.5 selection enables quality `xhigh` and `max`; elsewhere those
337
+ values normalize to `medium` with `QUALITY_DEFAULTED`. Sidecars, history, generation
338
+ results and existing XMP embedding paths retain the selected `imageToolModel`.
339
+ Edit continues to preserve upstream bytes and uses sidecar metadata.
340
+ `GET /api/capabilities` advertises the API-only tool model/quality allowlist separately
341
+ from the outer models in `/api/models`.
342
+
330
343
  Supported moderation values: `auto`, `low`.
331
344
 
332
345
  When `storyboard` is `true`, the server prepends storyboard keyframe instructions so image
package/docs/CLI.md CHANGED
@@ -19,6 +19,20 @@ For a quick start, see the [main README](../README.md). For endpoint mapping, se
19
19
  | `ima2 reset` | Remove saved config |
20
20
  | `ima2 backfill-thumbs` | Generate missing gallery thumbnails for images and videos (offline, no running server needed) |
21
21
 
22
+ ## API image tool selection
23
+
24
+ `gen`, `edit`, `multimode`, and `node generate` accept
25
+ `--image-tool-model gpt-image-2.5-sunburst|gpt-image-2.5-flare` with `--provider api`.
26
+ This is separate from `--model`, which continues to choose the outer GPT reasoning
27
+ model. Omission preserves upstream selection and existing defaults. Only an explicit
28
+ API 2.5 selection enables `--quality xhigh|max`; other scopes normalize these values
29
+ to medium. Non-API core providers ignore the tool model; MCP generation rejects the
30
+ core-only flag.
31
+
32
+ ```bash
33
+ ima2 gen "botanical illustration" --provider api --model gpt-5.4 --image-tool-model gpt-image-2.5-sunburst --quality xhigh
34
+ ```
35
+
22
36
  ## Common flags
23
37
 
24
38
  These work on most client commands:
package/docs/FAQ.ko.md CHANGED
@@ -104,6 +104,7 @@ ima2 serve
104
104
  앱은 `gpt-5.6-luna`로 시작합니다. 계정 지원 범위나 호환성 때문에 필요할 때만 다른 모델을 고르세요.
105
105
 
106
106
  - `gpt-5.6-luna`: 현재 앱 기본값입니다.
107
+ - `gpt-6-astra`: 가장 최신 GPT 이미지 모델입니다. 선택 가능하지만 기본값은 아닙니다.
107
108
  - `gpt-5.6-terra` / `gpt-5.6-sol`: 계정에서 지원할 때 고를 수 있는 GPT-5.6 대안입니다.
108
109
  - `gpt-5.5` / `gpt-5.4` / `gpt-5.4-mini`: 호환 선택지로 유지합니다.
109
110
 
package/docs/FAQ.md CHANGED
@@ -137,6 +137,7 @@ Use the **Switch Account** button in Settings > QuotaCard for the provider. This
137
137
  The app starts with `gpt-5.6-luna`; choose another model only when you need an explicit compatibility or account-specific override.
138
138
 
139
139
  - `gpt-5.6-luna`: current app default.
140
+ - `gpt-6-astra`: newest GPT image model; selectable, not the default.
140
141
  - `gpt-5.6-sol` / `gpt-5.6-terra`: current GPT-5.6 alternatives;
141
142
  availability depends on your OAuth account access, so upstream may reject them
142
143
  until the rollout reaches you.
package/docs/FAQ.zh-CN.md CHANGED
@@ -124,6 +124,7 @@ ima2 serve
124
124
  该应用程序开始于`gpt-5.6-luna`;仅当您需要显式兼容性或特定于帐户的覆盖时才选择其他模型。
125
125
 
126
126
  - `gpt-5.6-luna`:当前应用程序默认值。
127
+ - `gpt-6-astra`:最新的 GPT 图像模型;可选,但不是默认值。
127
128
  - `gpt-5.6-sol` / `gpt-5.6-terra`: 当前的GPT-5.6替代方案;
128
129
  可用性取决于您的OAuth帐户访问,因此上游可能会拒绝它们
129
130
  直到您收到推送通知。
package/docs/FAQ.zh-TW.md CHANGED
@@ -124,6 +124,7 @@ ima2 serve
124
124
  該應用程式開始於`gpt-5.6-luna`;僅當您需要明確相容性或特定於帳戶的覆蓋時才選擇其他模型。
125
125
 
126
126
  - `gpt-5.6-luna`:當前應用程式預設值。
127
+ - `gpt-6-astra`:最新的 GPT 影像模型;可選,但不是預設值。
127
128
  - `gpt-5.6-sol` / `gpt-5.6-terra`: 當前的GPT-5.6替代方案;
128
129
  可用性取決於您的OAuth帳戶訪問,因此上游可能會拒絕它們
129
130
  直到您收到推播通知。
package/docs/README.ja.md CHANGED
@@ -131,6 +131,7 @@ Grok 動画の既定値は正式名 `grok-imagine-video-1.5` です。`grok-imag
131
131
  画像生成と Prompt Builder の既定値は **`gpt-5.6-luna`** です。旧モデルは互換オプションとして残ります。
132
132
 
133
133
  - `gpt-5.6-luna` — 現在の画像・Prompt Builder 既定値。
134
+ - `gpt-6-astra` — 最新の GPT 画像モデル。選択できますが既定値ではありません。
134
135
  - `gpt-5.6-terra` / `gpt-5.6-sol` — アカウントで利用できる GPT-5.6 オプション。
135
136
  - `gpt-5.5` / `gpt-5.4` / `gpt-5.4-mini` — 互換オプション。
136
137
 
package/docs/README.ko.md CHANGED
@@ -150,6 +150,7 @@ Grok video 기본값은 정식 `grok-imagine-video-1.5`입니다. `grok-imagine-
150
150
  이미지 생성과 Prompt Builder의 앱 기본값은 **`gpt-5.6-luna`**입니다. 이전 모델은 호환 선택지로 남아 있습니다.
151
151
 
152
152
  - `gpt-5.6-luna` — 현재 이미지·Prompt Builder 기본값.
153
+ - `gpt-6-astra` — 가장 최신 GPT 이미지 모델. 선택 가능하지만 기본값은 아닙니다.
153
154
  - `gpt-5.6-terra` / `gpt-5.6-sol` — 계정에서 지원할 때 고를 수 있는 GPT-5.6 대안.
154
155
  - `gpt-5.5`, `gpt-5.4`, `gpt-5.4-mini` — 호환 선택지.
155
156
 
@@ -187,6 +187,7 @@ Grok视频生成默认为规范`grok-imagine-video-1.5`; `grok-imagine-video`仍
187
187
  该应用程序默认为**`gpt-5.6-luna`**用于图像生成和 Prompt Builder 规划。较旧的受支持型号仍保留明确的兼容性选择。
188
188
 
189
189
  - `gpt-5.6-luna`— 当前图像和提示生成器默认值。
190
+ - `gpt-6-astra`— 最新的 GPT 图像模型;可选,但不是默认值。
190
191
  - `gpt-5.6-terra` / `gpt-5.6-sol`- 当前的GPT-5.6当您的帐户暴露它们时的替代方案。
191
192
  - `gpt-5.5`, `gpt-5.4`, `gpt-5.4-mini`- 支持的兼容性选择。
192
193
 
@@ -181,6 +181,7 @@ Grok影片產生預設為規範`grok-imagine-video-1.5`; `grok-imagine-video`仍
181
181
  該應用程式預設為**`gpt-5.6-luna`**用於影像生成和 Prompt Builder 規劃。較舊的受支援型號仍保留明確的兼容性選擇。
182
182
 
183
183
  - `gpt-5.6-luna`— 目前影像和提示產生器預設值。
184
+ - `gpt-6-astra`— 最新的 GPT 影像模型;可選,但不是預設值。
184
185
  - `gpt-5.6-terra` / `gpt-5.6-sol`- 目前的GPT-5.6當您的帳戶暴露它們時的替代方案。
185
186
  - `gpt-5.5`, `gpt-5.4`, `gpt-5.4-mini`- 支援的相容性選擇。
186
187
 
@@ -4,7 +4,7 @@ Generated by `npm run test:inventory` (script: `scripts/classify-tests.mjs`).
4
4
 
5
5
  _Tests considered "runtime-importing" if they import from `../lib/`, `../routes/`, `../bin/`, `../server`, or `../config`._
6
6
 
7
- Total: 490 (runtime: 225, contract: 265)
7
+ Total: 499 (runtime: 226, contract: 273)
8
8
 
9
9
  ## Runtime-importing tests
10
10
  - `tests/advertise-payload-contract.test.ts`
@@ -26,6 +26,7 @@ Total: 490 (runtime: 225, contract: 265)
26
26
  - `tests/agy-execution-cleanup.test.ts`
27
27
  - `tests/agy-execution-process.test.ts`
28
28
  - `tests/api-cache-policy.test.ts`
29
+ - `tests/api-image-tool-model.test.ts`
29
30
  - `tests/api-provider-parity.test.ts`
30
31
  - `tests/api-request-budget.test.ts`
31
32
  - `tests/asset-character-bindings.test.ts`
@@ -331,6 +332,14 @@ Total: 490 (runtime: 225, contract: 265)
331
332
  - `tests/core-selection-reconcile.test.ts`
332
333
  - `tests/core-selection-transport.test.mjs`
333
334
  - `tests/current-image-actions-readiness-contract.test.js`
335
+ - `tests/desktop-app-lifecycle.test.ts`
336
+ - `tests/desktop-mac-verification.test.ts`
337
+ - `tests/desktop-release-assets.test.ts`
338
+ - `tests/desktop-release-contract.test.ts`
339
+ - `tests/desktop-release-workflow-shell.test.ts`
340
+ - `tests/desktop-sign-extra.test.ts`
341
+ - `tests/desktop-signing-policy.test.ts`
342
+ - `tests/desktop-updater.test.ts`
334
343
  - `tests/direct-mode-visual-contract.test.js`
335
344
  - `tests/dropped-media-sorting-contract.test.ts`
336
345
  - `tests/duration-slider-contract.test.js`
@@ -17,7 +17,7 @@ from PIL import Image
17
17
  DEFAULT_SERVER_URL = "http://127.0.0.1:3333"
18
18
  IMA2_CLIENT_HEADER = "comfyui/bridge"
19
19
  ALLOWED_LOOPBACK_HOSTS = {"127.0.0.1", "localhost", "::1"}
20
- MODEL_OPTIONS = ["", "gpt-5.6-luna", "gpt-5.6-terra", "gpt-5.6-sol", "gpt-5.5", "gpt-5.4", "gpt-5.4-mini"]
20
+ MODEL_OPTIONS = ["", "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"]
21
21
 
22
22
 
23
23
  class Ima2BridgeError(Exception):
@@ -4,7 +4,7 @@ import { AGENT_TOOL_MANIFEST } from "./agentToolManifest.js";
4
4
  import { buildCatalog, catalogSummary } from "./contracts/catalog.js";
5
5
  import { loadAllBundledSnapshots } from "./mcp/snapshotStore.js";
6
6
  import { KEY_TO_ENV, WRITABLE_CONFIG_KEYS } from "./configKeys.js";
7
- import { DEFAULT_IMAGE_QUALITY, VALID_IMAGE_QUALITIES } from "./oauthNormalize.js";
7
+ import { DEFAULT_IMAGE_QUALITY, VALID_IMAGE_QUALITIES, API_IMAGE_TOOL_MODELS, API_IMAGE_25_QUALITIES } from "./oauthNormalize.js";
8
8
  import { MAX_REF2V_DURATION_15, MAX_REF2V_DURATION_BASE, MAX_REF2V_REFERENCES, MAX_REFERENCE_AUDIOS, MAX_VIDEO_DURATION, MIN_VIDEO_DURATION, } from "./imageModels.js";
9
9
  import { deriveProviderIds, getProviderSurfaceSupport } from "./providers/derive.js";
10
10
  import { PROVIDER_SURFACES } from "./providers/surfaceSupport.js";
@@ -75,6 +75,7 @@ export function buildIma2Capabilities({ appConfig = runtimeConfigDefault, packag
75
75
  },
76
76
  },
77
77
  valid: {
78
+ imageToolModels: { api: [...API_IMAGE_TOOL_MODELS], qualities: [...API_IMAGE_25_QUALITIES] },
78
79
  imageModels: {
79
80
  supported: toArray(appConfig.imageModels.valid),
80
81
  unsupported: toArray(appConfig.imageModels.unsupported),
@@ -117,7 +117,7 @@ export async function runGeneratePipeline(req, res, ctx) {
117
117
  }
118
118
  const sessionId = typeof req.body?.sessionId === "string" ? req.body.sessionId : null;
119
119
  const clientNodeId = typeof req.body?.clientNodeId === "string" ? req.body.clientNodeId : null;
120
- const { prompt, quality: rawQuality = "medium", size = "1024x1024", format = "png", moderation = "low", provider = "auto", n = 1, references = [], mode: promptMode = "auto", model: rawModel, reasoningEffort: rawReasoningEffort, webSearchEnabled: rawWebSearchEnabled = true, } = req.body;
120
+ const { prompt, quality: rawQuality = "medium", size = "1024x1024", format = "png", moderation = "low", provider = "auto", n = 1, references = [], mode: promptMode = "auto", model: rawModel, imageToolModel: rawImageToolModel, reasoningEffort: rawReasoningEffort, webSearchEnabled: rawWebSearchEnabled = true, } = req.body;
121
121
  const promptError = validateGenerationPrompt(prompt);
122
122
  if (promptError)
123
123
  return fail(400, promptError);
@@ -144,10 +144,11 @@ export async function runGeneratePipeline(req, res, ctx) {
144
144
  // forced value; the OAuth proxy does not (see lib/imageBackgroundParam.ts).
145
145
  const composerPrompt = normalizeComposerPrompt(req.body?.composerPrompt);
146
146
  const composerInsertedPrompts = normalizeComposerInsertedPrompts(req.body?.composerInsertedPrompts);
147
- const { quality, warnings: qualityWarnings } = normalizeOAuthParams({ provider, quality: rawQuality });
147
+ const { quality, warnings: qualityWarnings } = normalizeOAuthParams({ provider, quality: rawQuality, imageToolModel: rawImageToolModel });
148
148
  const providerOptions = resolveProviderOptions(ctx, {
149
149
  provider,
150
150
  rawModel,
151
+ rawImageToolModel,
151
152
  rawReasoningEffort,
152
153
  rawSize: size,
153
154
  rawWebSearchEnabled,
@@ -156,6 +157,7 @@ export async function runGeneratePipeline(req, res, ctx) {
156
157
  return fail(providerOptions.status, { error: providerOptions.error, code: providerOptions.code });
157
158
  }
158
159
  const imageModel = providerOptions.model;
160
+ const imageToolModel = providerOptions.imageToolModel;
159
161
  const reasoningEffort = providerOptions.reasoningEffort;
160
162
  const effectiveSize = providerOptions.size;
161
163
  const webSearchEnabled = providerOptions.webSearchEnabled;
@@ -317,6 +319,7 @@ export async function runGeneratePipeline(req, res, ctx) {
317
319
  clientNodeId,
318
320
  quality,
319
321
  model: imageModel,
322
+ ...(imageToolModel ? { imageToolModel } : {}),
320
323
  size: effectiveSize,
321
324
  n: count,
322
325
  refsCount: providerRefCount,
@@ -356,6 +359,7 @@ export async function runGeneratePipeline(req, res, ctx) {
356
359
  provider: activeProvider,
357
360
  quality,
358
361
  model: imageModel,
362
+ ...(imageToolModel ? { imageToolModel } : {}),
359
363
  size: effectiveSize,
360
364
  moderation,
361
365
  n: count,
@@ -385,7 +389,7 @@ export async function runGeneratePipeline(req, res, ctx) {
385
389
  surface: "classic", provider: activeProvider, requestId,
386
390
  signal: cancelController.signal, prompt: generationPrompt, rawPrompt: prompt,
387
391
  references: refCheck.refDetails, providerUrl: incomingProviderUrl,
388
- options: { model: imageModel, quality, size: effectiveSize, moderation,
392
+ options: { model: imageModel, imageToolModel, quality, size: effectiveSize, moderation,
389
393
  mode: normalizedPromptMode, reasoningEffort, webSearchEnabled },
390
394
  background: backgroundParams,
391
395
  backgroundConstraint: backgroundPreset ? backgroundPlannerConstraint(backgroundPreset) : undefined,
@@ -514,6 +518,7 @@ export async function runGeneratePipeline(req, res, ctx) {
514
518
  format: resultFormat,
515
519
  moderation,
516
520
  model: activeProvider === "grok" ? resolveGrokQualityModel(imageModel, quality) : imageModel,
521
+ ...(imageToolModel ? { imageToolModel } : {}),
517
522
  reasoningEffort,
518
523
  provider: activeProvider,
519
524
  createdAt,
@@ -617,6 +622,7 @@ export async function runGeneratePipeline(req, res, ctx) {
617
622
  size: effectiveSize,
618
623
  moderation,
619
624
  model: imageModel,
625
+ ...(imageToolModel ? { imageToolModel } : {}),
620
626
  warnings: qualityWarnings,
621
627
  revisedPrompt: firstRevised,
622
628
  promptMode: normalizedPromptMode,
@@ -64,6 +64,7 @@ export async function listHistoryRows(baseDir = config.storage.generatedDir) {
64
64
  size: meta?.size || null,
65
65
  format: meta?.format || name.split(".").pop(),
66
66
  model: meta?.model || null,
67
+ ...(meta?.provider === "api" && meta?.imageToolModel ? { imageToolModel: meta.imageToolModel } : {}),
67
68
  reasoningEffort: meta?.reasoningEffort ?? null,
68
69
  elapsed: meta?.elapsed ?? null,
69
70
  provider: meta?.provider || "oauth",
@@ -55,6 +55,7 @@ export function buildIma2MetadataPayload(meta = {}, context = {}) {
55
55
  format: stringOrNull(meta.format, 20),
56
56
  moderation: stringOrNull(meta.moderation, 40),
57
57
  model: stringOrNull(meta.model, 80),
58
+ ...(meta.provider === "api" && meta.imageToolModel ? { imageToolModel: stringOrNull(meta.imageToolModel, 80) } : {}),
58
59
  reasoningEffort: stringOrNull(meta.reasoningEffort, 40),
59
60
  elapsed: numberOrNull(meta.elapsed),
60
61
  provider: stringOrNull(meta.provider, 40),
@@ -5,6 +5,23 @@ const UNSUPPORTED_IMAGE_MODELS = deriveUnsupportedImageModels();
5
5
  const FALLBACK_REASONING_EFFORT = "none";
6
6
  const VALID_REASONING_EFFORTS = new Set(["none", "low", "medium", "high", "xhigh", "max"]);
7
7
  export const GROK_FALLBACK_IMAGE_MODEL = "grok-imagine-image-2.0";
8
+ /**
9
+ * Models that reject `reasoning.effort: "none"` upstream.
10
+ *
11
+ * GPT-6 Astra accepts low/medium/high/xhigh/max but not "none", while this app's
12
+ * own default effort is "none". Without a coercion here, simply selecting Astra
13
+ * in the UI would fail the request before an image is ever generated. OpenAI's
14
+ * migration guidance names "low" as the replacement for "none"/"minimal", so
15
+ * that is what a "none" request becomes for these models. Every other model is
16
+ * untouched, and an explicit effort is never downgraded.
17
+ */
18
+ const MODELS_WITHOUT_NONE_EFFORT = new Set(["gpt-6-astra"]);
19
+ /** Coerce only the unsupported pairing; any other model/effort passes through. */
20
+ export function coerceReasoningEffortForModel(model, effort) {
21
+ if (effort !== "none" || model === undefined)
22
+ return effort;
23
+ return MODELS_WITHOUT_NONE_EFFORT.has(model) ? "low" : effort;
24
+ }
8
25
  // xAI's current flagship Imagine image model. The legacy "quality" knob used to
9
26
  // swap in grok-imagine-image-quality; 2.0 supersedes it for high-quality work,
10
27
  // so the knob now resolves here. An explicit user selection is never downgraded.
@@ -60,7 +77,7 @@ export function normalizeImageModel(ctx, rawModel) {
60
77
  }
61
78
  if (!valid.has(rawModel)) {
62
79
  return {
63
- error: "model must be one of: gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna",
80
+ error: "model must be one of: gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-6-astra",
64
81
  code: "INVALID_IMAGE_MODEL",
65
82
  status: 400,
66
83
  };
@@ -111,6 +111,7 @@ export async function runMultimodePipeline(req, res, ctx) {
111
111
  let routeQuality = "medium";
112
112
  let routeEffectiveSize = "1024x1024";
113
113
  let routeModeration = "low";
114
+ let routeImageToolModel;
114
115
  let routeImageModel = null;
115
116
  let routeWebSearchEnabled = true;
116
117
  let routePromptMode = "auto";
@@ -127,12 +128,12 @@ export async function runMultimodePipeline(req, res, ctx) {
127
128
  res.flushHeaders?.();
128
129
  }
129
130
  try {
130
- const { prompt, quality: rawQuality = "medium", size = "1024x1024", format = "png", moderation = "low", provider = "auto", references = [], mode: promptMode = "auto", model: rawModel, reasoningEffort: rawReasoningEffort, webSearchEnabled: rawWebSearchEnabled = true, } = req.body;
131
+ const { prompt, quality: rawQuality = "medium", size = "1024x1024", format = "png", moderation = "low", provider = "auto", references = [], mode: promptMode = "auto", model: rawModel, imageToolModel: rawImageToolModel, reasoningEffort: rawReasoningEffort, webSearchEnabled: rawWebSearchEnabled = true, } = req.body;
131
132
  const composerPrompt = normalizeComposerPrompt(req.body?.composerPrompt);
132
133
  const composerInsertedPrompts = normalizeComposerInsertedPrompts(req.body?.composerInsertedPrompts);
133
134
  const maxImages = normalizeMaxImages(maxImagesResult.value, maxAllowedImages);
134
135
  const normalizedPromptMode = promptMode === "direct" ? "direct" : "auto";
135
- const { quality, warnings: qualityWarnings } = normalizeOAuthParams({ provider, quality: rawQuality });
136
+ const { quality, warnings: qualityWarnings } = normalizeOAuthParams({ provider, quality: rawQuality, imageToolModel: rawImageToolModel });
136
137
  // Multimode has no comfy dispatch in this unit. Without this guard the
137
138
  // request would reach generateViaResponses and bill OAuth for an image
138
139
  // the user asked ComfyUI to make — silently, with no error to trace.
@@ -151,6 +152,7 @@ export async function runMultimodePipeline(req, res, ctx) {
151
152
  const providerOptions = resolveProviderOptions(ctx, {
152
153
  provider,
153
154
  rawModel,
155
+ rawImageToolModel,
154
156
  rawReasoningEffort,
155
157
  rawSize: size,
156
158
  rawWebSearchEnabled,
@@ -167,6 +169,7 @@ export async function runMultimodePipeline(req, res, ctx) {
167
169
  });
168
170
  }
169
171
  const imageModel = providerOptions.model;
172
+ const imageToolModel = providerOptions.imageToolModel;
170
173
  const reasoningEffort = providerOptions.reasoningEffort;
171
174
  const effectiveSize = providerOptions.size;
172
175
  const webSearchEnabled = providerOptions.webSearchEnabled;
@@ -227,6 +230,7 @@ export async function runMultimodePipeline(req, res, ctx) {
227
230
  kind: "multimode",
228
231
  quality,
229
232
  model: imageModel,
233
+ ...(imageToolModel ? { imageToolModel } : {}),
230
234
  size: effectiveSize,
231
235
  maxImages,
232
236
  refsCount: referencePayload.refsCount,
@@ -270,6 +274,7 @@ export async function runMultimodePipeline(req, res, ctx) {
270
274
  routeEffectiveSize = effectiveSize;
271
275
  routeModeration = moderation;
272
276
  routeImageModel = imageModel ?? null;
277
+ routeImageToolModel = imageToolModel;
273
278
  routeWebSearchEnabled = webSearchEnabled ?? false;
274
279
  routePromptMode = normalizedPromptMode;
275
280
  routeQualityWarnings = qualityWarnings;
@@ -317,6 +322,7 @@ export async function runMultimodePipeline(req, res, ctx) {
317
322
  format: resultFormat,
318
323
  moderation,
319
324
  model: activeProvider === "grok" ? resolveGrokQualityModel(imageModel, quality) : imageModel,
325
+ ...(imageToolModel ? { imageToolModel } : {}),
320
326
  provider: activeProvider,
321
327
  createdAt,
322
328
  usage: latestUsage,
@@ -357,7 +363,7 @@ export async function runMultimodePipeline(req, res, ctx) {
357
363
  surface: "multimode", provider: activeProvider, requestId,
358
364
  signal: cancelController.signal, prompt: generationPrompt, rawPrompt: prompt,
359
365
  references: refCheck.refDetails, providerUrl: incomingProviderUrl, maxImages,
360
- options: { model: imageModel, quality, size: effectiveSize, moderation,
366
+ options: { model: imageModel, imageToolModel, quality, size: effectiveSize, moderation,
361
367
  mode: normalizedPromptMode, reasoningEffort, webSearchEnabled },
362
368
  nai: activeProvider === "nai" ? readNaiOptions(req.body) : {},
363
369
  }, {
@@ -419,6 +425,7 @@ export async function runMultimodePipeline(req, res, ctx) {
419
425
  size: effectiveSize,
420
426
  moderation,
421
427
  model: imageModel,
428
+ ...(imageToolModel ? { imageToolModel } : {}),
422
429
  usage: latestUsage,
423
430
  webSearchCalls: latestWebSearchCalls,
424
431
  webSearchEnabled,
@@ -470,6 +477,7 @@ export async function runMultimodePipeline(req, res, ctx) {
470
477
  size: routeEffectiveSize,
471
478
  moderation: routeModeration,
472
479
  model: routeImageModel,
480
+ ...(routeImageToolModel ? { imageToolModel: routeImageToolModel } : {}),
473
481
  usage: latestUsage,
474
482
  webSearchCalls: latestWebSearchCalls,
475
483
  webSearchEnabled: routeWebSearchEnabled,