@xopcai/xopc 0.0.166 → 0.0.167

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 (136) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
  3. package/dist/extensions/telegram/xopc.extension.json +1 -1
  4. package/dist/gateway/static/root/assets/{activity-store-DkALQFJT.js → activity-store-DjEDu38T.js} +1 -1
  5. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Tr2hgn4j.js → agent-avatar-dicebear-DQULz7qY.js} +1 -1
  6. package/dist/gateway/static/root/assets/agents-CnzkBgKC.js +2 -0
  7. package/dist/gateway/static/root/assets/{api-DPIXEcIq.js → api-DwVCuPOb.js} +1 -1
  8. package/dist/gateway/static/root/assets/{app-management-settings-panel-D7Nrbhez.js → app-management-settings-panel-DOjWALVF.js} +1 -1
  9. package/dist/gateway/static/root/assets/{appearance-settings-DNNbABFj.js → appearance-settings-C31COUIC.js} +1 -1
  10. package/dist/gateway/static/root/assets/{apps-page-0zXIeywq.js → apps-page-lSC486eu.js} +1 -1
  11. package/dist/gateway/static/root/assets/{archive-plugin-CizqFy4G.js → archive-plugin-DQPvLqfx.js} +1 -1
  12. package/dist/gateway/static/root/assets/{attachment-load-DU8KjTua.js → attachment-load-DwT1x1rc.js} +1 -1
  13. package/dist/gateway/static/root/assets/{automation-suggestion-card-3pqyIveW.js → automation-suggestion-card-ByYBiiMR.js} +1 -1
  14. package/dist/gateway/static/root/assets/{automations-page-DSz3xDsi.js → automations-page-iZTnc1YZ.js} +1 -1
  15. package/dist/gateway/static/root/assets/{binary-plugins-CipUnsOr.js → binary-plugins-CRXiHoZS.js} +1 -1
  16. package/dist/gateway/static/root/assets/{block-editor-XoEq6qiv.js → block-editor-CHO6ILiz.js} +1 -1
  17. package/dist/gateway/static/root/assets/{browser-settings-page-COYvq3y5.js → browser-settings-page-C-OOPZcJ.js} +1 -1
  18. package/dist/gateway/static/root/assets/{browser-workflow-inputs-DRH995XI.js → browser-workflow-inputs-3UaarfXS.js} +1 -1
  19. package/dist/gateway/static/root/assets/{browser-workflows-page-65edjhpz.js → browser-workflows-page-BFaa-N_D.js} +1 -1
  20. package/dist/gateway/static/root/assets/capabilities-settings-panel-BayH0Uom.js +7 -0
  21. package/dist/gateway/static/root/assets/capability-presets-api-DdCHSzcA.js +1 -0
  22. package/dist/gateway/static/root/assets/capability-presets-settings-panel-JlhjAhT7.js +2 -0
  23. package/dist/gateway/static/root/assets/{channel-recipient-api-B5rLegwk.js → channel-recipient-api-BOkLI89e.js} +1 -1
  24. package/dist/gateway/static/root/assets/{channels-settings-D8nH0mve.js → channels-settings-C-Spmwr5.js} +1 -1
  25. package/dist/gateway/static/root/assets/channels-status-swr-ZrqadU60.js +1 -0
  26. package/dist/gateway/static/root/assets/{connector-logo-n4Hg41iD.js → connector-logo-CbcC0T6i.js} +1 -1
  27. package/dist/gateway/static/root/assets/{connectors-page-jvRWZn3Y.js → connectors-page-DpDmJ3Nx.js} +1 -1
  28. package/dist/gateway/static/root/assets/{desktop-pet-D4OzBOwm.js → desktop-pet-CR7RGQff.js} +1 -1
  29. package/dist/gateway/static/root/assets/{desktop-pet-settings-CDGezbaf.js → desktop-pet-settings-BudmCcPE.js} +1 -1
  30. package/dist/gateway/static/root/assets/{directory-picker-path-field-DmL4AUXQ.js → directory-picker-path-field-BLG2Oe6z.js} +1 -1
  31. package/dist/gateway/static/root/assets/{dreaming-settings-ClUcBKnd.js → dreaming-settings-BwBGC0SG.js} +1 -1
  32. package/dist/gateway/static/root/assets/{extension-debug-page-DsV_KUDM.js → extension-debug-page-Pr65_hLC.js} +1 -1
  33. package/dist/gateway/static/root/assets/{extension-page-DJv_G1jY.js → extension-page-BuJDcoKn.js} +1 -1
  34. package/dist/gateway/static/root/assets/{extension-settings-page-DUAlJ4CU.js → extension-settings-page-B3802tn5.js} +1 -1
  35. package/dist/gateway/static/root/assets/{gateway-config-api-SVYgzqv7.js → gateway-config-api-D-NVkfts.js} +1 -1
  36. package/dist/gateway/static/root/assets/{gateway-settings-YL6f2C_F.js → gateway-settings-DIdsKVXv.js} +1 -1
  37. package/dist/gateway/static/root/assets/{gateway-startup-retry-ulwTJovP.js → gateway-startup-retry-qnEmj-vL.js} +1 -1
  38. package/dist/gateway/static/root/assets/{goal-create-dialog-CJJe-4Co.js → goal-create-dialog-51j_DxgV.js} +1 -1
  39. package/dist/gateway/static/root/assets/{goal-detail-page-Da9VMWRd.js → goal-detail-page-CeqMr5GF.js} +1 -1
  40. package/dist/gateway/static/root/assets/{goals-config-api-st_Itxa9.js → goals-config-api-BkVBh7ct.js} +1 -1
  41. package/dist/gateway/static/root/assets/{goals-page-B6pC7PYZ.js → goals-page-lf3oM4Xa.js} +1 -1
  42. package/dist/gateway/static/root/assets/{goals-settings-BEZePZ5m.js → goals-settings-Cg7MJLvx.js} +1 -1
  43. package/dist/gateway/static/root/assets/{heartbeat-settings-DpeD9GQ9.js → heartbeat-settings-BV3kJOcX.js} +1 -1
  44. package/dist/gateway/static/root/assets/{index-BhQMnKH1.js → index-B24B26og.js} +9 -9
  45. package/dist/gateway/static/root/assets/index-DfE2j0g8.css +1 -0
  46. package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-B9K5rTZZ.js +1 -0
  47. package/dist/gateway/static/root/assets/{local-app-workbench-page-Ctia6lSG.js → local-app-workbench-page-ehzr9CBG.js} +1 -1
  48. package/dist/gateway/static/root/assets/{local-apps-page-DWF9rpWQ.js → local-apps-page-BBx6mfAD.js} +1 -1
  49. package/dist/gateway/static/root/assets/locale-store-tnRLWdx2.js +1 -0
  50. package/dist/gateway/static/root/assets/{logs-page-D3gL6vHX.js → logs-page-xuwEkusZ.js} +1 -1
  51. package/dist/gateway/static/root/assets/{markdown-split-BuduJ0Ev.js → markdown-split-DRDddIt8.js} +1 -1
  52. package/dist/gateway/static/root/assets/{media-plugins-CLASZjhT.js → media-plugins-Dn3hxhOV.js} +1 -1
  53. package/dist/gateway/static/root/assets/{messages-C96Zjb0V.js → messages-B1EG4gdu.js} +2 -4
  54. package/dist/gateway/static/root/assets/{notes-page-ve3XiBDF.js → notes-page-BtKHSb4z.js} +1 -1
  55. package/dist/gateway/static/root/assets/{notes-workbench-oHU532lp.js → notes-workbench-DXGXn5XF.js} +2 -2
  56. package/dist/gateway/static/root/assets/{page-tabs-DU85jn98.js → page-tabs-P2BEd1D8.js} +1 -1
  57. package/dist/gateway/static/root/assets/{preview-open-alternatives-CtchTuRQ.js → preview-open-alternatives-DrkjmW0F.js} +1 -1
  58. package/dist/gateway/static/root/assets/{product-open-page-DFT6Fx6y.js → product-open-page-CGzMEowb.js} +1 -1
  59. package/dist/gateway/static/root/assets/{project-detail-page-Ceu0NoC1.js → project-detail-page-IiE2ddWb.js} +1 -1
  60. package/dist/gateway/static/root/assets/{projects-page-CdpqOmO8.js → projects-page-DGOVhaYI.js} +1 -1
  61. package/dist/gateway/static/root/assets/{remote-access-hub-D1NKLyiR.js → remote-access-hub-l_y_Shgl.js} +1 -1
  62. package/dist/gateway/static/root/assets/{save-bar-controls-CmcrHpMc.js → save-bar-controls-BkGrufxl.js} +1 -1
  63. package/dist/gateway/static/root/assets/{schedule-field-DmIkP6JV.js → schedule-field-A8tDpL-9.js} +1 -1
  64. package/dist/gateway/static/root/assets/{schema-form-Dkk9blN0.js → schema-form-N6_hDWgx.js} +1 -1
  65. package/dist/gateway/static/root/assets/{sessions-page-CMj_JaOk.js → sessions-page-CAX4ipPv.js} +1 -1
  66. package/dist/gateway/static/root/assets/settings-advanced-gate-eVlH9brU.js +1 -0
  67. package/dist/gateway/static/root/assets/{settings-form-section-CuQzWvg5.js → settings-form-section-Bb6yYt5f.js} +1 -1
  68. package/dist/gateway/static/root/assets/{settings-loading-skeleton-C5FNbyej.js → settings-loading-skeleton-Q_Vh6HEl.js} +1 -1
  69. package/dist/gateway/static/root/assets/{settings-page-a-A2yTGE.js → settings-page-CjOtQpbR.js} +1 -1
  70. package/dist/gateway/static/root/assets/{settings-page-layout-MuXpamWD.js → settings-page-layout-CtESNNdx.js} +1 -1
  71. package/dist/gateway/static/root/assets/{setup-status-panel-BUdh8FKY.js → setup-status-panel-CGxHXgJB.js} +1 -1
  72. package/dist/gateway/static/root/assets/{share-preview-page-BfzzmTgg.js → share-preview-page-C65IsFmd.js} +1 -1
  73. package/dist/gateway/static/root/assets/{shares-settings-8a9qnLF8.js → shares-settings-Ct3fcbHr.js} +1 -1
  74. package/dist/gateway/static/root/assets/skill-reload-api-ElA5kabh.js +1 -0
  75. package/dist/gateway/static/root/assets/{skills-page-CmHj-CKY.js → skills-page-FeieOEn3.js} +1 -1
  76. package/dist/gateway/static/root/assets/{system-settings-panel-CyhRkWMi.js → system-settings-panel-CdUl2I0D.js} +1 -1
  77. package/dist/gateway/static/root/assets/{theme-store-D37XLD_C.js → theme-store-H9T49Iar.js} +1 -1
  78. package/dist/gateway/static/root/assets/{time-picker-WEPp3HrB.js → time-picker-EkhqT3fS.js} +1 -1
  79. package/dist/gateway/static/root/assets/{typed-models-lib-Bi1uoxFR.js → typed-models-lib-5xRRUE9Y.js} +1 -1
  80. package/dist/gateway/static/root/assets/{use-channel-catalog-DBh0dOC9.js → use-channel-catalog-UACJTyH0.js} +1 -1
  81. package/dist/gateway/static/root/assets/{user-context-page-BObEyiim.js → user-context-page-DHiRvd4i.js} +1 -1
  82. package/dist/gateway/static/root/assets/{voice-api-key-field-Gvgodhec.js → voice-api-key-field-Bql0SPCp.js} +1 -1
  83. package/dist/gateway/static/root/assets/{work-discovery-page-BHnjvGH1.js → work-discovery-page-BcmZux7y.js} +1 -1
  84. package/dist/gateway/static/root/assets/{work-item-detail-page-CQ8ybkxx.js → work-item-detail-page-D94Q5zVD.js} +1 -1
  85. package/dist/gateway/static/root/assets/{work-page-D_U_En23.js → work-page-DdFBMDcB.js} +1 -1
  86. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-B-Sk6gFn.js → workflow-run-setup-panel-Dow7ZztR.js} +1 -1
  87. package/dist/gateway/static/root/assets/{workflows-page-DT0pzfkz.js → workflows-page-wOVSoduy.js} +1 -1
  88. package/dist/gateway/static/root/index.html +8 -8
  89. package/dist/package.js +1 -1
  90. package/dist/src/agent/image/generation/custom-openai-images-provider.d.ts +7 -0
  91. package/dist/src/agent/image/generation/custom-openai-images-provider.js +81 -0
  92. package/dist/src/agent/image/generation/{openai-compatible-image-provider.d.ts → openai-images-provider.d.ts} +10 -31
  93. package/dist/src/agent/image/generation/{openai-compatible-image-provider.js → openai-images-provider.js} +36 -23
  94. package/dist/src/agent/image/generation/provider-registry.d.ts +2 -0
  95. package/dist/src/agent/image/generation/provider-registry.js +50 -4
  96. package/dist/src/agent/image/generation/providers/openai.js +2 -2
  97. package/dist/src/agent/image/generation/runtime.js +5 -1
  98. package/dist/src/agent/image/generation/types.d.ts +11 -0
  99. package/dist/src/cli/commands/channels.js +1 -1
  100. package/dist/src/cli/commands/connectors.js +1 -1
  101. package/dist/src/cli/commands/doctor/checks/image-providers.d.ts +2 -0
  102. package/dist/src/cli/commands/doctor/checks/image-providers.js +68 -0
  103. package/dist/src/cli/commands/doctor/flow.js +2 -0
  104. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  105. package/dist/src/cli/commands/models.js +1 -1
  106. package/dist/src/cli/commands/onboard.js +1 -1
  107. package/dist/src/config/index.js +2 -2
  108. package/dist/src/config/models-json.d.ts +325 -0
  109. package/dist/src/config/models-json.js +139 -4
  110. package/dist/src/extensions/sdk/index.js +1 -1
  111. package/dist/src/gateway/custom-image-providers.d.ts +89 -0
  112. package/dist/src/gateway/custom-image-providers.js +59 -0
  113. package/dist/src/gateway/heartbeat/service.js +1 -1
  114. package/dist/src/gateway/hono/routes/models.js +182 -1
  115. package/dist/src/gateway/hono/routes/workflows.js +1 -1
  116. package/dist/src/gateway/image-generation-setup.js +7 -1
  117. package/dist/src/gateway/service/config-coordinator.js +2 -0
  118. package/dist/src/gateway/service.js +2 -2
  119. package/dist/src/heartbeat/index.js +1 -1
  120. package/dist/src/media-shared/http/index.d.ts +1 -1
  121. package/dist/src/media-shared/http/index.js +2 -2
  122. package/dist/src/media-shared/http/provider-http.d.ts +2 -0
  123. package/dist/src/media-shared/http/provider-http.js +43 -4
  124. package/dist/src/providers/model-reference-auditor.d.ts +1 -0
  125. package/dist/src/providers/model-reference-auditor.js +23 -13
  126. package/package.json +2 -2
  127. package/dist/gateway/static/root/assets/agents-DZ69tC4K.js +0 -2
  128. package/dist/gateway/static/root/assets/capabilities-settings-panel-LOQyfdsM.js +0 -2
  129. package/dist/gateway/static/root/assets/capability-presets-api-XL4h7dCK.js +0 -1
  130. package/dist/gateway/static/root/assets/capability-presets-settings-panel-KPJFkt1h.js +0 -2
  131. package/dist/gateway/static/root/assets/channels-status-swr-DZZq0upi.js +0 -1
  132. package/dist/gateway/static/root/assets/index-BXtJvaQl.css +0 -1
  133. package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-fhssr2-J.js +0 -1
  134. package/dist/gateway/static/root/assets/locale-store-C_ax27uc.js +0 -1
  135. package/dist/gateway/static/root/assets/settings-advanced-gate-DR9Q2ZyF.js +0 -1
  136. package/dist/gateway/static/root/assets/skill-reload-api-da5gLCer.js +0 -1
@@ -10,8 +10,8 @@
10
10
  <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png?v=20260713-1" />
11
11
  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?v=20260713-1" />
12
12
  <link rel="manifest" href="/site.webmanifest?v=20260713-1" />
13
- <title>xopc</title>
14
- <script type="module" crossorigin src="/assets/index-BhQMnKH1.js"></script>
13
+ <title>XOPC — Turn goals into loops. Keep what matters moving.</title>
14
+ <script type="module" crossorigin src="/assets/index-B24B26og.js"></script>
15
15
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-aKtaBQYM.js">
16
16
  <link rel="modulepreload" crossorigin href="/assets/preload-helper-zJ_50EbN.js">
17
17
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-BOUWij0V.js">
@@ -24,17 +24,17 @@
24
24
  <link rel="modulepreload" crossorigin href="/assets/check-DUBLcKHA.js">
25
25
  <link rel="modulepreload" crossorigin href="/assets/circle-alert-BEjgurr2.js">
26
26
  <link rel="modulepreload" crossorigin href="/assets/middleware-Pd0e8Igk.js">
27
- <link rel="modulepreload" crossorigin href="/assets/messages-C96Zjb0V.js">
28
- <link rel="modulepreload" crossorigin href="/assets/locale-store-C_ax27uc.js">
27
+ <link rel="modulepreload" crossorigin href="/assets/messages-B1EG4gdu.js">
28
+ <link rel="modulepreload" crossorigin href="/assets/locale-store-tnRLWdx2.js">
29
29
  <link rel="modulepreload" crossorigin href="/assets/cn-JdwAhgNB.js">
30
30
  <link rel="modulepreload" crossorigin href="/assets/schemas-DgQq6Z5S.js">
31
31
  <link rel="modulepreload" crossorigin href="/assets/product-delivery-BTRT9kCS.js">
32
32
  <link rel="modulepreload" crossorigin href="/assets/config-reload-event-Bjh2zoKp.js">
33
33
  <link rel="modulepreload" crossorigin href="/assets/copy-to-clipboard-HOJ79TWv.js">
34
- <link rel="modulepreload" crossorigin href="/assets/gateway-startup-retry-ulwTJovP.js">
35
- <link rel="modulepreload" crossorigin href="/assets/activity-store-DkALQFJT.js">
36
- <link rel="modulepreload" crossorigin href="/assets/theme-store-D37XLD_C.js">
37
- <link rel="stylesheet" crossorigin href="/assets/index-BXtJvaQl.css">
34
+ <link rel="modulepreload" crossorigin href="/assets/gateway-startup-retry-qnEmj-vL.js">
35
+ <link rel="modulepreload" crossorigin href="/assets/activity-store-DjEDu38T.js">
36
+ <link rel="modulepreload" crossorigin href="/assets/theme-store-H9T49Iar.js">
37
+ <link rel="stylesheet" crossorigin href="/assets/index-DfE2j0g8.css">
38
38
  </head>
39
39
  <body>
40
40
  <div id="root"></div>
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.0.166";
2
+ var version = "0.0.167";
3
3
  //#endregion
4
4
  export { version };
@@ -0,0 +1,7 @@
1
+ import type { ImageGenerationProviderConfig, ProviderConfig } from '../../../config/models-json.js';
2
+ import type { ImageGenerationProvider } from './types.js';
3
+ export declare function buildCustomOpenAiImagesProvider(params: {
4
+ providerId: string;
5
+ provider: ProviderConfig;
6
+ imageGeneration: ImageGenerationProviderConfig;
7
+ }): ImageGenerationProvider;
@@ -0,0 +1,81 @@
1
+ import { resolveAuthProfileForProvider } from "../../../providers/auth-runtime/resolve-auth.js";
2
+ import { isProviderApiKeyConfigured } from "../../../providers/auth-runtime/is-configured.js";
3
+ import "../../../providers/auth-runtime/index.js";
4
+ import { createOpenAiImagesProvider } from "./openai-images-provider.js";
5
+ //#region src/agent/image/generation/custom-openai-images-provider.ts
6
+ function buildCustomOpenAiImagesProvider(params) {
7
+ const { providerId, provider, imageGeneration } = params;
8
+ if (!provider.baseUrl) throw new Error(`Custom image provider "${providerId}" requires baseUrl.`);
9
+ const models = imageGeneration.models.map((model) => model.id);
10
+ const modelCapabilities = Object.fromEntries(imageGeneration.models.map((model) => [model.id, model.capabilities]));
11
+ const defaultCapabilities = modelCapabilities[imageGeneration.defaultModel];
12
+ if (!defaultCapabilities) throw new Error(`Custom image provider "${providerId}" default model is not in its model catalog.`);
13
+ const auth = imageGeneration.auth;
14
+ const isConfigured = (ctx) => auth.type === "none" || isProviderApiKeyConfigured({
15
+ providerId,
16
+ cfg: ctx.cfg,
17
+ agentId: ctx.agentId
18
+ });
19
+ const executors = new Map(imageGeneration.models.map((model) => {
20
+ const executor = createOpenAiImagesProvider({
21
+ id: providerId,
22
+ label: imageGeneration.name,
23
+ defaultModel: model.id,
24
+ models: [model.id],
25
+ capabilities: model.capabilities,
26
+ isConfigured,
27
+ resolveApiKey: (req) => {
28
+ if (auth.type === "none") return null;
29
+ return resolveAuthProfileForProvider({
30
+ providerId,
31
+ cfg: req.cfg,
32
+ agentId: req.agentId,
33
+ store: req.authStore
34
+ }).apiKey;
35
+ },
36
+ resolveEndpoint: () => ({
37
+ baseUrl: provider.baseUrl,
38
+ headers: provider.headers,
39
+ generationsPath: imageGeneration.paths?.generations,
40
+ editsPath: imageGeneration.paths?.edits,
41
+ authorization: auth.type === "none" ? { kind: "none" } : auth.type === "header" ? {
42
+ kind: "header",
43
+ headerName: auth.headerName
44
+ } : { kind: "bearer" },
45
+ privateNetworkPolicy: imageGeneration.network ? {
46
+ allowPrivate: true,
47
+ allowHosts: imageGeneration.network.allowedHosts
48
+ } : {}
49
+ }),
50
+ defaultCount: model.defaults?.count,
51
+ defaultSize: model.defaults?.size
52
+ });
53
+ return [model.id, {
54
+ executor,
55
+ outputFormat: model.defaults?.outputFormat
56
+ }];
57
+ }));
58
+ return {
59
+ id: providerId,
60
+ label: imageGeneration.name,
61
+ source: "custom",
62
+ credentialMode: auth.type === "none" ? "none" : "api-key",
63
+ documentationUrl: imageGeneration.documentationUrl,
64
+ apiKeyUrl: imageGeneration.apiKeyUrl,
65
+ defaultModel: imageGeneration.defaultModel,
66
+ models,
67
+ capabilities: defaultCapabilities,
68
+ modelCapabilities,
69
+ isConfigured,
70
+ async generateImage(req) {
71
+ const selected = executors.get(req.model);
72
+ if (!selected) throw new Error(`Image model is not configured for ${providerId}: ${req.model}`);
73
+ return selected.executor.generateImage({
74
+ ...req,
75
+ outputFormat: req.outputFormat ?? selected.outputFormat
76
+ });
77
+ }
78
+ };
79
+ }
80
+ //#endregion
81
+ export { buildCustomOpenAiImagesProvider };
@@ -1,19 +1,8 @@
1
- /**
2
- * OpenAI-compatible image provider factory.
3
- *
4
- * Many image providers (OpenAI, Azure OpenAI, OpenAI-compatible gateways)
5
- * speak the same `/images/generations` and `/images/edits` REST shape.
6
- * This factory wraps that wire format on top of shared provider HTTP, normalization,
7
- * and auth-runtime so vendor modules only need to declare:
8
- * - id / models / capabilities
9
- * - baseUrl resolution
10
- * - apiKey resolution
11
- * - optional request body / response transforms
12
- *
13
- */
1
+ /** Strict implementation of the OpenAI Images REST protocol. */
2
+ import type { PrivateNetworkPolicy } from '../../../media-shared/http/index.js';
14
3
  import type { GeneratedImageAsset, ImageGenerationProvider, ImageGenerationProviderCapabilities, ImageGenerationProviderConfiguredContext, ImageGenerationRequest } from './types.js';
15
4
  /** Subset of the official OpenAI image response we map. */
16
- export interface OpenAiCompatibleImageResponse {
5
+ export interface OpenAiImagesResponse {
17
6
  data?: Array<{
18
7
  b64_json?: string;
19
8
  revised_prompt?: string;
@@ -21,7 +10,7 @@ export interface OpenAiCompatibleImageResponse {
21
10
  }>;
22
11
  [k: string]: unknown;
23
12
  }
24
- export interface OpenAiCompatibleEndpointResolution {
13
+ export interface OpenAiImagesEndpointResolution {
25
14
  /** Base URL without trailing slash, e.g. `https://api.openai.com/v1`. */
26
15
  baseUrl: string;
27
16
  /** Optional override for the generations sub-path; defaults to `/images/generations`. */
@@ -42,8 +31,10 @@ export interface OpenAiCompatibleEndpointResolution {
42
31
  kind: 'header';
43
32
  headerName: string;
44
33
  };
34
+ /** Explicit private-host allowlist for this endpoint. Omit to use the global policy. */
35
+ privateNetworkPolicy?: PrivateNetworkPolicy;
45
36
  }
46
- export interface OpenAiCompatibleImageProviderOptions {
37
+ export interface OpenAiImagesProviderOptions {
47
38
  id: string;
48
39
  label: string;
49
40
  defaultModel: string;
@@ -54,24 +45,12 @@ export interface OpenAiCompatibleImageProviderOptions {
54
45
  /** Resolve API key for one request. May return null when using OAuth/header auth. */
55
46
  resolveApiKey: (req: ImageGenerationRequest) => string | null | undefined;
56
47
  /** Resolve endpoint info per-request (region, baseUrl override, Azure deployment, …). */
57
- resolveEndpoint: (req: ImageGenerationRequest) => OpenAiCompatibleEndpointResolution;
48
+ resolveEndpoint: (req: ImageGenerationRequest) => OpenAiImagesEndpointResolution;
58
49
  /** Default per-call timeout. Combined with provider-level config in provider-http. */
59
50
  defaultTimeoutMs?: number;
60
- /**
61
- * Hook to enrich the JSON body sent to `/images/generations`. Mutate the
62
- * returned object freely; the factory keeps standard fields stable.
63
- */
64
- buildGenerateRequestBody?: (req: ImageGenerationRequest, base: Record<string, unknown>) => Record<string, unknown>;
65
- /** Hook to enrich the multipart form for `/images/edits`. */
66
- buildEditFormFields?: (req: ImageGenerationRequest, fields: Record<string, string>) => Record<string, string>;
67
- /**
68
- * Optional response mapper. Default: `mapDefaultOpenAiResponse`. Vendor
69
- * implementations can override to handle non-standard response shapes.
70
- */
71
- mapResponse?: (raw: unknown, req: ImageGenerationRequest) => GeneratedImageAsset[];
72
51
  /** Default count clamp. Provider may also enforce via `capabilities.generate.maxCount`. */
73
52
  defaultCount?: number;
74
53
  defaultSize?: string;
75
54
  }
76
- export declare function createOpenAiCompatibleImageProvider(options: OpenAiCompatibleImageProviderOptions): ImageGenerationProvider;
77
- export declare function mapDefaultOpenAiResponse(raw: unknown, req: ImageGenerationRequest): GeneratedImageAsset[];
55
+ export declare function createOpenAiImagesProvider(options: OpenAiImagesProviderOptions): ImageGenerationProvider;
56
+ export declare function mapOpenAiImagesResponse(raw: unknown, req: ImageGenerationRequest): GeneratedImageAsset[];
@@ -4,14 +4,17 @@ import { imageAssetFromBase64, imageFileExtensionForMimeType } from "./image-ass
4
4
  import { pickTimeoutMsOrFallback } from "../../../media-shared/http/deadline.js";
5
5
  import { privateNetworkPolicyToSsrfGuardOptions } from "../../../media-shared/http/private-network.js";
6
6
  import { resolveProviderHttpRequestConfig } from "../../../media-shared/http/resolve-provider-http-request-config.js";
7
- import { postJsonRequest, postMultipartRequest } from "../../../media-shared/http/provider-http.js";
7
+ import { postJsonRequest, postMultipartRequest, readJsonResponseLimited } from "../../../media-shared/http/provider-http.js";
8
8
  import "../../../media-shared/http/index.js";
9
- //#region src/agent/image/generation/openai-compatible-image-provider.ts
9
+ //#region src/agent/image/generation/openai-images-provider.ts
10
10
  init_logger();
11
- const log = createLogger("ImageGen:OpenAICompat");
11
+ const log = createLogger("ImageGen:OpenAIImages");
12
12
  const DEFAULT_OUTPUT_MIME = "image/png";
13
13
  const DEFAULT_TIMEOUT_MS = 12e4;
14
- function createOpenAiCompatibleImageProvider(options) {
14
+ const MAX_RESPONSE_BYTES = 64 * 1024 * 1024;
15
+ const MAX_IMAGE_BYTES = 32 * 1024 * 1024;
16
+ const MAX_TOTAL_IMAGE_BYTES = 64 * 1024 * 1024;
17
+ function createOpenAiImagesProvider(options) {
15
18
  const defaultMaxCount = options.capabilities.generate?.maxCount ?? 4;
16
19
  const defaultCount = options.defaultCount ?? 1;
17
20
  const defaultSize = options.defaultSize ?? "1024x1024";
@@ -39,6 +42,7 @@ function createOpenAiCompatibleImageProvider(options) {
39
42
  endpoint,
40
43
  apiKey
41
44
  });
45
+ const ssrfGuardOptions = privateNetworkPolicyToSsrfGuardOptions(endpoint.privateNetworkPolicy);
42
46
  const isEdit = (req.inputImages?.length ?? 0) > 0;
43
47
  const count = clampCount(req.count, defaultCount, defaultMaxCount);
44
48
  const size = req.size?.trim() || defaultSize;
@@ -49,8 +53,8 @@ function createOpenAiCompatibleImageProvider(options) {
49
53
  phase: isEdit ? "edit_request" : "generate_request",
50
54
  count,
51
55
  size
52
- }, `OpenAI-compatible ${isEdit ? "edit" : "generate"} request`);
53
- const responseJson = isEdit ? await postEditRequest({
56
+ }, `OpenAI Images ${isEdit ? "edit" : "generate"} request`);
57
+ const mapped = mapOpenAiImagesResponse(isEdit ? await postEditRequest({
54
58
  providerId: options.id,
55
59
  req,
56
60
  url,
@@ -58,7 +62,7 @@ function createOpenAiCompatibleImageProvider(options) {
58
62
  count,
59
63
  size,
60
64
  httpDefaults,
61
- buildEditFormFields: options.buildEditFormFields
65
+ ssrfGuardOptions
62
66
  }) : await postGenerateRequest({
63
67
  providerId: options.id,
64
68
  req,
@@ -67,9 +71,8 @@ function createOpenAiCompatibleImageProvider(options) {
67
71
  count,
68
72
  size,
69
73
  httpDefaults,
70
- buildGenerateRequestBody: options.buildGenerateRequestBody
71
- });
72
- const mapped = (options.mapResponse ?? mapDefaultOpenAiResponse)(responseJson, req);
74
+ ssrfGuardOptions
75
+ }), req);
73
76
  if (mapped.length === 0) throw new Error(`${options.id} returned no images.`);
74
77
  return {
75
78
  images: mapped,
@@ -79,24 +82,23 @@ function createOpenAiCompatibleImageProvider(options) {
79
82
  };
80
83
  }
81
84
  async function postGenerateRequest(params) {
82
- const baseBody = {
85
+ const body = {
83
86
  model: params.req.model,
84
87
  prompt: params.req.prompt,
85
88
  n: params.count,
86
89
  size: params.size,
87
90
  response_format: "b64_json"
88
91
  };
89
- applyOpenAiOptions(baseBody, params.req);
90
- const body = params.buildGenerateRequestBody ? params.buildGenerateRequestBody(params.req, baseBody) : baseBody;
92
+ applyOpenAiOptions(body, params.req);
91
93
  const timeoutMs = pickTimeoutMsOrFallback(params.req.timeoutMs, params.httpDefaults.timeoutMs, DEFAULT_TIMEOUT_MS);
92
- return await (await postJsonRequest(params.url, {
94
+ return await readJsonResponseLimited(await postJsonRequest(params.url, {
93
95
  label: params.providerId,
94
96
  timeoutMs,
95
97
  signal: params.req.signal,
96
98
  body,
97
99
  headers: params.headers,
98
- ...privateNetworkPolicyToSsrfGuardOptions()
99
- })).json();
100
+ ...params.ssrfGuardOptions
101
+ }), MAX_RESPONSE_BYTES);
100
102
  }
101
103
  async function postEditRequest(params) {
102
104
  const inputImages = params.req.inputImages ?? [];
@@ -107,9 +109,8 @@ async function postEditRequest(params) {
107
109
  size: params.size,
108
110
  response_format: "b64_json"
109
111
  };
110
- const merged = params.buildEditFormFields ? params.buildEditFormFields(params.req, fields) : fields;
111
112
  const form = new FormData();
112
- for (const [k, v] of Object.entries(merged)) if (typeof v === "string") form.append(k, v);
113
+ for (const [k, v] of Object.entries(fields)) if (typeof v === "string") form.append(k, v);
113
114
  for (let idx = 0; idx < inputImages.length; idx++) {
114
115
  const img = inputImages[idx];
115
116
  const field = idx === 0 ? "image" : `image[${idx}]`;
@@ -121,24 +122,29 @@ async function postEditRequest(params) {
121
122
  const headers = new Headers(params.headers);
122
123
  headers.delete("content-type");
123
124
  const timeoutMs = pickTimeoutMsOrFallback(params.req.timeoutMs, params.httpDefaults.timeoutMs, DEFAULT_TIMEOUT_MS);
124
- return await (await postMultipartRequest(params.url, {
125
+ return await readJsonResponseLimited(await postMultipartRequest(params.url, {
125
126
  label: params.providerId,
126
127
  timeoutMs,
127
128
  signal: params.req.signal,
128
129
  body: form,
129
130
  headers,
130
- ...privateNetworkPolicyToSsrfGuardOptions()
131
- })).json();
131
+ ...params.ssrfGuardOptions
132
+ }), MAX_RESPONSE_BYTES);
132
133
  }
133
- function mapDefaultOpenAiResponse(raw, req) {
134
+ function mapOpenAiImagesResponse(raw, req) {
134
135
  if (!raw || typeof raw !== "object") return [];
135
136
  const data = raw.data;
136
137
  if (!Array.isArray(data)) return [];
137
138
  const ext = imageFileExtensionForMimeType(req.outputFormat ? `image/${req.outputFormat}` : DEFAULT_OUTPUT_MIME);
138
139
  const out = [];
140
+ let totalBytes = 0;
139
141
  data.forEach((entry, index) => {
140
142
  const b64 = entry?.b64_json;
141
143
  if (typeof b64 !== "string" || b64.length === 0) return;
144
+ const estimatedBytes = Math.ceil(b64.replace(/\s+/g, "").length * 3 / 4);
145
+ if (estimatedBytes > MAX_IMAGE_BYTES) throw new Error(`OpenAI Images response image exceeds ${MAX_IMAGE_BYTES} bytes.`);
146
+ totalBytes += estimatedBytes;
147
+ if (totalBytes > MAX_TOTAL_IMAGE_BYTES) throw new Error(`OpenAI Images response images exceed ${MAX_TOTAL_IMAGE_BYTES} bytes total.`);
142
148
  const asset = imageAssetFromBase64({
143
149
  base64: b64,
144
150
  mimeType: req.outputFormat ? `image/${req.outputFormat}` : void 0,
@@ -155,12 +161,19 @@ function applyAuthorizationHeader(params) {
155
161
  const auth = params.endpoint.authorization ?? { kind: "bearer" };
156
162
  if (auth.kind === "none") return;
157
163
  if (!params.apiKey) return;
164
+ assertHttpHeaderValue(params.apiKey, "API key");
158
165
  if (auth.kind === "bearer") {
159
166
  params.headers["authorization"] = `Bearer ${params.apiKey}`;
160
167
  return;
161
168
  }
162
169
  if (auth.kind === "header") params.headers[auth.headerName.toLowerCase()] = params.apiKey;
163
170
  }
171
+ function assertHttpHeaderValue(value, label) {
172
+ for (let index = 0; index < value.length; index++) {
173
+ const code = value.charCodeAt(index);
174
+ if (code > 255 || code < 32 && code !== 9 || code === 127) throw new Error(`${label} contains characters that cannot be sent in an HTTP header.`);
175
+ }
176
+ }
164
177
  function applyOpenAiOptions(body, req) {
165
178
  if (req.quality) body.quality = req.quality;
166
179
  if (req.outputFormat) body.output_format = req.outputFormat;
@@ -186,4 +199,4 @@ function joinPath(baseUrl, subPath) {
186
199
  return `${baseUrl.replace(/\/+$/, "")}${subPath.startsWith("/") ? subPath : `/${subPath}`}`;
187
200
  }
188
201
  //#endregion
189
- export { createOpenAiCompatibleImageProvider, mapDefaultOpenAiResponse };
202
+ export { createOpenAiImagesProvider, mapOpenAiImagesResponse };
@@ -1,4 +1,6 @@
1
+ import type { ModelsJsonConfig } from '../../../config/models-json.js';
1
2
  import type { ImageGenerationProvider, ImageGenerationProviderSummary } from './types.js';
3
+ export declare function reloadImageGenerationProviders(config?: ModelsJsonConfig): void;
2
4
  export declare function getImageGenerationProvider(providerId: string): ImageGenerationProvider | undefined;
3
5
  export declare function listImageGenerationProviders(): ImageGenerationProvider[];
4
6
  export declare function listImageGenerationProvidersSummary(): ImageGenerationProviderSummary[];
@@ -1,31 +1,77 @@
1
+ import { createLogger } from "../../../utils/logger/index.js";
2
+ import { init_logger } from "../../../utils/logger.js";
3
+ import { init_models_json, loadModelsJson } from "../../../config/models-json.js";
4
+ import { buildCustomOpenAiImagesProvider } from "./custom-openai-images-provider.js";
1
5
  import { buildDashScopeImageGenerationProvider } from "./providers/dashscope.js";
2
6
  import { buildFalImageGenerationProvider } from "./providers/fal.js";
3
7
  import { buildGoogleImageGenerationProvider } from "./providers/google.js";
4
8
  import { buildMinimaxImageGenerationProvider } from "./providers/minimax.js";
5
9
  import { buildOpenAIImageGenerationProvider } from "./providers/openai.js";
6
10
  //#region src/agent/image/generation/provider-registry.ts
7
- const providers = [
11
+ init_models_json();
12
+ init_logger();
13
+ const log = createLogger("ImageGenerationRegistry");
14
+ const builtInProviders = [
8
15
  buildOpenAIImageGenerationProvider(),
9
16
  buildDashScopeImageGenerationProvider(),
10
17
  buildMinimaxImageGenerationProvider(),
11
18
  buildGoogleImageGenerationProvider(),
12
19
  buildFalImageGenerationProvider()
13
20
  ];
14
- const providersById = new Map(providers.map((provider) => [provider.id, provider]));
21
+ let providers = [...builtInProviders];
22
+ let providersById = new Map(providers.map((provider) => [provider.id, provider]));
23
+ let customProvidersLoaded = false;
24
+ function ensureCustomProvidersLoaded() {
25
+ if (!customProvidersLoaded) reloadImageGenerationProviders();
26
+ }
27
+ function reloadImageGenerationProviders(config) {
28
+ let resolved = config;
29
+ if (!resolved) {
30
+ const loaded = loadModelsJson();
31
+ if (loaded.error) {
32
+ log.warn({ errorMessage: loaded.error }, `Custom image providers not loaded: ${loaded.error}`);
33
+ providers = [...builtInProviders];
34
+ providersById = new Map(providers.map((provider) => [provider.id, provider]));
35
+ customProvidersLoaded = true;
36
+ return;
37
+ }
38
+ resolved = loaded.config;
39
+ }
40
+ const builtInIds = new Set(builtInProviders.map((provider) => provider.id));
41
+ const customEntries = Object.entries(resolved.providers).filter((entry) => Boolean(entry[1].imageGeneration));
42
+ const conflictingId = customEntries.find(([providerId]) => builtInIds.has(providerId))?.[0];
43
+ if (conflictingId) throw new Error(`Custom image generation cannot override built-in provider "${conflictingId}".`);
44
+ const customProviders = customEntries.map(([providerId, provider]) => buildCustomOpenAiImagesProvider({
45
+ providerId,
46
+ provider,
47
+ imageGeneration: provider.imageGeneration
48
+ }));
49
+ providers = [...builtInProviders, ...customProviders];
50
+ providersById = new Map(providers.map((provider) => [provider.id, provider]));
51
+ customProvidersLoaded = true;
52
+ }
15
53
  function getImageGenerationProvider(providerId) {
54
+ ensureCustomProvidersLoaded();
16
55
  return providersById.get(providerId);
17
56
  }
18
57
  function listImageGenerationProviders() {
58
+ ensureCustomProvidersLoaded();
19
59
  return [...providers];
20
60
  }
21
61
  function listImageGenerationProvidersSummary() {
62
+ ensureCustomProvidersLoaded();
22
63
  return providers.map((provider) => ({
23
64
  id: provider.id,
24
65
  label: provider.label,
66
+ source: provider.source ?? "builtin",
67
+ credentialMode: provider.credentialMode ?? "api-key",
68
+ ...provider.documentationUrl ? { documentationUrl: provider.documentationUrl } : {},
69
+ ...provider.apiKeyUrl ? { apiKeyUrl: provider.apiKeyUrl } : {},
25
70
  defaultModel: provider.defaultModel,
26
71
  models: [...provider.models],
27
- capabilities: provider.capabilities
72
+ capabilities: provider.capabilities,
73
+ ...provider.modelCapabilities ? { modelCapabilities: structuredClone(provider.modelCapabilities) } : {}
28
74
  }));
29
75
  }
30
76
  //#endregion
31
- export { getImageGenerationProvider, listImageGenerationProviders, listImageGenerationProvidersSummary };
77
+ export { getImageGenerationProvider, listImageGenerationProviders, listImageGenerationProvidersSummary, reloadImageGenerationProviders };
@@ -1,7 +1,7 @@
1
1
  import { resolveAuthProfileForProvider } from "../../../../providers/auth-runtime/resolve-auth.js";
2
2
  import { isProviderApiKeyConfigured } from "../../../../providers/auth-runtime/is-configured.js";
3
3
  import "../../../../providers/auth-runtime/index.js";
4
- import { createOpenAiCompatibleImageProvider } from "../openai-compatible-image-provider.js";
4
+ import { createOpenAiImagesProvider } from "../openai-images-provider.js";
5
5
  //#region src/agent/image/generation/providers/openai.ts
6
6
  /**
7
7
  * Built-in OpenAI image-generation provider.
@@ -93,7 +93,7 @@ function buildAzureEndpoint(azure) {
93
93
  };
94
94
  }
95
95
  function buildOpenAIImageGenerationProvider() {
96
- return createOpenAiCompatibleImageProvider({
96
+ return createOpenAiImagesProvider({
97
97
  id: "openai",
98
98
  label: "OpenAI",
99
99
  defaultModel: OPENAI_DEFAULT_IMAGE_MODEL,
@@ -51,8 +51,12 @@ async function generateImage(params, deps = {}) {
51
51
  continue;
52
52
  }
53
53
  try {
54
+ const modelCapabilities = provider.modelCapabilities?.[candidate.model];
54
55
  const sanitized = resolveImageGenerationOverrides({
55
- provider,
56
+ provider: modelCapabilities ? {
57
+ ...provider,
58
+ capabilities: modelCapabilities
59
+ } : provider,
56
60
  size: params.size,
57
61
  aspectRatio: params.aspectRatio,
58
62
  resolution: params.resolution,
@@ -51,18 +51,29 @@ export interface ImageGenerationProviderConfiguredContext {
51
51
  export interface ImageGenerationProvider {
52
52
  id: string;
53
53
  label: string;
54
+ source?: 'builtin' | 'custom';
55
+ credentialMode?: 'api-key' | 'none';
56
+ documentationUrl?: string;
57
+ apiKeyUrl?: string;
54
58
  defaultModel: string;
55
59
  models: string[];
56
60
  capabilities: ImageGenerationProviderCapabilities;
61
+ /** Per-model capability declarations when models on one endpoint differ. */
62
+ modelCapabilities?: Record<string, ImageGenerationProviderCapabilities>;
57
63
  isConfigured(ctx: ImageGenerationProviderConfiguredContext): boolean;
58
64
  generateImage(req: ImageGenerationRequest): Promise<ImageGenerationResult>;
59
65
  }
60
66
  export interface ImageGenerationProviderSummary {
61
67
  id: string;
62
68
  label: string;
69
+ source: 'builtin' | 'custom';
70
+ credentialMode: 'api-key' | 'none';
71
+ documentationUrl?: string;
72
+ apiKeyUrl?: string;
63
73
  defaultModel: string;
64
74
  models: string[];
65
75
  capabilities: ImageGenerationProviderCapabilities;
76
+ modelCapabilities?: Record<string, ImageGenerationProviderCapabilities>;
66
77
  }
67
78
  export interface ImageGenerationOpenAIOptions {
68
79
  background?: ImageGenerationBackground;
@@ -1,8 +1,8 @@
1
1
  import { buildChannelCatalogForConfig, init_channel_catalog_service } from "../../channels/catalog/channel-catalog-service.js";
2
2
  import { init_loader, loadConfig, saveConfig } from "../../config/loader.js";
3
- import { formatExamples, register } from "../registry.js";
4
3
  import { listChannelPairingState } from "../../channels/pairing/pairing-service.js";
5
4
  import { approveChannelPairingFromCli } from "../../channels/pairing/approve-cli.js";
5
+ import { formatExamples, register } from "../registry.js";
6
6
  import { colors } from "../utils/colors.js";
7
7
  import { Command } from "commander";
8
8
  //#region src/cli/commands/channels.ts
@@ -1,8 +1,8 @@
1
1
  import { init_loader, loadConfig, saveConfig } from "../../config/loader.js";
2
2
  import { getStoreConnectorInstallPlan, installStoreConnector, listStoreConnectors } from "../../capabilities/store-connector.js";
3
3
  import { register } from "../registry.js";
4
- import { Command } from "commander";
5
4
  import { confirm } from "@inquirer/prompts";
5
+ import { Command } from "commander";
6
6
  //#region src/cli/commands/connectors.ts
7
7
  init_loader();
8
8
  function parsePairs(values, option) {
@@ -0,0 +1,2 @@
1
+ import type { CheckResult, DoctorContext } from '../types.js';
2
+ export declare function checkImageProviders(_ctx: DoctorContext): Promise<CheckResult>;
@@ -0,0 +1,68 @@
1
+ import { resolveModelsJsonPath } from "../../../../config/paths.js";
2
+ import { init_models_json, loadModelsJson } from "../../../../config/models-json.js";
3
+ import { CredentialResolver, init_credentials } from "../../../../auth/credentials.js";
4
+ import { classifyHost } from "../../../../media-shared/http/private-network.js";
5
+ import "../../../../media-shared/http/index.js";
6
+ import { existsSync } from "node:fs";
7
+ //#region src/cli/commands/doctor/checks/image-providers.ts
8
+ init_credentials();
9
+ init_models_json();
10
+ async function checkImageProviders(_ctx) {
11
+ const path = resolveModelsJsonPath();
12
+ if (!existsSync(path)) return {
13
+ id: "image-providers",
14
+ label: "Image providers",
15
+ status: "skip",
16
+ message: "No models.json file; custom image providers are not configured.",
17
+ hints: []
18
+ };
19
+ const loaded = loadModelsJson(path);
20
+ if (loaded.error) return {
21
+ id: "image-providers",
22
+ label: "Image providers",
23
+ status: "fail",
24
+ message: "models.json image provider configuration is invalid.",
25
+ hints: [loaded.error, path]
26
+ };
27
+ const entries = Object.entries(loaded.config.providers).filter((entry) => Boolean(entry[1].imageGeneration));
28
+ if (entries.length === 0) return {
29
+ id: "image-providers",
30
+ label: "Image providers",
31
+ status: "skip",
32
+ message: "No custom image providers are configured.",
33
+ hints: [path]
34
+ };
35
+ const blockedEndpoints = [];
36
+ const missingCredentials = [];
37
+ const resolver = new CredentialResolver();
38
+ for (const [providerId, provider] of entries) {
39
+ const hostname = new URL(provider.baseUrl).hostname.toLowerCase();
40
+ const hostClass = classifyHost(hostname);
41
+ const allowedHosts = (provider.imageGeneration.network?.allowedHosts ?? []).map((entry) => entry.toLowerCase());
42
+ if (hostClass !== "public" && !allowedHosts.includes(hostname)) blockedEndpoints.push(`${providerId}: ${hostname}`);
43
+ if (provider.imageGeneration.auth.type !== "none" && !await resolver.resolveApiKey(providerId)) missingCredentials.push(providerId);
44
+ }
45
+ if (blockedEndpoints.length > 0) return {
46
+ id: "image-providers",
47
+ label: "Image providers",
48
+ status: "fail",
49
+ message: `${blockedEndpoints.length} custom image provider endpoint(s) are blocked by private-network policy.`,
50
+ hints: [...blockedEndpoints, "Add each exact private hostname to imageGeneration.network.allowedHosts only when you trust it."]
51
+ };
52
+ if (missingCredentials.length > 0) return {
53
+ id: "image-providers",
54
+ label: "Image providers",
55
+ status: "warn",
56
+ message: `${missingCredentials.length} custom image provider(s) need an API key.`,
57
+ hints: missingCredentials.map((providerId) => `Configure a credential for ${providerId}.`)
58
+ };
59
+ return {
60
+ id: "image-providers",
61
+ label: "Image providers",
62
+ status: "pass",
63
+ message: `${entries.length} custom image provider(s) validate and are ready.`,
64
+ hints: [path]
65
+ };
66
+ }
67
+ //#endregion
68
+ export { checkImageProviders };
@@ -14,6 +14,7 @@ import { checkWorkspaceStatus } from "./checks/workspace-status.js";
14
14
  import { checkVersionUpdate } from "./checks/version-check.js";
15
15
  import { checkChannelPlugins } from "./checks/channel-plugins.js";
16
16
  import { checkMigrations } from "./checks/migrations.js";
17
+ import { checkImageProviders } from "./checks/image-providers.js";
17
18
  //#region src/cli/commands/doctor/flow.ts
18
19
  const DOCTOR_CHECKS = [
19
20
  checkVersionUpdate,
@@ -23,6 +24,7 @@ const DOCTOR_CHECKS = [
23
24
  checkStateIntegrity,
24
25
  checkDatabaseSchema,
25
26
  checkProviderAuth,
27
+ checkImageProviders,
26
28
  checkChannelConfig,
27
29
  checkChannelPairingPending,
28
30
  checkSecurityAudit,
@@ -12,8 +12,8 @@ import { join, resolve } from "node:path";
12
12
  import { existsSync, readFileSync, rmSync, statSync } from "node:fs";
13
13
  import { execSync } from "node:child_process";
14
14
  import { createHash } from "node:crypto";
15
- import { Command } from "commander";
16
15
  import { confirm } from "@inquirer/prompts";
16
+ import { Command } from "commander";
17
17
  import semver from "semver";
18
18
  //#region src/cli/commands/extension-marketplace.ts
19
19
  init_loader();
@@ -8,8 +8,8 @@ import "../../config/index.js";
8
8
  import { prepareUpdateGlobalDefaults } from "../../gateway/global-defaults-admin.js";
9
9
  import { colors } from "../utils/colors.js";
10
10
  import { runCliOAuthLogin } from "../utils/oauth-login.js";
11
- import { Command } from "commander";
12
11
  import { input } from "@inquirer/prompts";
12
+ import { Command } from "commander";
13
13
  //#region src/cli/commands/models.ts
14
14
  init_credentials();
15
15
  init_schema();