@duckmind/dm-windows-x64 0.60.6 → 0.60.9

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 (197) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +70 -123
  3. package/extensions/dm-9router-ext/src/index.js +410 -5
  4. package/extensions/dm-caveman/extensions/caveman.js +283 -12
  5. package/extensions/dm-cliproxy/index.js +182 -2
  6. package/extensions/dm-cliproxy/scripts/check-config-migration.js +66 -8
  7. package/extensions/dm-cliproxy/src/apply.js +228 -1
  8. package/extensions/dm-cliproxy/src/cache.js +42 -1
  9. package/extensions/dm-cliproxy/src/commands.js +50 -2
  10. package/extensions/dm-cliproxy/src/compat.js +81 -1
  11. package/extensions/dm-cliproxy/src/config.js +192 -2
  12. package/extensions/dm-cliproxy/src/conflicts.js +46 -1
  13. package/extensions/dm-cliproxy/src/fetch-models.js +190 -1
  14. package/extensions/dm-cliproxy/src/fetch-usage.js +41 -1
  15. package/extensions/dm-cliproxy/src/log.js +23 -1
  16. package/extensions/dm-cliproxy/src/status-quota.js +77 -1
  17. package/extensions/dm-cliproxy/src/ui-frame.js +50 -1
  18. package/extensions/dm-cliproxy/src/ui-hub/hub.js +199 -2
  19. package/extensions/dm-cliproxy/src/ui-hub/index.js +26 -2
  20. package/extensions/dm-cliproxy/src/ui-hub/shell.js +46 -1
  21. package/extensions/dm-cliproxy/src/ui-hub/view-diagnostics.js +69 -1
  22. package/extensions/dm-cliproxy/src/ui-hub/view-models.js +379 -1
  23. package/extensions/dm-cliproxy/src/ui-hub/view-usage.js +106 -1
  24. package/extensions/dm-cliproxy/src/ui-picker/catalog.js +41 -1
  25. package/extensions/dm-cliproxy/src/ui-picker/mutate.js +115 -1
  26. package/extensions/dm-cliproxy/src/ui-picker/prompt-confirm.js +35 -1
  27. package/extensions/dm-cliproxy/src/ui-picker/prompt-name.js +62 -1
  28. package/extensions/dm-cliproxy/src/ui-picker/providers.js +45 -1
  29. package/extensions/dm-cliproxy/src/ui-picker/render-text.js +34 -1
  30. package/extensions/dm-cliproxy/src/ui-picker/rows.js +57 -1
  31. package/extensions/dm-cliproxy/src/ui-setup.js +260 -2
  32. package/extensions/dm-cliproxy/src/ui-usage.js +151 -1
  33. package/extensions/dm-cliproxy/src/usage-shared-cache.js +97 -1
  34. package/extensions/dm-context/src/context.js +144 -1
  35. package/extensions/dm-context/src/index.js +339 -7
  36. package/extensions/dm-context/src/utils.js +9 -1
  37. package/extensions/dm-cua/bin/browser-cua.mjs +73 -8
  38. package/extensions/dm-cua/index.js +75 -6
  39. package/extensions/dm-cua/src/browser-cua-lib.mjs +490 -6
  40. package/extensions/dm-cua/src/browser-install.mjs +331 -2
  41. package/extensions/dm-fff/src/index.js +688 -12
  42. package/extensions/dm-fff/src/query.js +60 -1
  43. package/extensions/dm-goal/src/goal.js +894 -23
  44. package/extensions/dm-image2/index.js +103 -9
  45. package/extensions/dm-image2/src/image-lib.mjs +1275 -8
  46. package/extensions/dm-subagents/install.mjs +62 -8
  47. package/extensions/dm-subagents/src/agents/agent-management.js +1190 -36
  48. package/extensions/dm-subagents/src/agents/agent-memory.js +216 -6
  49. package/extensions/dm-subagents/src/agents/agent-scope.js +5 -1
  50. package/extensions/dm-subagents/src/agents/agent-selection.js +20 -1
  51. package/extensions/dm-subagents/src/agents/agent-serializer.js +120 -5
  52. package/extensions/dm-subagents/src/agents/agents.js +1139 -11
  53. package/extensions/dm-subagents/src/agents/chain-serializer.js +299 -11
  54. package/extensions/dm-subagents/src/agents/frontmatter.js +65 -6
  55. package/extensions/dm-subagents/src/agents/identity.js +29 -1
  56. package/extensions/dm-subagents/src/agents/proactive-skills.js +141 -1
  57. package/extensions/dm-subagents/src/agents/skills.js +614 -6
  58. package/extensions/dm-subagents/src/extension/config.js +35 -2
  59. package/extensions/dm-subagents/src/extension/control-notices.js +69 -4
  60. package/extensions/dm-subagents/src/extension/doctor.js +172 -15
  61. package/extensions/dm-subagents/src/extension/fanout-child.js +158 -231
  62. package/extensions/dm-subagents/src/extension/index.js +521 -359
  63. package/extensions/dm-subagents/src/extension/rpc.js +266 -7
  64. package/extensions/dm-subagents/src/extension/schemas.js +275 -1
  65. package/extensions/dm-subagents/src/extension/tool-description.js +111 -6
  66. package/extensions/dm-subagents/src/intercom/intercom-bridge.js +126 -4
  67. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +452 -5
  68. package/extensions/dm-subagents/src/intercom/result-intercom.js +319 -3
  69. package/extensions/dm-subagents/src/profiles/profiles.js +458 -3
  70. package/extensions/dm-subagents/src/runs/background/async-execution.js +834 -40
  71. package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +435 -14
  72. package/extensions/dm-subagents/src/runs/background/async-resume.js +334 -8
  73. package/extensions/dm-subagents/src/runs/background/async-status.js +313 -12
  74. package/extensions/dm-subagents/src/runs/background/chain-append.js +245 -2
  75. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +136 -1
  76. package/extensions/dm-subagents/src/runs/background/completion-batcher.js +94 -1
  77. package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +54 -1
  78. package/extensions/dm-subagents/src/runs/background/control-channel.js +190 -1
  79. package/extensions/dm-subagents/src/runs/background/fleet-view.js +483 -17
  80. package/extensions/dm-subagents/src/runs/background/notify.js +129 -3
  81. package/extensions/dm-subagents/src/runs/background/parallel-groups.js +34 -1
  82. package/extensions/dm-subagents/src/runs/background/result-watcher.js +236 -6
  83. package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +76 -4
  84. package/extensions/dm-subagents/src/runs/background/run-status.js +427 -23
  85. package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +487 -4
  86. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +306 -9
  87. package/extensions/dm-subagents/src/runs/background/subagent-runner.js +2849 -73
  88. package/extensions/dm-subagents/src/runs/background/top-level-async.js +5 -1
  89. package/extensions/dm-subagents/src/runs/background/wait.js +206 -11
  90. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +1013 -12
  91. package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +980 -101
  92. package/extensions/dm-subagents/src/runs/foreground/execution.js +1165 -45
  93. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +3157 -222
  94. package/extensions/dm-subagents/src/runs/shared/acceptance.js +835 -3
  95. package/extensions/dm-subagents/src/runs/shared/chain-outputs.js +104 -1
  96. package/extensions/dm-subagents/src/runs/shared/completion-guard.js +116 -3
  97. package/extensions/dm-subagents/src/runs/shared/dm-args.js +208 -1
  98. package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +90 -1
  99. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +282 -1
  100. package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +148 -1
  101. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +305 -1
  102. package/extensions/dm-subagents/src/runs/shared/model-fallback.js +194 -1
  103. package/extensions/dm-subagents/src/runs/shared/model-scope.js +65 -1
  104. package/extensions/dm-subagents/src/runs/shared/nested-events.js +851 -8
  105. package/extensions/dm-subagents/src/runs/shared/nested-path.js +41 -1
  106. package/extensions/dm-subagents/src/runs/shared/nested-render.js +105 -1
  107. package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +81 -4
  108. package/extensions/dm-subagents/src/runs/shared/run-history.js +51 -4
  109. package/extensions/dm-subagents/src/runs/shared/single-output.js +149 -8
  110. package/extensions/dm-subagents/src/runs/shared/structured-output.js +58 -1
  111. package/extensions/dm-subagents/src/runs/shared/subagent-control.js +166 -5
  112. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +329 -13
  113. package/extensions/dm-subagents/src/runs/shared/tool-budget.js +73 -1
  114. package/extensions/dm-subagents/src/runs/shared/turn-budget.js +47 -4
  115. package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +196 -1
  116. package/extensions/dm-subagents/src/runs/shared/worktree.js +435 -3
  117. package/extensions/dm-subagents/src/shared/artifacts.js +92 -2
  118. package/extensions/dm-subagents/src/shared/atomic-json.js +55 -1
  119. package/extensions/dm-subagents/src/shared/child-transcript.js +167 -5
  120. package/extensions/dm-subagents/src/shared/file-coalescer.js +25 -1
  121. package/extensions/dm-subagents/src/shared/fork-context.js +147 -4
  122. package/extensions/dm-subagents/src/shared/formatters.js +98 -7
  123. package/extensions/dm-subagents/src/shared/jsonl-writer.js +56 -2
  124. package/extensions/dm-subagents/src/shared/model-info.js +62 -1
  125. package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.js +68 -1
  126. package/extensions/dm-subagents/src/shared/session-identity.js +6 -1
  127. package/extensions/dm-subagents/src/shared/session-tokens.js +39 -2
  128. package/extensions/dm-subagents/src/shared/settings.js +198 -11
  129. package/extensions/dm-subagents/src/shared/status-format.js +53 -1
  130. package/extensions/dm-subagents/src/shared/types.js +184 -6
  131. package/extensions/dm-subagents/src/shared/utils.js +462 -2
  132. package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +288 -1
  133. package/extensions/dm-subagents/src/slash/prompt-workflows.js +297 -7
  134. package/extensions/dm-subagents/src/slash/slash-bridge.js +118 -1
  135. package/extensions/dm-subagents/src/slash/slash-commands.js +1287 -31
  136. package/extensions/dm-subagents/src/slash/slash-live-state.js +240 -4
  137. package/extensions/dm-subagents/src/tui/render-helpers.js +64 -1
  138. package/extensions/dm-subagents/src/tui/render.js +1542 -4
  139. package/extensions/dm-usage/index.js +1294 -9
  140. package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +40 -9
  141. package/extensions/greedysearch-dm/bin/cdp-headless.mjs +5 -2
  142. package/extensions/greedysearch-dm/bin/cdp-visible.mjs +5 -2
  143. package/extensions/greedysearch-dm/bin/cdp.mjs +896 -30
  144. package/extensions/greedysearch-dm/bin/gschrome.mjs +30 -2
  145. package/extensions/greedysearch-dm/bin/kill-visible.mjs +7 -2
  146. package/extensions/greedysearch-dm/bin/launch-visible.mjs +13 -2
  147. package/extensions/greedysearch-dm/bin/launch.mjs +282 -10
  148. package/extensions/greedysearch-dm/bin/mcp.mjs +386 -361
  149. package/extensions/greedysearch-dm/bin/search.mjs +620 -540
  150. package/extensions/greedysearch-dm/bin/visible.mjs +22 -2
  151. package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +329 -579
  152. package/extensions/greedysearch-dm/extractors/chatgpt.mjs +301 -583
  153. package/extensions/greedysearch-dm/extractors/common.mjs +408 -32
  154. package/extensions/greedysearch-dm/extractors/consensus.mjs +376 -365
  155. package/extensions/greedysearch-dm/extractors/consent.mjs +303 -14
  156. package/extensions/greedysearch-dm/extractors/gemini.mjs +228 -592
  157. package/extensions/greedysearch-dm/extractors/google-ai.mjs +78 -499
  158. package/extensions/greedysearch-dm/extractors/logically.mjs +270 -347
  159. package/extensions/greedysearch-dm/extractors/perplexity.mjs +243 -581
  160. package/extensions/greedysearch-dm/extractors/selectors.mjs +32 -1
  161. package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +130 -317
  162. package/extensions/greedysearch-dm/index.js +123 -23
  163. package/extensions/greedysearch-dm/src/fetcher.mjs +576 -2
  164. package/extensions/greedysearch-dm/src/formatters/results.js +95 -10
  165. package/extensions/greedysearch-dm/src/formatters/sources.js +57 -1
  166. package/extensions/greedysearch-dm/src/formatters/synthesis.js +49 -1
  167. package/extensions/greedysearch-dm/src/github.mjs +222 -7
  168. package/extensions/greedysearch-dm/src/reddit.mjs +145 -14
  169. package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +340 -9
  170. package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +112 -4
  171. package/extensions/greedysearch-dm/src/search/chrome.mjs +486 -285
  172. package/extensions/greedysearch-dm/src/search/constants.mjs +109 -8
  173. package/extensions/greedysearch-dm/src/search/defaults.mjs +10 -1
  174. package/extensions/greedysearch-dm/src/search/engines.mjs +79 -9
  175. package/extensions/greedysearch-dm/src/search/fetch-source.mjs +441 -349
  176. package/extensions/greedysearch-dm/src/search/file-sources.mjs +29 -7
  177. package/extensions/greedysearch-dm/src/search/minimize.mjs +86 -1
  178. package/extensions/greedysearch-dm/src/search/output.mjs +51 -5
  179. package/extensions/greedysearch-dm/src/search/paths.mjs +48 -1
  180. package/extensions/greedysearch-dm/src/search/pdf.mjs +63 -2
  181. package/extensions/greedysearch-dm/src/search/port-pid.mjs +69 -1
  182. package/extensions/greedysearch-dm/src/search/progress.mjs +109 -2
  183. package/extensions/greedysearch-dm/src/search/query.mjs +21 -1
  184. package/extensions/greedysearch-dm/src/search/recovery.mjs +49 -1
  185. package/extensions/greedysearch-dm/src/search/research.mjs +2227 -458
  186. package/extensions/greedysearch-dm/src/search/scale-aware.mjs +61 -11
  187. package/extensions/greedysearch-dm/src/search/simple-research.mjs +396 -805
  188. package/extensions/greedysearch-dm/src/search/sources.mjs +412 -1
  189. package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +127 -12
  190. package/extensions/greedysearch-dm/src/search/synthesis.mjs +202 -12
  191. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +209 -23
  192. package/extensions/greedysearch-dm/src/tools/shared.js +226 -10
  193. package/extensions/greedysearch-dm/src/utils/content.mjs +35 -4
  194. package/extensions/greedysearch-dm/src/utils/helpers.js +22 -1
  195. package/extensions/greedysearch-dm/src/utils/node-runtime.mjs +10 -1
  196. package/extensions/greedysearch-dm/src/utils/system-cmds.mjs +61 -1
  197. package/package.json +1 -1
@@ -1,8 +1,1275 @@
1
- import{randomUUID as l}from"node:crypto";import{existsSync as F,mkdirSync as x,readFileSync as S,unlinkSync as XZ,writeFileSync as d}from"node:fs";import{dirname as R,join as L,resolve as f}from"node:path";import{spawn as YZ,spawnSync as JZ}from"node:child_process";import{homedir as p}from"node:os";import{getAgentDir as n}from"@duckmind/dm-coding-agent";var QZ="https://chatgpt.com/backend-api/codex/responses",zZ="https://openrouter.ai/api/v1/chat/completions",A="1:1",KZ="png",y=900000,VZ="DM_IMAGE_TEST_DELAY_MS",i="DM_IMAGE_ALLOW_LEGACY_FALLBACK",I="@preset/image2",jZ=I,E="gpt-5.5",GZ=new Set(["image2",I,"duckmind/image2","duckmind/@preset/image2","openrouter/image2","openrouter/@preset/image2"]),qZ=new Set([E,`openai-codex/${E}`,`codex/${E}`]),b={"1:1":{width:1024,height:1024},"4:5":{width:1024,height:1280},"16:9":{width:1600,height:900}},s=f(R(new URL(import.meta.url).pathname),"..","vendor","imagen"),P=L(s,"imagen.py");function BZ(Z){return String(Z??"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,48)||"image"}function UZ(Z){return typeof Z==="string"&&Z in b?Z:A}function NZ(Z){return Z==="svg"?"svg":KZ}function o(Z){let $=String(Z??"").trim();if(qZ.has($))return E;return GZ.has($)?I:jZ}function OZ(Z){return o(Z)===I}function _Z(Z=process.env[i]){return typeof Z==="string"&&/^(1|true|yes|on)$/i.test(Z.trim())}function w(Z="Request was aborted"){let $=Error(Z);return $.name="AbortError",$}function C(Z){if(!Z)return!1;if(Z instanceof Error&&Z.name==="AbortError")return!0;let $=Z instanceof Error?Z.message:String(Z);return $==="Request was aborted"||$==="This operation was aborted"}function k(Z){if(!Z?.aborted)return;if(Z.reason instanceof Error)throw Z.reason;throw w()}function LZ(Z,$=y){let H=new AbortController,W=setTimeout(()=>H.abort(w(`Image request timed out after ${Math.round($/1000)}s`)),$),X=()=>{H.abort(Z?.reason instanceof Error?Z.reason:w())};if(Z?.aborted)X();else if(Z)Z.addEventListener("abort",X,{once:!0});return{signal:H.signal,cleanup(){clearTimeout(W),Z?.removeEventListener("abort",X)}}}function MZ(Z,$){if(!Number.isFinite(Z)||Z<=0)return Promise.resolve();return k($),new Promise((H,W)=>{let X=setTimeout(()=>{$?.removeEventListener("abort",Y),H()},Z),Y=()=>{clearTimeout(X),$?.removeEventListener("abort",Y),W(w())};$?.addEventListener("abort",Y,{once:!0})})}function DZ(){let Z=String(process.env[VZ]??"").trim();if(!Z)return 0;let $=Number.parseInt(Z,10);if(!Number.isFinite($)||$<=0)return 0;return Math.min($,300000)}function r(Z){return L(n(),Z)}function v(Z){try{return JSON.parse(S(Z,"utf-8"))}catch{return}}function Q0(Z,$=Date.now()){let H=typeof Z?.expiresAt==="number"?Z.expiresAt:typeof Z?.expires_at==="number"?Z.expires_at:void 0,W=typeof H!=="number"||H>$;return!!(Z&&Z.accessToken&&Z.accountId&&(!Z.needsReauth||W))}function fZ(Z){if(!Z||typeof Z!=="object")return;let $=Object.keys(Z).filter((Y)=>Y==="openai-codex"||/^openai-codex-account-\d+$/.test(Y)).sort((Y,J)=>{if(Y==="openai-codex")return-1;if(J==="openai-codex")return 1;return Y.localeCompare(J,void 0,{numeric:!0})}).find((Y)=>{let J=Z[Y];return J&&typeof J==="object"&&!Array.isArray(J)}),H=$?Z[$]:void 0;if(!H||typeof H!=="object"||Array.isArray(H))return;let W=typeof H.access==="string"?H.access:void 0,X=typeof H.accountId==="string"?H.accountId:typeof H.account_id==="string"?H.account_id:void 0;if(!W||!X)return;return{accessToken:W,accountId:X,email:typeof H.email==="string"?H.email:void 0}}function TZ(Z){try{let $=String(Z??"").split(".");if($.length!==3)return;let W=JSON.parse(Buffer.from($[1],"base64url").toString("utf8"))?.["https://api.openai.com/auth"]?.chatgpt_account_id;return typeof W==="string"&&W.trim()?W.trim():void 0}catch{return}}function m(Z,$){if(!Z||typeof Z!=="object")return;let H=$.toLowerCase();for(let[W,X]of Object.entries(Z)){if(String(W).toLowerCase()!==H)continue;if(typeof X==="string"&&X.trim())return X.trim()}return}function FZ(Z){let $=Z?.modelRegistry,H=[],W=new Set,X=(Y)=>{if(!Y||typeof Y!=="object")return;if(Y.provider!=="openai-codex")return;let J=`${Y.provider}/${Y.id}`;if(W.has(J))return;W.add(J),H.push(Y)};if(X(Z?.model),$&&typeof $.find==="function")X($.find("openai-codex",E));if($&&typeof $.getAvailable==="function")for(let Y of $.getAvailable())X(Y);return H}async function SZ(Z){let $=Z?.modelRegistry;if(!$||typeof $.getApiKeyAndHeaders!=="function")return;for(let H of FZ(Z)){let W=await $.getApiKeyAndHeaders(H);if(!W?.ok||!W.apiKey)continue;let X=TZ(W.apiKey)||m(W.headers,"ChatGPT-Account-Id")||m(W.headers,"chatgpt-account-id");if(!X)continue;return{accessToken:W.apiKey,accountId:X,email:void 0,source:`model-registry:${H.provider}/${H.id}`}}return}function a(){let Z=v(r("auth.json")),$=fZ(Z);if($)return{...$,source:"auth.json"};throw Error("Missing active Codex credentials. Ensure ~/.dm/agent/auth.json contains an openai-codex or openai-codex-account-N account.")}function bZ(){return["You are a focused image-generation assistant.","Finish by calling image_generation for the final answer.","Do not stop at plain text when the user asked for an image.","Follow the requested visual direction, aspect ratio, and subject details closely."].join(" ")}function wZ(){return["You are a vector illustrator.","Return exactly one standalone SVG image and no prose.","Do not wrap the SVG in markdown fences.","Do not return a data URI unless absolutely necessary.","Do not mention limitations, policies, or explanations in the answer."].join(" ")}function kZ(Z,$){let H=b[$]??b[A];return[`Create one polished self-contained SVG illustration for this brief: ${Z}`,`Use a ${$} aspect ratio with a ${H.width}x${H.height} canvas and a matching viewBox.`,"Return only raw <svg>...</svg> markup.","Use simple shapes, gradients, paths, and fills; avoid external fonts, scripts, or remote URLs.","If the request is a portrait, make it respectful, stylized, and presentation-ready."].join(`
2
- `)}function CZ(Z,$){let H=b[$]??b[A];return[Z,`Output in exactly ${H.width}px x ${H.height}px (${$}).`,"Prefer production-ready composition, grounded local detail when relevant, and the strongest final image_generation call."].join(`
3
- `)}function t(Z,$){let H=b[$]??b[A];return[Z,`Target aspect ratio: ${$}.`,`Compose for roughly ${H.width}x${H.height} output.`,"Generate the final answer by calling the image_generation tool.","If the prompt is a portrait or public person, keep it respectful and presentation-ready."].join(`
4
- `)}function EZ(Z){let $=String(Z??"").trim();if(!$)throw Error("Codex returned an empty image response.");let H=$.match(/!\[[^\]]*]\((data:image\/svg\+xml[^)]+)\)/i);if(H?.[1]){let Q=H[1],z=Q.indexOf(",");if(z!==-1)return decodeURIComponent(Q.slice(z+1)).trim()}let W=$.match(/data:image\/svg\+xml[^,]*,(.+)$/is);if(W?.[1])return decodeURIComponent(W[1]).trim();let Y=$.match(/```(?:svg)?\s*([\s\S]*?)```/i)?.[1]?.trim();if(Y?.startsWith("<svg"))return Y;let J=$.match(/<svg[\s\S]*<\/svg>/i);if(J?.[0])return J[0].trim();throw Error(`Expected SVG markup from Codex, received: ${$.slice(0,240)}`)}async function c(Z,$,H){let W=LZ(H,y),X;try{X=await fetch(QZ,{method:"POST",signal:W.signal,headers:{"Content-Type":"application/json",Authorization:`Bearer ${$.accessToken}`,Accept:"text/event-stream","Cache-Control":"no-cache",originator:"Codex Desktop","User-Agent":"Codex Desktop/26.212.1823 (darwin; arm64)","ChatGPT-Account-Id":$.accountId},body:JSON.stringify(Z)})}catch(K){if(W.cleanup(),C(K)||W.signal.aborted)throw w();throw K}if(!X.ok){W.cleanup();let K=await X.text().catch(()=>"");throw Error(`Codex image request failed: HTTP ${X.status} ${K.slice(0,240)}`)}if(!X.body)throw W.cleanup(),Error("Codex image request returned no response body.");let Y=new TextDecoder,J=X.body.getReader(),Q="",z=null,q=[],B="",G,N=[],U=()=>{if(!z||q.length===0)return z=null,q=[],!1;let K=JSON.parse(q.join(`
5
- `));if(z==="response.output_text.delta"&&typeof K.delta==="string")B+=K.delta;if(z==="response.output_item.done"&&K.item&&typeof K.item==="object")N.push(K.item);if(z==="response.completed"||z==="response.done")return G=K.response?.id??G,z=null,q=[],!0;if(z==="response.failed"||z==="response.incomplete"){let j=K.response?.error?.message||K.response?.incomplete_details?.reason||"Codex image request did not complete.";throw Error(String(j))}return z=null,q=[],!1};try{while(!0){k(W.signal);let{value:K,done:j}=await J.read();Q+=Y.decode(K??new Uint8Array,{stream:!j});while(!0){let O=Q.indexOf(`
6
- `);if(O===-1)break;let V=Q.slice(0,O);if(Q=Q.slice(O+1),V.endsWith("\r"))V=V.slice(0,-1);if(!V){if(U())return{assistantText:B.trim(),outputItems:N,responseId:G};continue}if(V.startsWith("event: "))z=V.slice(7).trim();else if(V.startsWith("data: "))q.push(V.slice(6))}if(j)break}if(q.length>0)U();return{assistantText:B.trim(),outputItems:N,responseId:G}}catch(K){if(await J.cancel().catch(()=>{}),C(K)||W.signal.aborted)throw w();throw K}finally{W.cleanup(),J.releaseLock()}}function IZ(Z,$,H){switch($){case"global":return f(L(n(),"generated-images"));case"custom":if(!H||!String(H).trim())throw Error("save=custom requires saveDir.");return f(String(H).trim());case"project":default:return f(Z,".dm","generated-images")}}function xZ(Z,$,H,W){let X=W||`run-${l().slice(0,8)}`;return f("/tmp","dm-imagen-runs",X)}function h(Z,$){x(R(Z),{recursive:!0}),d(Z,$,"utf-8")}function g(Z,$){x(R(Z),{recursive:!0}),d(Z,$)}function RZ(Z,$){let H=JZ("sips",["-s","format","png",Z,"--out",$],{encoding:"utf-8"});if(H.status===0&&F($))return;let W=(H.stderr||H.stdout||`exit ${H.status??"unknown"}`).trim();throw Error(`Failed to convert SVG to PNG via sips: ${W}`)}function e(Z,$="png"){let H=String(Z??"").trim();if(!H)throw Error("image_generation.result is empty");let W=H,X="";if(H.startsWith("data:")){let Q=H.indexOf(",");if(Q===-1)throw Error("image_generation.result data URL is malformed");let z=H.slice(5,Q);X=String(z.split(";",1)[0]??"").trim(),W=H.slice(Q+1)}let Y=Buffer.from(W,"base64");if(!Y.length)throw Error("image_generation.result decoded to empty bytes");let J=String($||"png").trim().toLowerCase().replace(/^\./,"")||"png";if(X==="image/jpeg")J="jpg";else if(X==="image/webp")J="webp";else if(X==="image/gif")J="gif";else if(X==="image/png")J="png";return{bytes:Y,mimeType:X||(J==="jpg"?"image/jpeg":`image/${J}`),extension:J}}function AZ(Z){return String(Z??"").trim().replace(/^['"]|['"]$/g,"")}function PZ(Z){if(!Z||!F(Z))return;try{for(let $ of S(Z,"utf-8").split(/\r?\n/)){let H=$.trim();if(!H||H.startsWith("#")||!H.includes("="))continue;if(H.startsWith("export "))H=H.slice(7).trim();let[W,...X]=H.split("="),Y=W.trim(),J=AZ(X.join("="));if((Y==="OPENROUTER_API_KEY"||Y==="OPENROUTER_KEY")&&J)return{apiKey:J,source:`config:${Z}`}}}catch{return}return}function u(Z){let $=[],H=f(Z||process.cwd());while(!0){$.push(L(H,"config","openrouter.cnf"));let W=R(H);if(W===H)break;H=W}return $}function yZ(Z){let $=process.env.OPENROUTER_API_KEY||process.env.OPENROUTER_KEY;if($)return{apiKey:$,source:"env:OPENROUTER_API_KEY"};let H=[...u(Z),...u(process.cwd()),L(p(),".dm","openrouter.cnf"),L(p(),".config","dm","openrouter.cnf")],W=new Set;for(let X of H){if(W.has(X))continue;W.add(X);let Y=PZ(X);if(Y)return Y}return}async function vZ(Z){let $=Z?.modelRegistry;if(!$||typeof $!=="object")return;if(typeof $.getApiKeyForProvider==="function")for(let Y of["openrouter","duckmind"]){let J=await $.getApiKeyForProvider(Y);if(typeof J==="string"&&J.trim())return{apiKey:J.trim(),source:`model-registry:${Y}`}}if(typeof $.getApiKeyAndHeaders!=="function")return;let H=[],W=new Set,X=(Y)=>{if(!Y||typeof Y!=="object")return;let J=String(Y.provider??"").toLowerCase();if(J!=="openrouter"&&J!=="duckmind")return;let Q=String(Y.id??Y.modelId??""),z=`${J}/${Q}`;if(W.has(z))return;W.add(z),H.push(Y)};if(X(Z?.model),typeof $.find==="function")X($.find("openrouter",I)),X($.find("openrouter","@preset/free"));if(typeof $.getAvailable==="function")for(let Y of $.getAvailable())X(Y);for(let Y of H){let J=await $.getApiKeyAndHeaders(Y);if(J?.ok&&typeof J.apiKey==="string"&&J.apiKey.trim())return{apiKey:J.apiKey.trim(),source:`model-registry:${Y.provider}/${Y.id??Y.modelId??""}`}}return}async function hZ(Z,$){return await vZ($)??yZ(Z)}function gZ(Z){let $=Z?.content;if(typeof $==="string")return $.trim();if(!Array.isArray($))return"";return $.map((H)=>{if(!H||typeof H!=="object")return"";return typeof H.text==="string"?H.text:""}).join("").trim()}function pZ(Z){for(let $ of Array.isArray(Z?.choices)?Z.choices:[]){let H=$?.message;for(let W of Array.isArray(H?.images)?H.images:[]){let X=W?.image_url,Y=typeof X==="string"?X:X?.url??W?.url;if(typeof Y==="string"&&Y.trim())return Y.trim()}for(let W of Array.isArray(H?.content)?H.content:[]){let X=W?.image_url,Y=typeof X==="string"?X:X?.url??W?.url;if(typeof Y==="string"&&Y.trim())return Y.trim()}}return""}async function mZ({prompt:Z,aspectRatio:$,outputFormat:H,model:W,outputRoot:X,baseName:Y,cwd:J,signal:Q,runtimeAuthContext:z}){if(H!=="png")throw Error("DuckMind image2 currently supports PNG output only.");if(typeof fetch!=="function")throw Error("DuckMind image2 requires a runtime with global fetch support.");let q=await hZ(J,z);if(!q?.apiKey)throw Error("Missing DuckMind API key for image2. Set OPENROUTER_API_KEY, paste a DuckMind API key via /login, or provide a local DuckMind image service config.");let B={model:W,modalities:["image","text"],messages:[{role:"user",content:[{type:"text",text:t(Z,$)}]}]},G=await fetch(zZ,{method:"POST",headers:{Authorization:`Bearer ${q.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(B),signal:Q}),N=await G.text(),U;try{U=N?JSON.parse(N):{}}catch{U={}}if(!G.ok){let _=U?.error?.message||U?.message||N.slice(0,400)||G.statusText;throw Error(`DuckMind image_generation failed (${G.status}): ${_}`)}let K=pZ(U);if(!K)throw Error("DuckMind image_generation returned no image data.");let j=e(K,H),O=L(X,`${Y}.${j.extension}`);g(O,j.bytes);let V=U?.choices?.[0]?.message;return{status:"ok",imagePath:O,svgPath:"",imageBase64:j.bytes.toString("base64"),mimeType:j.mimeType,prompt:Z,aspectRatio:$,model:W,source:q.source,responseId:typeof U?.id==="string"?U.id:"",revisedPrompt:"",backendMode:"openrouter-image-generation",requestedBackend:"openrouter:image2",authSource:q.source,assistantText:gZ(V),openRouterModel:typeof U?.model==="string"?U.model:""}}function cZ(Z){return Array.isArray(Z)?Z.filter(($)=>$&&typeof $==="object"&&$.type==="image_generation_call"):[]}function uZ(){let Z=a();return{tokens:{access_token:Z.accessToken,account_id:Z.accountId}}}function lZ(Z){let $=L(Z,"auth.json");return h($,JSON.stringify(uZ(),null,2)+`
7
- `),$}function ZZ(){let Z=[],$=new Set;for(let[H,W]of[["env:auth_file",process.env.auth_file],["env:M_AUTH_FILE",process.env.M_AUTH_FILE]]){if(typeof W!=="string"||!W.trim())continue;let X=f(W);if(!F(X)||$.has(X))continue;$.add(X),Z.push({kind:"file",authSource:H,authFile:X})}return Z}function $Z(){let Z=v(r("auth.json"));if(!Z||typeof Z!=="object")return[];return Object.keys(Z).filter(($)=>$==="openai-codex"||/^openai-codex-account-\d+$/.test($)).sort(($,H)=>{if($==="openai-codex")return-1;if(H==="openai-codex")return 1;return $.localeCompare(H,void 0,{numeric:!0})}).map(($)=>{let H=Z[$],W=typeof H?.access==="string"?H.access:"",X=typeof H?.accountId==="string"?H.accountId:typeof H?.account_id==="string"?H.account_id:"";if(!W||!X)return;return{kind:"managed",authSource:`auth.json:${$}`,email:typeof H?.email==="string"?H.email:$,authData:{tokens:{access_token:W,account_id:X}}}}).filter(Boolean)}function dZ(Z,$){let H=[],W=new Set,X=(Q,z)=>{let q=z?.tokens?.access_token,B=z?.tokens?.account_id;if(typeof q!=="string"||!q.trim())return;if(typeof B!=="string"||!B.trim())return;let G=`inline:${Q}:${B}`;if(W.has(G))return;W.add(G),H.push({kind:"managed",authSource:Q,email:Q,authData:z})},Y=(Q,z)=>{if(!z)return;let q=f(z);if(!F(q))return;let B=`file:${q}`;if(W.has(B))return;W.add(B),H.push({kind:"file",authSource:Q,authFile:q})},J=$?.liveCodexCredentials;if(J?.accessToken&&J?.accountId)X(J.source||"model-registry",{tokens:{access_token:J.accessToken,account_id:J.accountId}});for(let Q of ZZ())Y(Q.authSource,Q.authFile);for(let Q of $Z()){let z=`managed:${Q.email}`;if(W.has(z))continue;W.add(z),H.push(Q)}if(H.length===0)H.push({kind:"file",authSource:"dm-auth-shim",authFile:lZ(Z)});return H}function nZ(Z){let H=v(Z)?.tokens,W=typeof H?.access_token==="string"?H.access_token.trim():"",X=typeof H?.account_id==="string"?H.account_id.trim():"";if(!W||!X)return;return{accessToken:W,accountId:X,email:void 0,source:Z}}function iZ(Z){let $=Z?.tokens,H=typeof $?.access_token==="string"?$.access_token.trim():"",W=typeof $?.account_id==="string"?$.account_id.trim():"";if(!H||!W)return;return{accessToken:H,accountId:W,email:void 0,source:"managed-inline"}}function sZ(){for(let Z of ZZ()){let $=nZ(Z.authFile);if($)return{...$,source:Z.authSource}}for(let Z of $Z()){let $=iZ(Z.authData);if($)return{...$,source:Z.authSource}}return a()}function HZ(Z){return String(Z??"").split(/\r?\n/).map(($)=>$.trim()).filter(Boolean).map(($)=>f($))}function oZ(Z){if(!F(Z))return[];return S(Z,"utf-8").split(/\r?\n/).map(($)=>$.trim()).filter(Boolean).map(($)=>{try{return JSON.parse($)}catch{return null}}).filter(Boolean)}function rZ(Z){let $=oZ(Z),H="",W="";for(let X of $)if(X?.type==="image_generation_call"&&typeof X.saved_path==="string"&&X.saved_path){if(H=X.saved_path,typeof X.revised_prompt==="string"&&X.revised_prompt)W=X.revised_prompt}return{savedPath:H,revisedPrompt:W}}async function aZ({prompt:Z,aspectRatio:$,model:H,outputFormat:W,cwd:X,signal:Y,sessionRoot:J,candidate:Q,index:z}){let q=L(J,`session-${z}.jsonl`),B=Q.authFile;if(Q.kind==="managed")B=L(J,`auth-${z}.json`),h(B,JSON.stringify(Q.authData,null,2)+`
8
- `);let G={...process.env,PYTHONPATH:s+(process.env.PYTHONPATH?`:${process.env.PYTHONPATH}`:""),auth_file:B,model:H,quiet:"1",no_session_lock:"1",session:`new:${q}`},N=y;return await new Promise((U,K)=>{let j=!1,O="",V="",_=YZ("python3",[P,CZ(Z,$)],{cwd:X,env:G,stdio:["ignore","pipe","pipe"]}),T=setTimeout(()=>{_.kill("SIGTERM")},N),M=()=>{_.kill("SIGTERM")};Y?.addEventListener("abort",M,{once:!0}),_.stdout.on("data",(D)=>{O+=D.toString("utf-8")}),_.stderr.on("data",(D)=>{V+=D.toString("utf-8")}),_.on("error",(D)=>{if(j)return;j=!0,clearTimeout(T),Y?.removeEventListener("abort",M),K(D)}),_.on("close",(D,WZ)=>{if(j)return;if(j=!0,clearTimeout(T),Y?.removeEventListener("abort",M),Y?.aborted){K(w());return}if(WZ==="SIGTERM"){K(Error(`imagen.py timed out after ${Math.round(N/1000)}s`));return}if(D!==0){K(Error(V.trim()||O.trim()||`imagen.py exited ${D}`));return}U({stdout:O,stderr:V,sessionFile:q,sessionRoot:J,authSource:Q.authSource})})})}async function tZ({prompt:Z,aspectRatio:$,model:H,outputFormat:W,cwd:X,save:Y,saveDir:J,signal:Q,runId:z,runtimeAuthContext:q}){if(W!=="png")throw Error("The bundled imagen.py lane only supports PNG output.");if(!F(P))throw Error(`Bundled imagen.py is missing: ${P}`);let B=xZ(X,Y,J,z);x(B,{recursive:!0});let G=dZ(B,q),N=[];for(let[U,K]of G.entries())try{let j=await aZ({prompt:Z,aspectRatio:$,model:H,outputFormat:W,cwd:X,signal:Q,sessionRoot:B,candidate:K,index:U});if(HZ(j.stdout).length>0)return j;N.push(`${K.authSource}: no saved image path`)}catch(j){if(C(j))throw j;N.push(`${K.authSource}: ${j instanceof Error?j.message:String(j)}`)}throw Error(`imagen.py candidates exhausted: ${N.slice(-5).join(" | ")}`)}function eZ(Z,$,H){let W=f(Z);if(!F(W))throw Error(`imagen.py reported a missing artifact: ${W}`);let X=W.split(".").pop()||"png",Y=L($,`${H}.${X}`);return g(Y,S(W)),Y}async function Z0({prompt:Z,aspectRatio:$,outputFormat:H,model:W,outputRoot:X,baseName:Y,credentials:J,signal:Q,imagenError:z,requestedBackend:q}){let B="",G="",N="",U="image/png";try{if(H==="png"){let V=await c({model:W,instructions:bZ(),input:[{type:"message",role:"user",content:[{type:"input_text",text:t(Z,$)}]}],reasoning:{effort:"medium",summary:"detailed"},tools:[{type:"image_generation",output_format:"png"}],tool_choice:"auto",parallel_tool_calls:!1,store:!1,stream:!0,include:["reasoning.encrypted_content"]},J,Q),_=cZ(V.outputItems);if(_.length>0){let T=_[0],M=e(T.result,"png");return G=L(X,`${Y}.${M.extension}`),g(G,M.bytes),N=S(G).toString("base64"),{status:"ok",imagePath:G,svgPath:"",imageBase64:N,mimeType:M.mimeType,prompt:Z,aspectRatio:$,model:W,source:J.source,responseId:V.responseId,revisedPrompt:typeof T.revised_prompt==="string"?T.revised_prompt:"",backendMode:"native-image-generation",requestedBackend:q,imagenError:z,assistantText:V.assistantText}}}let{assistantText:K,responseId:j}=await c({model:W,instructions:wZ(),input:[{type:"message",role:"user",content:[{type:"input_text",text:kZ(Z,$)}]}],reasoning:{effort:"medium",summary:"detailed"},tool_choice:"auto",parallel_tool_calls:!1,store:!1,stream:!0,include:["reasoning.encrypted_content"]},J,Q);k(Q);let O=EZ(K);if(B=L(X,`${Y}.svg`),h(B,O),G=B,U="image/svg+xml",H==="png"){let V=L(X,`${Y}.png`);k(Q),RZ(B,V),k(Q),G=V,N=S(V).toString("base64"),U="image/png"}return{status:"ok",imagePath:G,svgPath:B,imageBase64:N,mimeType:U,prompt:Z,aspectRatio:$,model:W,source:J.source,responseId:j,backendMode:"svg-fallback",requestedBackend:q,imagenError:z,assistantText:K}}catch(K){if(C(K))for(let j of[G,B]){if(!j||!F(j))continue;try{XZ(j)}catch{}}throw K}}async function z0(Z,$,H,W=void 0){let X=String(Z.prompt??"").trim();if(!X)throw Error("image_generation.prompt is required.");k(H);let Y=UZ(Z.aspectRatio),J=NZ(Z.outputFormat),Q=o(Z.model),z=IZ($,Z.save??"project",Z.saveDir);x(z,{recursive:!0});let q=new Date().toISOString().replace(/[:.]/g,"-"),B=BZ(X),G=`image-${q}-${B}-${l().slice(0,8)}`;if(OZ(Q))return await mZ({prompt:X,aspectRatio:Y,outputFormat:J,model:Q,outputRoot:z,baseName:G,cwd:$,signal:H,runtimeAuthContext:W});let N=await SZ(W),U=N??sZ();try{let K=DZ();if(K>0)await MZ(K,H);let j="",O=_Z();if(J==="png"){try{let V=await tZ({prompt:X,aspectRatio:Y,model:Q,outputFormat:J,cwd:$,save:Z.save??"project",saveDir:Z.saveDir,signal:H,runId:G,runtimeAuthContext:N?{liveCodexCredentials:N}:W}),_=HZ(V.stdout);if(_.length>0){let T=rZ(V.sessionFile),M=eZ(T.savedPath||_[0],z,G),D=S(M).toString("base64");return{status:"ok",imagePath:M,svgPath:"",imageBase64:D,mimeType:"image/png",prompt:X,aspectRatio:Y,model:Q,source:U.source,responseId:"",revisedPrompt:T.revisedPrompt||"",backendMode:"imagen-py",requestedBackend:"imagen-py",authSource:V.authSource,assistantText:String(V.stderr??"").trim(),sessionFile:V.sessionFile,imagenSavedPaths:_}}j=`imagen.py produced no saved image path. stderr=${V.stderr.trim().slice(0,400)}`}catch(V){if(C(V))throw V;j=V instanceof Error?V.message:String(V)}if(!O)throw Error(`Bundled imagen.py is required for PNG image_generation, but it failed: ${j}. Provide a healthy DM Codex credential source or set ${i}=1 only for emergency legacy fallback debugging.`)}return await Z0({prompt:X,aspectRatio:Y,outputFormat:J,model:Q,outputRoot:z,baseName:G,credentials:U,signal:H,imagenError:j,requestedBackend:"legacy-fallback"})}catch(K){if(C(K))throw K;throw K}}export{MZ as sleepWithSignal,NZ as resolveOutputFormat,SZ as resolveLiveSessionCodexCredentials,o as resolveImageGenerationModel,sZ as resolveCustomFallbackCredentials,UZ as normalizeAspectRatio,a as loadActiveCodexCredentials,Q0 as isManagedCodexAccountUsable,_Z as isLegacyFallbackEnabled,C as isAbortError,z0 as generateVectorImage,EZ as extractSvgMarkup,TZ as extractAccountIdFromAccessToken,e as decodeGeneratedImageData,w as createAbortError,kZ as buildVectorPrompt};
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { spawn, spawnSync } from "node:child_process";
5
+ import { homedir } from "node:os";
6
+ import { getAgentDir } from "@duckmind/dm-coding-agent";
7
+ const CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
8
+ const OPENROUTER_CHAT_COMPLETIONS_URL = "https://openrouter.ai/api/v1/chat/completions";
9
+ const DEFAULT_ASPECT_RATIO = "1:1";
10
+ const DEFAULT_OUTPUT_FORMAT = "png";
11
+ const REQUEST_TIMEOUT_MS = 900000;
12
+ const TEST_DELAY_ENV = "DM_IMAGE_TEST_DELAY_MS";
13
+ const LEGACY_FALLBACK_ENV = "DM_IMAGE_ALLOW_LEGACY_FALLBACK";
14
+ const IMAGE2_MODEL = "@preset/image2";
15
+ const DEFAULT_IMAGE_MODEL = IMAGE2_MODEL;
16
+ const LEGACY_CODEX_MODEL = "gpt-5.5";
17
+ const IMAGE2_ALIASES = new Set([
18
+ "image2",
19
+ IMAGE2_MODEL,
20
+ "duckmind/image2",
21
+ "duckmind/@preset/image2",
22
+ "openrouter/image2",
23
+ "openrouter/@preset/image2"
24
+ ]);
25
+ const LEGACY_CODEX_ALIASES = new Set([
26
+ LEGACY_CODEX_MODEL,
27
+ `openai-codex/${LEGACY_CODEX_MODEL}`,
28
+ `codex/${LEGACY_CODEX_MODEL}`
29
+ ]);
30
+ const ASPECT_RATIOS = {
31
+ "1:1": { width: 1024, height: 1024 },
32
+ "4:5": { width: 1024, height: 1280 },
33
+ "16:9": { width: 1600, height: 900 }
34
+ };
35
+ const VENDOR_DIR = resolve(dirname(new URL(import.meta.url).pathname), "..", "vendor", "imagen");
36
+ const IMAGEN_SCRIPT = join(VENDOR_DIR, "imagen.py");
37
+ function slugify(value) {
38
+ const text = String(value ?? "").trim().toLowerCase();
39
+ const slug = text.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
40
+ return slug || "image";
41
+ }
42
+ export function normalizeAspectRatio(value) {
43
+ return typeof value === "string" && value in ASPECT_RATIOS ? value : DEFAULT_ASPECT_RATIO;
44
+ }
45
+ export function resolveOutputFormat(value) {
46
+ return value === "svg" ? "svg" : DEFAULT_OUTPUT_FORMAT;
47
+ }
48
+ export function resolveImageGenerationModel(value) {
49
+ const raw = String(value ?? "").trim();
50
+ if (LEGACY_CODEX_ALIASES.has(raw))
51
+ return LEGACY_CODEX_MODEL;
52
+ return IMAGE2_ALIASES.has(raw) ? IMAGE2_MODEL : DEFAULT_IMAGE_MODEL;
53
+ }
54
+ function isOpenRouterImageModel(value) {
55
+ return resolveImageGenerationModel(value) === IMAGE2_MODEL;
56
+ }
57
+ export function isLegacyFallbackEnabled(value = process.env[LEGACY_FALLBACK_ENV]) {
58
+ return typeof value === "string" && /^(1|true|yes|on)$/i.test(value.trim());
59
+ }
60
+ export function createAbortError(message = "Request was aborted") {
61
+ const error = new Error(message);
62
+ error.name = "AbortError";
63
+ return error;
64
+ }
65
+ export function isAbortError(error) {
66
+ if (!error)
67
+ return false;
68
+ if (error instanceof Error && error.name === "AbortError")
69
+ return true;
70
+ const message = error instanceof Error ? error.message : String(error);
71
+ return message === "Request was aborted" || message === "This operation was aborted";
72
+ }
73
+ function throwIfAborted(signal) {
74
+ if (!signal?.aborted)
75
+ return;
76
+ if (signal.reason instanceof Error) {
77
+ throw signal.reason;
78
+ }
79
+ throw createAbortError();
80
+ }
81
+ function createRequestAbortScope(signal, timeoutMs = REQUEST_TIMEOUT_MS) {
82
+ const controller = new AbortController;
83
+ const timeoutId = setTimeout(() => controller.abort(createAbortError(`Image request timed out after ${Math.round(timeoutMs / 1000)}s`)), timeoutMs);
84
+ const onAbort = () => {
85
+ controller.abort(signal?.reason instanceof Error ? signal.reason : createAbortError());
86
+ };
87
+ if (signal?.aborted) {
88
+ onAbort();
89
+ } else if (signal) {
90
+ signal.addEventListener("abort", onAbort, { once: true });
91
+ }
92
+ return {
93
+ signal: controller.signal,
94
+ cleanup() {
95
+ clearTimeout(timeoutId);
96
+ signal?.removeEventListener("abort", onAbort);
97
+ }
98
+ };
99
+ }
100
+ export function sleepWithSignal(ms, signal) {
101
+ if (!Number.isFinite(ms) || ms <= 0)
102
+ return Promise.resolve();
103
+ throwIfAborted(signal);
104
+ return new Promise((resolvePromise, rejectPromise) => {
105
+ const timeoutId = setTimeout(() => {
106
+ signal?.removeEventListener("abort", onAbort);
107
+ resolvePromise();
108
+ }, ms);
109
+ const onAbort = () => {
110
+ clearTimeout(timeoutId);
111
+ signal?.removeEventListener("abort", onAbort);
112
+ rejectPromise(createAbortError());
113
+ };
114
+ signal?.addEventListener("abort", onAbort, { once: true });
115
+ });
116
+ }
117
+ function getTestDelayMs() {
118
+ const raw = String(process.env[TEST_DELAY_ENV] ?? "").trim();
119
+ if (!raw)
120
+ return 0;
121
+ const parsed = Number.parseInt(raw, 10);
122
+ if (!Number.isFinite(parsed) || parsed <= 0)
123
+ return 0;
124
+ return Math.min(parsed, 300000);
125
+ }
126
+ function getAgentPath(fileName) {
127
+ return join(getAgentDir(), fileName);
128
+ }
129
+ function parseJsonFile(filePath) {
130
+ try {
131
+ return JSON.parse(readFileSync(filePath, "utf-8"));
132
+ } catch {
133
+ return;
134
+ }
135
+ }
136
+ function isPlaceholderAccountEmail(value) {
137
+ const email = String(value ?? "").trim().toLowerCase();
138
+ return !email || email === "select";
139
+ }
140
+ export function isManagedCodexAccountUsable(account, nowMs = Date.now()) {
141
+ const expiresAt = typeof account?.expiresAt === "number" ? account.expiresAt : typeof account?.expires_at === "number" ? account.expires_at : undefined;
142
+ const tokenStillFresh = typeof expiresAt !== "number" || expiresAt > nowMs;
143
+ return !!(account && account.accessToken && account.accountId && (!account.needsReauth || tokenStillFresh));
144
+ }
145
+ function selectActiveManagedAccount(storage) {
146
+ if (!storage || typeof storage !== "object")
147
+ return;
148
+ const accounts = Array.isArray(storage.accounts) ? storage.accounts : [];
149
+ const activeEmail = typeof storage.activeEmail === "string" ? storage.activeEmail : undefined;
150
+ for (const account of accounts) {
151
+ if (activeEmail && account?.email === activeEmail && isManagedCodexAccountUsable(account)) {
152
+ return account;
153
+ }
154
+ }
155
+ return accounts.find((account) => isManagedCodexAccountUsable(account));
156
+ }
157
+ function selectFallbackAuth(auth) {
158
+ if (!auth || typeof auth !== "object")
159
+ return;
160
+ const key = Object.keys(auth).filter((candidate) => candidate === "openai-codex" || /^openai-codex-account-\d+$/.test(candidate)).sort((left, right) => {
161
+ if (left === "openai-codex")
162
+ return -1;
163
+ if (right === "openai-codex")
164
+ return 1;
165
+ return left.localeCompare(right, undefined, { numeric: true });
166
+ }).find((candidate) => {
167
+ const value = auth[candidate];
168
+ return value && typeof value === "object" && !Array.isArray(value);
169
+ });
170
+ const entry = key ? auth[key] : undefined;
171
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
172
+ return;
173
+ const accessToken = typeof entry.access === "string" ? entry.access : undefined;
174
+ const accountId = typeof entry.accountId === "string" ? entry.accountId : typeof entry.account_id === "string" ? entry.account_id : undefined;
175
+ if (!accessToken || !accountId)
176
+ return;
177
+ return {
178
+ accessToken,
179
+ accountId,
180
+ email: typeof entry.email === "string" ? entry.email : undefined
181
+ };
182
+ }
183
+ export function extractAccountIdFromAccessToken(token) {
184
+ try {
185
+ const parts = String(token ?? "").split(".");
186
+ if (parts.length !== 3)
187
+ return;
188
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
189
+ const accountId = payload?.["https://api.openai.com/auth"]?.chatgpt_account_id;
190
+ return typeof accountId === "string" && accountId.trim() ? accountId.trim() : undefined;
191
+ } catch {
192
+ return;
193
+ }
194
+ }
195
+ function getHeaderValue(headers, key) {
196
+ if (!headers || typeof headers !== "object")
197
+ return;
198
+ const lower = key.toLowerCase();
199
+ for (const [headerKey, headerValue] of Object.entries(headers)) {
200
+ if (String(headerKey).toLowerCase() !== lower)
201
+ continue;
202
+ if (typeof headerValue === "string" && headerValue.trim()) {
203
+ return headerValue.trim();
204
+ }
205
+ }
206
+ return;
207
+ }
208
+ function listLiveCodexModels(runtimeAuthContext) {
209
+ const registry = runtimeAuthContext?.modelRegistry;
210
+ const models = [];
211
+ const seen = new Set;
212
+ const push = (model) => {
213
+ if (!model || typeof model !== "object")
214
+ return;
215
+ if (model.provider !== "openai-codex")
216
+ return;
217
+ const key = `${model.provider}/${model.id}`;
218
+ if (seen.has(key))
219
+ return;
220
+ seen.add(key);
221
+ models.push(model);
222
+ };
223
+ push(runtimeAuthContext?.model);
224
+ if (registry && typeof registry.find === "function") {
225
+ push(registry.find("openai-codex", LEGACY_CODEX_MODEL));
226
+ }
227
+ if (registry && typeof registry.getAvailable === "function") {
228
+ for (const model of registry.getAvailable()) {
229
+ push(model);
230
+ }
231
+ }
232
+ return models;
233
+ }
234
+ export async function resolveLiveSessionCodexCredentials(runtimeAuthContext) {
235
+ const registry = runtimeAuthContext?.modelRegistry;
236
+ if (!registry || typeof registry.getApiKeyAndHeaders !== "function") {
237
+ return;
238
+ }
239
+ for (const model of listLiveCodexModels(runtimeAuthContext)) {
240
+ const auth = await registry.getApiKeyAndHeaders(model);
241
+ if (!auth?.ok || !auth.apiKey)
242
+ continue;
243
+ const accountId = extractAccountIdFromAccessToken(auth.apiKey) || getHeaderValue(auth.headers, "ChatGPT-Account-Id") || getHeaderValue(auth.headers, "chatgpt-account-id");
244
+ if (!accountId)
245
+ continue;
246
+ return {
247
+ accessToken: auth.apiKey,
248
+ accountId,
249
+ email: undefined,
250
+ source: `model-registry:${model.provider}/${model.id}`
251
+ };
252
+ }
253
+ return;
254
+ }
255
+ export function loadActiveCodexCredentials() {
256
+ const auth = parseJsonFile(getAgentPath("auth.json"));
257
+ const fallback = selectFallbackAuth(auth);
258
+ if (fallback) {
259
+ return {
260
+ ...fallback,
261
+ source: "auth.json"
262
+ };
263
+ }
264
+ throw new Error("Missing active Codex credentials. Ensure ~/.dm/agent/auth.json contains an openai-codex or openai-codex-account-N account.");
265
+ }
266
+ function buildNativeImageInstructions() {
267
+ return [
268
+ "You are a focused image-generation assistant.",
269
+ "Finish by calling image_generation for the final answer.",
270
+ "Do not stop at plain text when the user asked for an image.",
271
+ "Follow the requested visual direction, aspect ratio, and subject details closely."
272
+ ].join(" ");
273
+ }
274
+ function buildImageInstructions() {
275
+ return [
276
+ "You are a vector illustrator.",
277
+ "Return exactly one standalone SVG image and no prose.",
278
+ "Do not wrap the SVG in markdown fences.",
279
+ "Do not return a data URI unless absolutely necessary.",
280
+ "Do not mention limitations, policies, or explanations in the answer."
281
+ ].join(" ");
282
+ }
283
+ export function buildVectorPrompt(prompt, aspectRatio) {
284
+ const size = ASPECT_RATIOS[aspectRatio] ?? ASPECT_RATIOS[DEFAULT_ASPECT_RATIO];
285
+ return [
286
+ `Create one polished self-contained SVG illustration for this brief: ${prompt}`,
287
+ `Use a ${aspectRatio} aspect ratio with a ${size.width}x${size.height} canvas and a matching viewBox.`,
288
+ "Return only raw <svg>...</svg> markup.",
289
+ "Use simple shapes, gradients, paths, and fills; avoid external fonts, scripts, or remote URLs.",
290
+ "If the request is a portrait, make it respectful, stylized, and presentation-ready."
291
+ ].join(`
292
+ `);
293
+ }
294
+ function buildImagenPrompt(prompt, aspectRatio) {
295
+ const size = ASPECT_RATIOS[aspectRatio] ?? ASPECT_RATIOS[DEFAULT_ASPECT_RATIO];
296
+ return [
297
+ prompt,
298
+ `Output in exactly ${size.width}px x ${size.height}px (${aspectRatio}).`,
299
+ "Prefer production-ready composition, grounded local detail when relevant, and the strongest final image_generation call."
300
+ ].join(`
301
+ `);
302
+ }
303
+ function buildNativeImagePrompt(prompt, aspectRatio) {
304
+ const size = ASPECT_RATIOS[aspectRatio] ?? ASPECT_RATIOS[DEFAULT_ASPECT_RATIO];
305
+ return [
306
+ prompt,
307
+ `Target aspect ratio: ${aspectRatio}.`,
308
+ `Compose for roughly ${size.width}x${size.height} output.`,
309
+ "Generate the final answer by calling the image_generation tool.",
310
+ "If the prompt is a portrait or public person, keep it respectful and presentation-ready."
311
+ ].join(`
312
+ `);
313
+ }
314
+ export function extractSvgMarkup(text) {
315
+ const raw = String(text ?? "").trim();
316
+ if (!raw) {
317
+ throw new Error("Codex returned an empty image response.");
318
+ }
319
+ const markdownDataUri = raw.match(/!\[[^\]]*]\((data:image\/svg\+xml[^)]+)\)/i);
320
+ if (markdownDataUri?.[1]) {
321
+ const uri = markdownDataUri[1];
322
+ const commaIndex = uri.indexOf(",");
323
+ if (commaIndex !== -1) {
324
+ return decodeURIComponent(uri.slice(commaIndex + 1)).trim();
325
+ }
326
+ }
327
+ const plainDataUri = raw.match(/data:image\/svg\+xml[^,]*,(.+)$/is);
328
+ if (plainDataUri?.[1]) {
329
+ return decodeURIComponent(plainDataUri[1]).trim();
330
+ }
331
+ const fenced = raw.match(/```(?:svg)?\s*([\s\S]*?)```/i);
332
+ const fencedCandidate = fenced?.[1]?.trim();
333
+ if (fencedCandidate?.startsWith("<svg")) {
334
+ return fencedCandidate;
335
+ }
336
+ const inline = raw.match(/<svg[\s\S]*<\/svg>/i);
337
+ if (inline?.[0]) {
338
+ return inline[0].trim();
339
+ }
340
+ throw new Error(`Expected SVG markup from Codex, received: ${raw.slice(0, 240)}`);
341
+ }
342
+ async function streamCodexResponse(requestBody, credentials, signal) {
343
+ const requestScope = createRequestAbortScope(signal, REQUEST_TIMEOUT_MS);
344
+ let response;
345
+ try {
346
+ response = await fetch(CODEX_RESPONSES_URL, {
347
+ method: "POST",
348
+ signal: requestScope.signal,
349
+ headers: {
350
+ "Content-Type": "application/json",
351
+ Authorization: `Bearer ${credentials.accessToken}`,
352
+ Accept: "text/event-stream",
353
+ "Cache-Control": "no-cache",
354
+ originator: "Codex Desktop",
355
+ "User-Agent": "Codex Desktop/26.212.1823 (darwin; arm64)",
356
+ "ChatGPT-Account-Id": credentials.accountId
357
+ },
358
+ body: JSON.stringify(requestBody)
359
+ });
360
+ } catch (error) {
361
+ requestScope.cleanup();
362
+ if (isAbortError(error) || requestScope.signal.aborted) {
363
+ throw createAbortError();
364
+ }
365
+ throw error;
366
+ }
367
+ if (!response.ok) {
368
+ requestScope.cleanup();
369
+ const body = await response.text().catch(() => "");
370
+ throw new Error(`Codex image request failed: HTTP ${response.status} ${body.slice(0, 240)}`);
371
+ }
372
+ if (!response.body) {
373
+ requestScope.cleanup();
374
+ throw new Error("Codex image request returned no response body.");
375
+ }
376
+ const decoder = new TextDecoder;
377
+ const reader = response.body.getReader();
378
+ let buffer = "";
379
+ let currentEvent = null;
380
+ let dataLines = [];
381
+ let assistantText = "";
382
+ let responseId;
383
+ const outputItems = [];
384
+ const flushEvent = () => {
385
+ if (!currentEvent || dataLines.length === 0) {
386
+ currentEvent = null;
387
+ dataLines = [];
388
+ return false;
389
+ }
390
+ const payload = JSON.parse(dataLines.join(`
391
+ `));
392
+ if (currentEvent === "response.output_text.delta" && typeof payload.delta === "string") {
393
+ assistantText += payload.delta;
394
+ }
395
+ if (currentEvent === "response.output_item.done" && payload.item && typeof payload.item === "object") {
396
+ outputItems.push(payload.item);
397
+ }
398
+ if (currentEvent === "response.completed" || currentEvent === "response.done") {
399
+ responseId = payload.response?.id ?? responseId;
400
+ currentEvent = null;
401
+ dataLines = [];
402
+ return true;
403
+ }
404
+ if (currentEvent === "response.failed" || currentEvent === "response.incomplete") {
405
+ const message = payload.response?.error?.message || payload.response?.incomplete_details?.reason || "Codex image request did not complete.";
406
+ throw new Error(String(message));
407
+ }
408
+ currentEvent = null;
409
+ dataLines = [];
410
+ return false;
411
+ };
412
+ try {
413
+ while (true) {
414
+ throwIfAborted(requestScope.signal);
415
+ const { value, done } = await reader.read();
416
+ buffer += decoder.decode(value ?? new Uint8Array, { stream: !done });
417
+ while (true) {
418
+ const lineEnd = buffer.indexOf(`
419
+ `);
420
+ if (lineEnd === -1)
421
+ break;
422
+ let line = buffer.slice(0, lineEnd);
423
+ buffer = buffer.slice(lineEnd + 1);
424
+ if (line.endsWith("\r"))
425
+ line = line.slice(0, -1);
426
+ if (!line) {
427
+ if (flushEvent()) {
428
+ return { assistantText: assistantText.trim(), outputItems, responseId };
429
+ }
430
+ continue;
431
+ }
432
+ if (line.startsWith("event: ")) {
433
+ currentEvent = line.slice(7).trim();
434
+ } else if (line.startsWith("data: ")) {
435
+ dataLines.push(line.slice(6));
436
+ }
437
+ }
438
+ if (done)
439
+ break;
440
+ }
441
+ if (dataLines.length > 0) {
442
+ flushEvent();
443
+ }
444
+ return { assistantText: assistantText.trim(), outputItems, responseId };
445
+ } catch (error) {
446
+ await reader.cancel().catch(() => {});
447
+ if (isAbortError(error) || requestScope.signal.aborted) {
448
+ throw createAbortError();
449
+ }
450
+ throw error;
451
+ } finally {
452
+ requestScope.cleanup();
453
+ reader.releaseLock();
454
+ }
455
+ }
456
+ function resolveOutputRoot(cwd, save, saveDir) {
457
+ switch (save) {
458
+ case "global":
459
+ return resolve(join(getAgentDir(), "generated-images"));
460
+ case "custom":
461
+ if (!saveDir || !String(saveDir).trim()) {
462
+ throw new Error("save=custom requires saveDir.");
463
+ }
464
+ return resolve(String(saveDir).trim());
465
+ case "project":
466
+ default:
467
+ return resolve(cwd, ".dm", "generated-images");
468
+ }
469
+ }
470
+ function buildImagenSessionRoot(cwd, save, saveDir, runId) {
471
+ const suffix = runId || `run-${randomUUID().slice(0, 8)}`;
472
+ return resolve("/tmp", "dm-imagen-runs", suffix);
473
+ }
474
+ function writeTextFile(filePath, content) {
475
+ mkdirSync(dirname(filePath), { recursive: true });
476
+ writeFileSync(filePath, content, "utf-8");
477
+ }
478
+ function writeBinaryFile(filePath, content) {
479
+ mkdirSync(dirname(filePath), { recursive: true });
480
+ writeFileSync(filePath, content);
481
+ }
482
+ function renderSvgToPng(svgPath, pngPath) {
483
+ const sips = spawnSync("sips", ["-s", "format", "png", svgPath, "--out", pngPath], {
484
+ encoding: "utf-8"
485
+ });
486
+ if (sips.status === 0 && existsSync(pngPath)) {
487
+ return;
488
+ }
489
+ const detail = (sips.stderr || sips.stdout || `exit ${sips.status ?? "unknown"}`).trim();
490
+ throw new Error(`Failed to convert SVG to PNG via sips: ${detail}`);
491
+ }
492
+ export function decodeGeneratedImageData(imageData, outputFormat = "png") {
493
+ const raw = String(imageData ?? "").trim();
494
+ if (!raw) {
495
+ throw new Error("image_generation.result is empty");
496
+ }
497
+ let encoded = raw;
498
+ let mimeType = "";
499
+ if (raw.startsWith("data:")) {
500
+ const commaIndex = raw.indexOf(",");
501
+ if (commaIndex === -1) {
502
+ throw new Error("image_generation.result data URL is malformed");
503
+ }
504
+ const header = raw.slice(5, commaIndex);
505
+ mimeType = String(header.split(";", 1)[0] ?? "").trim();
506
+ encoded = raw.slice(commaIndex + 1);
507
+ }
508
+ const bytes = Buffer.from(encoded, "base64");
509
+ if (!bytes.length) {
510
+ throw new Error("image_generation.result decoded to empty bytes");
511
+ }
512
+ let extension = String(outputFormat || "png").trim().toLowerCase().replace(/^\./, "") || "png";
513
+ if (mimeType === "image/jpeg")
514
+ extension = "jpg";
515
+ else if (mimeType === "image/webp")
516
+ extension = "webp";
517
+ else if (mimeType === "image/gif")
518
+ extension = "gif";
519
+ else if (mimeType === "image/png")
520
+ extension = "png";
521
+ return {
522
+ bytes,
523
+ mimeType: mimeType || (extension === "jpg" ? "image/jpeg" : `image/${extension}`),
524
+ extension
525
+ };
526
+ }
527
+ function stripConfigQuotes(value) {
528
+ return String(value ?? "").trim().replace(/^['"]|['"]$/g, "");
529
+ }
530
+ function readOpenRouterKeyFile(filePath) {
531
+ if (!filePath || !existsSync(filePath))
532
+ return;
533
+ try {
534
+ for (const raw of readFileSync(filePath, "utf-8").split(/\r?\n/)) {
535
+ let line = raw.trim();
536
+ if (!line || line.startsWith("#") || !line.includes("="))
537
+ continue;
538
+ if (line.startsWith("export "))
539
+ line = line.slice("export ".length).trim();
540
+ const [key, ...rest] = line.split("=");
541
+ const name = key.trim();
542
+ const value = stripConfigQuotes(rest.join("="));
543
+ if ((name === "OPENROUTER_API_KEY" || name === "OPENROUTER_KEY") && value) {
544
+ return { apiKey: value, source: `config:${filePath}` };
545
+ }
546
+ }
547
+ } catch {
548
+ return;
549
+ }
550
+ return;
551
+ }
552
+ function walkConfigCandidates(startDir) {
553
+ const candidates = [];
554
+ let current = resolve(startDir || process.cwd());
555
+ while (true) {
556
+ candidates.push(join(current, "config", "openrouter.cnf"));
557
+ const parent = dirname(current);
558
+ if (parent === current)
559
+ break;
560
+ current = parent;
561
+ }
562
+ return candidates;
563
+ }
564
+ function resolveOpenRouterKey(cwd) {
565
+ const envKey = process.env.OPENROUTER_API_KEY || process.env.OPENROUTER_KEY;
566
+ if (envKey)
567
+ return { apiKey: envKey, source: "env:OPENROUTER_API_KEY" };
568
+ const candidates = [
569
+ ...walkConfigCandidates(cwd),
570
+ ...walkConfigCandidates(process.cwd()),
571
+ join(homedir(), ".dm", "openrouter.cnf"),
572
+ join(homedir(), ".config", "dm", "openrouter.cnf")
573
+ ];
574
+ const seen = new Set;
575
+ for (const candidate of candidates) {
576
+ if (seen.has(candidate))
577
+ continue;
578
+ seen.add(candidate);
579
+ const config = readOpenRouterKeyFile(candidate);
580
+ if (config)
581
+ return config;
582
+ }
583
+ return;
584
+ }
585
+ async function resolveRuntimeDuckMindKey(runtimeAuthContext) {
586
+ const registry = runtimeAuthContext?.modelRegistry;
587
+ if (!registry || typeof registry !== "object")
588
+ return;
589
+ if (typeof registry.getApiKeyForProvider === "function") {
590
+ for (const provider of ["openrouter", "duckmind"]) {
591
+ const apiKey = await registry.getApiKeyForProvider(provider);
592
+ if (typeof apiKey === "string" && apiKey.trim()) {
593
+ return { apiKey: apiKey.trim(), source: `model-registry:${provider}` };
594
+ }
595
+ }
596
+ }
597
+ if (typeof registry.getApiKeyAndHeaders !== "function")
598
+ return;
599
+ const candidates = [];
600
+ const seen = new Set;
601
+ const push = (model) => {
602
+ if (!model || typeof model !== "object")
603
+ return;
604
+ const provider = String(model.provider ?? "").toLowerCase();
605
+ if (provider !== "openrouter" && provider !== "duckmind")
606
+ return;
607
+ const id = String(model.id ?? model.modelId ?? "");
608
+ const key = `${provider}/${id}`;
609
+ if (seen.has(key))
610
+ return;
611
+ seen.add(key);
612
+ candidates.push(model);
613
+ };
614
+ push(runtimeAuthContext?.model);
615
+ if (typeof registry.find === "function") {
616
+ push(registry.find("openrouter", IMAGE2_MODEL));
617
+ push(registry.find("openrouter", "@preset/free"));
618
+ }
619
+ if (typeof registry.getAvailable === "function") {
620
+ for (const model of registry.getAvailable())
621
+ push(model);
622
+ }
623
+ for (const model of candidates) {
624
+ const auth = await registry.getApiKeyAndHeaders(model);
625
+ if (auth?.ok && typeof auth.apiKey === "string" && auth.apiKey.trim()) {
626
+ return { apiKey: auth.apiKey.trim(), source: `model-registry:${model.provider}/${model.id ?? model.modelId ?? ""}` };
627
+ }
628
+ }
629
+ return;
630
+ }
631
+ async function resolveDuckMindImage2Key(cwd, runtimeAuthContext) {
632
+ return await resolveRuntimeDuckMindKey(runtimeAuthContext) ?? resolveOpenRouterKey(cwd);
633
+ }
634
+ function openRouterResponseText(message) {
635
+ const content = message?.content;
636
+ if (typeof content === "string")
637
+ return content.trim();
638
+ if (!Array.isArray(content))
639
+ return "";
640
+ return content.map((item) => {
641
+ if (!item || typeof item !== "object")
642
+ return "";
643
+ return typeof item.text === "string" ? item.text : "";
644
+ }).join("").trim();
645
+ }
646
+ function firstOpenRouterImageData(response) {
647
+ for (const choice of Array.isArray(response?.choices) ? response.choices : []) {
648
+ const message = choice?.message;
649
+ for (const item of Array.isArray(message?.images) ? message.images : []) {
650
+ const imageUrl = item?.image_url;
651
+ const value = typeof imageUrl === "string" ? imageUrl : imageUrl?.url ?? item?.url;
652
+ if (typeof value === "string" && value.trim())
653
+ return value.trim();
654
+ }
655
+ for (const item of Array.isArray(message?.content) ? message.content : []) {
656
+ const imageUrl = item?.image_url;
657
+ const value = typeof imageUrl === "string" ? imageUrl : imageUrl?.url ?? item?.url;
658
+ if (typeof value === "string" && value.trim())
659
+ return value.trim();
660
+ }
661
+ }
662
+ return "";
663
+ }
664
+ async function generateViaOpenRouterImage({ prompt, aspectRatio, outputFormat, model, outputRoot, baseName, cwd, signal, runtimeAuthContext }) {
665
+ if (outputFormat !== "png") {
666
+ throw new Error("DuckMind image2 currently supports PNG output only.");
667
+ }
668
+ if (typeof fetch !== "function") {
669
+ throw new Error("DuckMind image2 requires a runtime with global fetch support.");
670
+ }
671
+ const key = await resolveDuckMindImage2Key(cwd, runtimeAuthContext);
672
+ if (!key?.apiKey) {
673
+ throw new Error("Missing DuckMind API key for image2. Set OPENROUTER_API_KEY, paste a DuckMind API key via /login, or provide a local DuckMind image service config.");
674
+ }
675
+ const body = {
676
+ model,
677
+ modalities: ["image", "text"],
678
+ messages: [
679
+ {
680
+ role: "user",
681
+ content: [{ type: "text", text: buildNativeImagePrompt(prompt, aspectRatio) }]
682
+ }
683
+ ]
684
+ };
685
+ const response = await fetch(OPENROUTER_CHAT_COMPLETIONS_URL, {
686
+ method: "POST",
687
+ headers: {
688
+ Authorization: `Bearer ${key.apiKey}`,
689
+ "Content-Type": "application/json"
690
+ },
691
+ body: JSON.stringify(body),
692
+ signal
693
+ });
694
+ const responseText = await response.text();
695
+ let payload;
696
+ try {
697
+ payload = responseText ? JSON.parse(responseText) : {};
698
+ } catch {
699
+ payload = {};
700
+ }
701
+ if (!response.ok) {
702
+ const detail = payload?.error?.message || payload?.message || responseText.slice(0, 400) || response.statusText;
703
+ throw new Error(`DuckMind image_generation failed (${response.status}): ${detail}`);
704
+ }
705
+ const imageData = firstOpenRouterImageData(payload);
706
+ if (!imageData) {
707
+ throw new Error("DuckMind image_generation returned no image data.");
708
+ }
709
+ const decoded = decodeGeneratedImageData(imageData, outputFormat);
710
+ const imagePath = join(outputRoot, `${baseName}.${decoded.extension}`);
711
+ writeBinaryFile(imagePath, decoded.bytes);
712
+ const message = payload?.choices?.[0]?.message;
713
+ return {
714
+ status: "ok",
715
+ imagePath,
716
+ svgPath: "",
717
+ imageBase64: decoded.bytes.toString("base64"),
718
+ mimeType: decoded.mimeType,
719
+ prompt,
720
+ aspectRatio,
721
+ model,
722
+ source: key.source,
723
+ responseId: typeof payload?.id === "string" ? payload.id : "",
724
+ revisedPrompt: "",
725
+ backendMode: "openrouter-image-generation",
726
+ requestedBackend: "openrouter:image2",
727
+ authSource: key.source,
728
+ assistantText: openRouterResponseText(message),
729
+ openRouterModel: typeof payload?.model === "string" ? payload.model : ""
730
+ };
731
+ }
732
+ function nativeImageItems(outputItems) {
733
+ return Array.isArray(outputItems) ? outputItems.filter((item) => item && typeof item === "object" && item.type === "image_generation_call") : [];
734
+ }
735
+ function loadDmCodexAuthEntry() {
736
+ const active = loadActiveCodexCredentials();
737
+ return {
738
+ tokens: {
739
+ access_token: active.accessToken,
740
+ account_id: active.accountId
741
+ }
742
+ };
743
+ }
744
+ function writeImagenAuthFile(sessionRoot) {
745
+ const authFile = join(sessionRoot, "auth.json");
746
+ writeTextFile(authFile, JSON.stringify(loadDmCodexAuthEntry(), null, 2) + `
747
+ `);
748
+ return authFile;
749
+ }
750
+ function listExplicitAuthFileCandidates() {
751
+ const candidates = [];
752
+ const seen = new Set;
753
+ for (const [authSource, rawPath] of [
754
+ ["env:auth_file", process.env.auth_file],
755
+ ["env:M_AUTH_FILE", process.env.M_AUTH_FILE]
756
+ ]) {
757
+ if (typeof rawPath !== "string" || !rawPath.trim())
758
+ continue;
759
+ const authFile = resolve(rawPath);
760
+ if (!existsSync(authFile) || seen.has(authFile))
761
+ continue;
762
+ seen.add(authFile);
763
+ candidates.push({ kind: "file", authSource, authFile });
764
+ }
765
+ return candidates;
766
+ }
767
+ function listAuthJsonImagenAuthCandidates() {
768
+ const auth = parseJsonFile(getAgentPath("auth.json"));
769
+ if (!auth || typeof auth !== "object")
770
+ return [];
771
+ return Object.keys(auth).filter((key) => key === "openai-codex" || /^openai-codex-account-\d+$/.test(key)).sort((left, right) => {
772
+ if (left === "openai-codex")
773
+ return -1;
774
+ if (right === "openai-codex")
775
+ return 1;
776
+ return left.localeCompare(right, undefined, { numeric: true });
777
+ }).map((key) => {
778
+ const account = auth[key];
779
+ const accessToken = typeof account?.access === "string" ? account.access : "";
780
+ const accountId = typeof account?.accountId === "string" ? account.accountId : typeof account?.account_id === "string" ? account.account_id : "";
781
+ if (!accessToken || !accountId)
782
+ return;
783
+ return {
784
+ kind: "managed",
785
+ authSource: `auth.json:${key}`,
786
+ email: typeof account?.email === "string" ? account.email : key,
787
+ authData: {
788
+ tokens: {
789
+ access_token: accessToken,
790
+ account_id: accountId
791
+ }
792
+ }
793
+ };
794
+ }).filter(Boolean);
795
+ }
796
+ function resolveImagenAuthFile(sessionRoot) {
797
+ return { authFile: writeImagenAuthFile(sessionRoot), authSource: "dm-auth-shim" };
798
+ }
799
+ function resolveImagenAuthCandidates(sessionRoot, runtimeAuthContext) {
800
+ const candidates = [];
801
+ const seen = new Set;
802
+ const pushInlineCandidate = (sourceLabel, authData) => {
803
+ const accessToken = authData?.tokens?.access_token;
804
+ const accountId = authData?.tokens?.account_id;
805
+ if (typeof accessToken !== "string" || !accessToken.trim())
806
+ return;
807
+ if (typeof accountId !== "string" || !accountId.trim())
808
+ return;
809
+ const key = `inline:${sourceLabel}:${accountId}`;
810
+ if (seen.has(key))
811
+ return;
812
+ seen.add(key);
813
+ candidates.push({ kind: "managed", authSource: sourceLabel, email: sourceLabel, authData });
814
+ };
815
+ const pushFileCandidate = (sourceLabel, rawPath) => {
816
+ if (!rawPath)
817
+ return;
818
+ const path = resolve(rawPath);
819
+ if (!existsSync(path))
820
+ return;
821
+ const key = `file:${path}`;
822
+ if (seen.has(key))
823
+ return;
824
+ seen.add(key);
825
+ candidates.push({ kind: "file", authSource: sourceLabel, authFile: path });
826
+ };
827
+ const live = runtimeAuthContext?.liveCodexCredentials;
828
+ if (live?.accessToken && live?.accountId) {
829
+ pushInlineCandidate(live.source || "model-registry", {
830
+ tokens: {
831
+ access_token: live.accessToken,
832
+ account_id: live.accountId
833
+ }
834
+ });
835
+ }
836
+ for (const candidate of listExplicitAuthFileCandidates()) {
837
+ pushFileCandidate(candidate.authSource, candidate.authFile);
838
+ }
839
+ for (const candidate of listAuthJsonImagenAuthCandidates()) {
840
+ const key = `managed:${candidate.email}`;
841
+ if (seen.has(key))
842
+ continue;
843
+ seen.add(key);
844
+ candidates.push(candidate);
845
+ }
846
+ if (candidates.length === 0) {
847
+ candidates.push({ kind: "file", authSource: "dm-auth-shim", authFile: writeImagenAuthFile(sessionRoot) });
848
+ }
849
+ return candidates;
850
+ }
851
+ function loadCodexAuthFileCredentials(filePath) {
852
+ const raw = parseJsonFile(filePath);
853
+ const tokens = raw?.tokens;
854
+ const accessToken = typeof tokens?.access_token === "string" ? tokens.access_token.trim() : "";
855
+ const accountId = typeof tokens?.account_id === "string" ? tokens.account_id.trim() : "";
856
+ if (!accessToken || !accountId)
857
+ return;
858
+ return {
859
+ accessToken,
860
+ accountId,
861
+ email: undefined,
862
+ source: filePath
863
+ };
864
+ }
865
+ function loadCodexAuthFileCredentialsFromData(raw) {
866
+ const tokens = raw?.tokens;
867
+ const accessToken = typeof tokens?.access_token === "string" ? tokens.access_token.trim() : "";
868
+ const accountId = typeof tokens?.account_id === "string" ? tokens.account_id.trim() : "";
869
+ if (!accessToken || !accountId)
870
+ return;
871
+ return {
872
+ accessToken,
873
+ accountId,
874
+ email: undefined,
875
+ source: "managed-inline"
876
+ };
877
+ }
878
+ export function resolveCustomFallbackCredentials() {
879
+ for (const candidate of listExplicitAuthFileCandidates()) {
880
+ const creds = loadCodexAuthFileCredentials(candidate.authFile);
881
+ if (creds)
882
+ return { ...creds, source: candidate.authSource };
883
+ }
884
+ for (const candidate of listAuthJsonImagenAuthCandidates()) {
885
+ const creds = loadCodexAuthFileCredentialsFromData(candidate.authData);
886
+ if (creds)
887
+ return { ...creds, source: candidate.authSource };
888
+ }
889
+ return loadActiveCodexCredentials();
890
+ }
891
+ async function resolvePreferredCodexCredentials(runtimeAuthContext) {
892
+ const live = await resolveLiveSessionCodexCredentials(runtimeAuthContext);
893
+ if (live)
894
+ return live;
895
+ return resolveCustomFallbackCredentials();
896
+ }
897
+ function collectSavedPaths(stdout) {
898
+ return String(stdout ?? "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => resolve(line));
899
+ }
900
+ function readImagenJsonSession(sessionFile) {
901
+ if (!existsSync(sessionFile))
902
+ return [];
903
+ return readFileSync(sessionFile, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
904
+ try {
905
+ return JSON.parse(line);
906
+ } catch {
907
+ return null;
908
+ }
909
+ }).filter(Boolean);
910
+ }
911
+ function latestGeneratedImageMeta(sessionFile) {
912
+ const rows = readImagenJsonSession(sessionFile);
913
+ let savedPath = "";
914
+ let revisedPrompt = "";
915
+ for (const row of rows) {
916
+ if (row?.type === "image_generation_call" && typeof row.saved_path === "string" && row.saved_path) {
917
+ savedPath = row.saved_path;
918
+ if (typeof row.revised_prompt === "string" && row.revised_prompt) {
919
+ revisedPrompt = row.revised_prompt;
920
+ }
921
+ }
922
+ }
923
+ return { savedPath, revisedPrompt };
924
+ }
925
+ async function runImagenCliWithCandidate({ prompt, aspectRatio, model, outputFormat, cwd, signal, sessionRoot, candidate, index }) {
926
+ const sessionFile = join(sessionRoot, `session-${index}.jsonl`);
927
+ let authFile = candidate.authFile;
928
+ if (candidate.kind === "managed") {
929
+ authFile = join(sessionRoot, `auth-${index}.json`);
930
+ writeTextFile(authFile, JSON.stringify(candidate.authData, null, 2) + `
931
+ `);
932
+ }
933
+ const env = {
934
+ ...process.env,
935
+ PYTHONPATH: VENDOR_DIR + (process.env.PYTHONPATH ? `:${process.env.PYTHONPATH}` : ""),
936
+ auth_file: authFile,
937
+ model,
938
+ quiet: "1",
939
+ no_session_lock: "1",
940
+ session: `new:${sessionFile}`
941
+ };
942
+ const timeoutMs = REQUEST_TIMEOUT_MS;
943
+ return await new Promise((resolvePromise, rejectPromise) => {
944
+ let settled = false;
945
+ let stdout = "";
946
+ let stderr = "";
947
+ const child = spawn("python3", [IMAGEN_SCRIPT, buildImagenPrompt(prompt, aspectRatio)], {
948
+ cwd,
949
+ env,
950
+ stdio: ["ignore", "pipe", "pipe"]
951
+ });
952
+ const timer = setTimeout(() => {
953
+ child.kill("SIGTERM");
954
+ }, timeoutMs);
955
+ const onAbort = () => {
956
+ child.kill("SIGTERM");
957
+ };
958
+ signal?.addEventListener("abort", onAbort, { once: true });
959
+ child.stdout.on("data", (chunk) => {
960
+ stdout += chunk.toString("utf-8");
961
+ });
962
+ child.stderr.on("data", (chunk) => {
963
+ stderr += chunk.toString("utf-8");
964
+ });
965
+ child.on("error", (error) => {
966
+ if (settled)
967
+ return;
968
+ settled = true;
969
+ clearTimeout(timer);
970
+ signal?.removeEventListener("abort", onAbort);
971
+ rejectPromise(error);
972
+ });
973
+ child.on("close", (code, sig) => {
974
+ if (settled)
975
+ return;
976
+ settled = true;
977
+ clearTimeout(timer);
978
+ signal?.removeEventListener("abort", onAbort);
979
+ if (signal?.aborted) {
980
+ rejectPromise(createAbortError());
981
+ return;
982
+ }
983
+ if (sig === "SIGTERM") {
984
+ rejectPromise(new Error(`imagen.py timed out after ${Math.round(timeoutMs / 1000)}s`));
985
+ return;
986
+ }
987
+ if (code !== 0) {
988
+ rejectPromise(new Error(stderr.trim() || stdout.trim() || `imagen.py exited ${code}`));
989
+ return;
990
+ }
991
+ resolvePromise({
992
+ stdout,
993
+ stderr,
994
+ sessionFile,
995
+ sessionRoot,
996
+ authSource: candidate.authSource
997
+ });
998
+ });
999
+ });
1000
+ }
1001
+ async function runImagenCli({ prompt, aspectRatio, model, outputFormat, cwd, save, saveDir, signal, runId, runtimeAuthContext }) {
1002
+ if (outputFormat !== "png") {
1003
+ throw new Error("The bundled imagen.py lane only supports PNG output.");
1004
+ }
1005
+ if (!existsSync(IMAGEN_SCRIPT)) {
1006
+ throw new Error(`Bundled imagen.py is missing: ${IMAGEN_SCRIPT}`);
1007
+ }
1008
+ const sessionRoot = buildImagenSessionRoot(cwd, save, saveDir, runId);
1009
+ mkdirSync(sessionRoot, { recursive: true });
1010
+ const candidates = resolveImagenAuthCandidates(sessionRoot, runtimeAuthContext);
1011
+ const errors = [];
1012
+ for (const [index, candidate] of candidates.entries()) {
1013
+ try {
1014
+ const run = await runImagenCliWithCandidate({
1015
+ prompt,
1016
+ aspectRatio,
1017
+ model,
1018
+ outputFormat,
1019
+ cwd,
1020
+ signal,
1021
+ sessionRoot,
1022
+ candidate,
1023
+ index
1024
+ });
1025
+ const savedPaths = collectSavedPaths(run.stdout);
1026
+ if (savedPaths.length > 0) {
1027
+ return run;
1028
+ }
1029
+ errors.push(`${candidate.authSource}: no saved image path`);
1030
+ } catch (error) {
1031
+ if (isAbortError(error))
1032
+ throw error;
1033
+ errors.push(`${candidate.authSource}: ${error instanceof Error ? error.message : String(error)}`);
1034
+ }
1035
+ }
1036
+ throw new Error(`imagen.py candidates exhausted: ${errors.slice(-5).join(" | ")}`);
1037
+ }
1038
+ function stageImagenArtifact(savedPath, outputRoot, baseName) {
1039
+ const resolvedSavedPath = resolve(savedPath);
1040
+ if (!existsSync(resolvedSavedPath)) {
1041
+ throw new Error(`imagen.py reported a missing artifact: ${resolvedSavedPath}`);
1042
+ }
1043
+ const extension = resolvedSavedPath.split(".").pop() || "png";
1044
+ const imagePath = join(outputRoot, `${baseName}.${extension}`);
1045
+ writeBinaryFile(imagePath, readFileSync(resolvedSavedPath));
1046
+ return imagePath;
1047
+ }
1048
+ async function generateViaCustomFallback({
1049
+ prompt,
1050
+ aspectRatio,
1051
+ outputFormat,
1052
+ model,
1053
+ outputRoot,
1054
+ baseName,
1055
+ credentials,
1056
+ signal,
1057
+ imagenError,
1058
+ requestedBackend
1059
+ }) {
1060
+ let svgPath = "";
1061
+ let imagePath = "";
1062
+ let imageBase64 = "";
1063
+ let mimeType = "image/png";
1064
+ try {
1065
+ if (outputFormat === "png") {
1066
+ const nativeResponse = await streamCodexResponse({
1067
+ model,
1068
+ instructions: buildNativeImageInstructions(),
1069
+ input: [
1070
+ {
1071
+ type: "message",
1072
+ role: "user",
1073
+ content: [{ type: "input_text", text: buildNativeImagePrompt(prompt, aspectRatio) }]
1074
+ }
1075
+ ],
1076
+ reasoning: { effort: "medium", summary: "detailed" },
1077
+ tools: [{ type: "image_generation", output_format: "png" }],
1078
+ tool_choice: "auto",
1079
+ parallel_tool_calls: false,
1080
+ store: false,
1081
+ stream: true,
1082
+ include: ["reasoning.encrypted_content"]
1083
+ }, credentials, signal);
1084
+ const imageItems = nativeImageItems(nativeResponse.outputItems);
1085
+ if (imageItems.length > 0) {
1086
+ const imageItem = imageItems[0];
1087
+ const decoded = decodeGeneratedImageData(imageItem.result, "png");
1088
+ imagePath = join(outputRoot, `${baseName}.${decoded.extension}`);
1089
+ writeBinaryFile(imagePath, decoded.bytes);
1090
+ imageBase64 = readFileSync(imagePath).toString("base64");
1091
+ return {
1092
+ status: "ok",
1093
+ imagePath,
1094
+ svgPath: "",
1095
+ imageBase64,
1096
+ mimeType: decoded.mimeType,
1097
+ prompt,
1098
+ aspectRatio,
1099
+ model,
1100
+ source: credentials.source,
1101
+ responseId: nativeResponse.responseId,
1102
+ revisedPrompt: typeof imageItem.revised_prompt === "string" ? imageItem.revised_prompt : "",
1103
+ backendMode: "native-image-generation",
1104
+ requestedBackend,
1105
+ imagenError,
1106
+ assistantText: nativeResponse.assistantText
1107
+ };
1108
+ }
1109
+ }
1110
+ const { assistantText, responseId } = await streamCodexResponse({
1111
+ model,
1112
+ instructions: buildImageInstructions(),
1113
+ input: [
1114
+ {
1115
+ type: "message",
1116
+ role: "user",
1117
+ content: [{ type: "input_text", text: buildVectorPrompt(prompt, aspectRatio) }]
1118
+ }
1119
+ ],
1120
+ reasoning: { effort: "medium", summary: "detailed" },
1121
+ tool_choice: "auto",
1122
+ parallel_tool_calls: false,
1123
+ store: false,
1124
+ stream: true,
1125
+ include: ["reasoning.encrypted_content"]
1126
+ }, credentials, signal);
1127
+ throwIfAborted(signal);
1128
+ const svgMarkup = extractSvgMarkup(assistantText);
1129
+ svgPath = join(outputRoot, `${baseName}.svg`);
1130
+ writeTextFile(svgPath, svgMarkup);
1131
+ imagePath = svgPath;
1132
+ mimeType = "image/svg+xml";
1133
+ if (outputFormat === "png") {
1134
+ const pngPath = join(outputRoot, `${baseName}.png`);
1135
+ throwIfAborted(signal);
1136
+ renderSvgToPng(svgPath, pngPath);
1137
+ throwIfAborted(signal);
1138
+ imagePath = pngPath;
1139
+ imageBase64 = readFileSync(pngPath).toString("base64");
1140
+ mimeType = "image/png";
1141
+ }
1142
+ return {
1143
+ status: "ok",
1144
+ imagePath,
1145
+ svgPath,
1146
+ imageBase64,
1147
+ mimeType,
1148
+ prompt,
1149
+ aspectRatio,
1150
+ model,
1151
+ source: credentials.source,
1152
+ responseId,
1153
+ backendMode: "svg-fallback",
1154
+ requestedBackend,
1155
+ imagenError,
1156
+ assistantText
1157
+ };
1158
+ } catch (error) {
1159
+ if (isAbortError(error)) {
1160
+ for (const artifactPath of [imagePath, svgPath]) {
1161
+ if (!artifactPath || !existsSync(artifactPath))
1162
+ continue;
1163
+ try {
1164
+ unlinkSync(artifactPath);
1165
+ } catch {}
1166
+ }
1167
+ }
1168
+ throw error;
1169
+ }
1170
+ }
1171
+ export async function generateVectorImage(params, cwd, signal, runtimeAuthContext = undefined) {
1172
+ const prompt = String(params.prompt ?? "").trim();
1173
+ if (!prompt) {
1174
+ throw new Error("image_generation.prompt is required.");
1175
+ }
1176
+ throwIfAborted(signal);
1177
+ const aspectRatio = normalizeAspectRatio(params.aspectRatio);
1178
+ const outputFormat = resolveOutputFormat(params.outputFormat);
1179
+ const model = resolveImageGenerationModel(params.model);
1180
+ const outputRoot = resolveOutputRoot(cwd, params.save ?? "project", params.saveDir);
1181
+ mkdirSync(outputRoot, { recursive: true });
1182
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
1183
+ const slug = slugify(prompt);
1184
+ const baseName = `image-${timestamp}-${slug}-${randomUUID().slice(0, 8)}`;
1185
+ if (isOpenRouterImageModel(model)) {
1186
+ return await generateViaOpenRouterImage({
1187
+ prompt,
1188
+ aspectRatio,
1189
+ outputFormat,
1190
+ model,
1191
+ outputRoot,
1192
+ baseName,
1193
+ cwd,
1194
+ signal,
1195
+ runtimeAuthContext
1196
+ });
1197
+ }
1198
+ const liveCodexCredentials = await resolveLiveSessionCodexCredentials(runtimeAuthContext);
1199
+ const credentials = liveCodexCredentials ?? resolveCustomFallbackCredentials();
1200
+ try {
1201
+ const testDelayMs = getTestDelayMs();
1202
+ if (testDelayMs > 0) {
1203
+ await sleepWithSignal(testDelayMs, signal);
1204
+ }
1205
+ let imagenError = "";
1206
+ const allowLegacyFallback = isLegacyFallbackEnabled();
1207
+ if (outputFormat === "png") {
1208
+ try {
1209
+ const run = await runImagenCli({
1210
+ prompt,
1211
+ aspectRatio,
1212
+ model,
1213
+ outputFormat,
1214
+ cwd,
1215
+ save: params.save ?? "project",
1216
+ saveDir: params.saveDir,
1217
+ signal,
1218
+ runId: baseName,
1219
+ runtimeAuthContext: liveCodexCredentials ? { liveCodexCredentials } : runtimeAuthContext
1220
+ });
1221
+ const savedPaths = collectSavedPaths(run.stdout);
1222
+ if (savedPaths.length > 0) {
1223
+ const meta = latestGeneratedImageMeta(run.sessionFile);
1224
+ const imagePath = stageImagenArtifact(meta.savedPath || savedPaths[0], outputRoot, baseName);
1225
+ const imageBase64 = readFileSync(imagePath).toString("base64");
1226
+ return {
1227
+ status: "ok",
1228
+ imagePath,
1229
+ svgPath: "",
1230
+ imageBase64,
1231
+ mimeType: "image/png",
1232
+ prompt,
1233
+ aspectRatio,
1234
+ model,
1235
+ source: credentials.source,
1236
+ responseId: "",
1237
+ revisedPrompt: meta.revisedPrompt || "",
1238
+ backendMode: "imagen-py",
1239
+ requestedBackend: "imagen-py",
1240
+ authSource: run.authSource,
1241
+ assistantText: String(run.stderr ?? "").trim(),
1242
+ sessionFile: run.sessionFile,
1243
+ imagenSavedPaths: savedPaths
1244
+ };
1245
+ }
1246
+ imagenError = `imagen.py produced no saved image path. stderr=${run.stderr.trim().slice(0, 400)}`;
1247
+ } catch (error) {
1248
+ if (isAbortError(error)) {
1249
+ throw error;
1250
+ }
1251
+ imagenError = error instanceof Error ? error.message : String(error);
1252
+ }
1253
+ if (!allowLegacyFallback) {
1254
+ throw new Error(`Bundled imagen.py is required for PNG image_generation, but it failed: ${imagenError}. ` + `Provide a healthy DM Codex credential source or set ${LEGACY_FALLBACK_ENV}=1 only for emergency legacy fallback debugging.`);
1255
+ }
1256
+ }
1257
+ return await generateViaCustomFallback({
1258
+ prompt,
1259
+ aspectRatio,
1260
+ outputFormat,
1261
+ model,
1262
+ outputRoot,
1263
+ baseName,
1264
+ credentials,
1265
+ signal,
1266
+ imagenError,
1267
+ requestedBackend: "legacy-fallback"
1268
+ });
1269
+ } catch (error) {
1270
+ if (isAbortError(error)) {
1271
+ throw error;
1272
+ }
1273
+ throw error;
1274
+ }
1275
+ }