@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
@@ -0,0 +1,89 @@
1
+ import { z } from 'zod';
2
+ import { type ModelsJsonConfig } from '../config/models-json.js';
3
+ export declare const CustomImageProviderInputSchema: z.ZodObject<{
4
+ baseUrl: z.ZodString;
5
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
6
+ imageGeneration: z.ZodObject<{
7
+ api: z.ZodLiteral<"openai-images">;
8
+ name: z.ZodString;
9
+ documentationUrl: z.ZodOptional<z.ZodString>;
10
+ apiKeyUrl: z.ZodOptional<z.ZodString>;
11
+ defaultModel: z.ZodString;
12
+ auth: z.ZodDiscriminatedUnion<[z.ZodObject<{
13
+ type: z.ZodLiteral<"bearer">;
14
+ }, z.core.$strict>, z.ZodObject<{
15
+ type: z.ZodLiteral<"header">;
16
+ headerName: z.ZodString;
17
+ }, z.core.$strict>, z.ZodObject<{
18
+ type: z.ZodLiteral<"none">;
19
+ }, z.core.$strict>], "type">;
20
+ paths: z.ZodOptional<z.ZodObject<{
21
+ generations: z.ZodOptional<z.ZodString>;
22
+ edits: z.ZodOptional<z.ZodString>;
23
+ }, z.core.$strict>>;
24
+ network: z.ZodOptional<z.ZodObject<{
25
+ allowedHosts: z.ZodArray<z.ZodString>;
26
+ }, z.core.$strict>>;
27
+ models: z.ZodArray<z.ZodObject<{
28
+ id: z.ZodString;
29
+ name: z.ZodOptional<z.ZodString>;
30
+ capabilities: z.ZodObject<{
31
+ generate: z.ZodOptional<z.ZodObject<{
32
+ maxCount: z.ZodOptional<z.ZodNumber>;
33
+ supportsSize: z.ZodOptional<z.ZodBoolean>;
34
+ supportsAspectRatio: z.ZodOptional<z.ZodBoolean>;
35
+ supportsResolution: z.ZodOptional<z.ZodBoolean>;
36
+ }, z.core.$strict>>;
37
+ edit: z.ZodOptional<z.ZodObject<{
38
+ enabled: z.ZodBoolean;
39
+ maxInputImages: z.ZodOptional<z.ZodNumber>;
40
+ supportsSize: z.ZodOptional<z.ZodBoolean>;
41
+ supportsAspectRatio: z.ZodOptional<z.ZodBoolean>;
42
+ }, z.core.$strict>>;
43
+ geometry: z.ZodOptional<z.ZodObject<{
44
+ sizes: z.ZodOptional<z.ZodArray<z.ZodString>>;
45
+ aspectRatios: z.ZodOptional<z.ZodArray<z.ZodString>>;
46
+ resolutions: z.ZodOptional<z.ZodArray<z.ZodEnum<{
47
+ "1K": "1K";
48
+ "2K": "2K";
49
+ "4K": "4K";
50
+ }>>>;
51
+ }, z.core.$strict>>;
52
+ output: z.ZodOptional<z.ZodObject<{
53
+ qualities: z.ZodOptional<z.ZodArray<z.ZodEnum<{
54
+ low: "low";
55
+ high: "high";
56
+ auto: "auto";
57
+ medium: "medium";
58
+ }>>>;
59
+ formats: z.ZodOptional<z.ZodArray<z.ZodEnum<{
60
+ png: "png";
61
+ jpeg: "jpeg";
62
+ webp: "webp";
63
+ }>>>;
64
+ backgrounds: z.ZodOptional<z.ZodArray<z.ZodEnum<{
65
+ auto: "auto";
66
+ transparent: "transparent";
67
+ opaque: "opaque";
68
+ }>>>;
69
+ }, z.core.$strict>>;
70
+ }, z.core.$strict>;
71
+ defaults: z.ZodOptional<z.ZodObject<{
72
+ count: z.ZodOptional<z.ZodNumber>;
73
+ size: z.ZodOptional<z.ZodString>;
74
+ outputFormat: z.ZodOptional<z.ZodEnum<{
75
+ png: "png";
76
+ jpeg: "jpeg";
77
+ webp: "webp";
78
+ }>>;
79
+ }, z.core.$strict>>;
80
+ }, z.core.$strict>>;
81
+ }, z.core.$strict>;
82
+ }, z.core.$strict>;
83
+ export type CustomImageProviderInput = z.infer<typeof CustomImageProviderInputSchema>;
84
+ export interface CustomImageProviderRecord extends CustomImageProviderInput {
85
+ providerId: string;
86
+ }
87
+ export declare function listCustomImageProviders(config: ModelsJsonConfig): CustomImageProviderRecord[];
88
+ export declare function upsertCustomImageProvider(config: ModelsJsonConfig, providerIdRaw: string, input: unknown): ModelsJsonConfig;
89
+ export declare function deleteCustomImageProvider(config: ModelsJsonConfig, providerIdRaw: string): ModelsJsonConfig;
@@ -0,0 +1,59 @@
1
+ import { ImageGenerationProviderSchema, init_models_json, validateModelsConfig } from "../config/models-json.js";
2
+ import { z } from "zod";
3
+ //#region src/gateway/custom-image-providers.ts
4
+ init_models_json();
5
+ const PROVIDER_ID_REGEX = /^[a-z0-9]([a-z0-9-_]*[a-z0-9])?$/;
6
+ const CustomImageProviderInputSchema = z.object({
7
+ baseUrl: z.string().url(),
8
+ headers: z.record(z.string(), z.string()).optional(),
9
+ imageGeneration: ImageGenerationProviderSchema
10
+ }).strict();
11
+ function listCustomImageProviders(config) {
12
+ return Object.entries(config.providers).flatMap(([providerId, provider]) => {
13
+ if (!provider.imageGeneration) return [];
14
+ return [{
15
+ providerId,
16
+ baseUrl: provider.baseUrl,
17
+ ...provider.headers ? { headers: { ...provider.headers } } : {},
18
+ imageGeneration: structuredClone(provider.imageGeneration)
19
+ }];
20
+ });
21
+ }
22
+ function upsertCustomImageProvider(config, providerIdRaw, input) {
23
+ const providerId = normalizeProviderId(providerIdRaw);
24
+ const parsed = CustomImageProviderInputSchema.safeParse(input);
25
+ if (!parsed.success) throw new Error(parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; "));
26
+ const { headers: _currentHeaders, ...currentWithoutHeaders } = config.providers[providerId] ?? {};
27
+ const next = structuredClone(config);
28
+ next.providers[providerId] = {
29
+ ...currentWithoutHeaders,
30
+ baseUrl: parsed.data.baseUrl,
31
+ ...parsed.data.headers ? { headers: parsed.data.headers } : {},
32
+ imageGeneration: parsed.data.imageGeneration
33
+ };
34
+ assertValid(next);
35
+ return next;
36
+ }
37
+ function deleteCustomImageProvider(config, providerIdRaw) {
38
+ const providerId = normalizeProviderId(providerIdRaw);
39
+ const current = config.providers[providerId];
40
+ if (!current?.imageGeneration) throw new Error(`Custom image provider not found: ${providerId}`);
41
+ const next = structuredClone(config);
42
+ if (Boolean(current.api || current.apiKey || current.authHeader !== void 0 || current.models?.length || current.modelOverrides && Object.keys(current.modelOverrides).length > 0 || current.modelDiscovery)) {
43
+ const { imageGeneration: _removed, ...remaining } = current;
44
+ next.providers[providerId] = remaining;
45
+ } else delete next.providers[providerId];
46
+ assertValid(next);
47
+ return next;
48
+ }
49
+ function normalizeProviderId(value) {
50
+ const providerId = value.trim();
51
+ if (!PROVIDER_ID_REGEX.test(providerId)) throw new Error("Provider ID must start/end with alphanumeric and contain only lowercase letters, numbers, hyphens, and underscores");
52
+ return providerId;
53
+ }
54
+ function assertValid(config) {
55
+ const validation = validateModelsConfig(config);
56
+ if (!validation.valid) throw new Error(validation.errors.filter((entry) => entry.severity === "error").map((entry) => `${entry.path}: ${entry.message}`).join("; "));
57
+ }
58
+ //#endregion
59
+ export { CustomImageProviderInputSchema, deleteCustomImageProvider, listCustomImageProviders, upsertCustomImageProvider };
@@ -1,9 +1,9 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
3
  import { shouldSilence, stripHeartbeatToken } from "../../heartbeat/tokens.js";
4
+ import { appendCronEventLines } from "../../heartbeat/event-prompt.js";
4
5
  import { isWithinActiveHours } from "../../heartbeat/active-hours.js";
5
6
  import { isHeartbeatContentEmpty } from "../../heartbeat/content-check.js";
6
- import { appendCronEventLines } from "../../heartbeat/event-prompt.js";
7
7
  import { createHeartbeatWake } from "../../heartbeat/wake.js";
8
8
  import { resolveHeartbeatMdPath } from "../workspace-heartbeat-path.js";
9
9
  import { readFile } from "fs/promises";
@@ -7,7 +7,9 @@ import { CredentialResolver, init_credentials } from "../../../auth/credentials.
7
7
  import "../../../providers/constants.js";
8
8
  import { getProviderRegistry, init_plugin_registry } from "../../../providers/plugin-registry.js";
9
9
  import { PROVIDER_META, getAllModels, getAllProviders, getAvailableModels, getProviderAuthState, init_providers, isProviderConfigured } from "../../../providers/index.js";
10
+ import { getImageGenerationProvider, reloadImageGenerationProviders } from "../../../agent/image/generation/provider-registry.js";
10
11
  import { SessionConfigStore } from "../../../session/config-store.js";
12
+ import { deleteCustomImageProvider, listCustomImageProviders, upsertCustomImageProvider } from "../../custom-image-providers.js";
11
13
  import { getDomesticProviderPreset, getDomesticProviderPresetIds } from "../../../providers/domestic-presets.js";
12
14
  import { getOnboardingFeaturedProviders, getProviderHint, getRecommendedModelsForProvider, isRecommendedModel, sortModelsForPicker, sortProvidersForPicker } from "../../../providers/presentation.js";
13
15
  import { getAgentImageGenerationConfig, getImageGenerationCatalog, prepareImageGenerationSetup, verifyImageGenerationCredential } from "../../image-generation-setup.js";
@@ -119,6 +121,7 @@ function registerModelsRoutes(authenticated, deps) {
119
121
  }, 400);
120
122
  const registry = getModelRegistry();
121
123
  registry.refresh();
124
+ reloadImageGenerationProviders(config);
122
125
  service.emit("models-json.updated", { modelCount: registry.getAll().length });
123
126
  return c.json({
124
127
  ok: true,
@@ -131,6 +134,7 @@ function registerModelsRoutes(authenticated, deps) {
131
134
  authenticated.post("/api/models-json/reload", async (c) => {
132
135
  const registry = getModelRegistry();
133
136
  registry.refresh();
137
+ reloadImageGenerationProviders();
134
138
  const error = registry.getError();
135
139
  const models = registry.getAll();
136
140
  service.emit("models-json.reloaded", {
@@ -239,6 +243,182 @@ function registerModelsRoutes(authenticated, deps) {
239
243
  payload: { providers: getImageGenerationCatalog(service.currentConfig) }
240
244
  });
241
245
  });
246
+ authenticated.get("/api/image-generation/custom-providers", (c) => {
247
+ const loaded = loadModelsJson(resolveModelsJsonPath());
248
+ if (loaded.error) return c.json({
249
+ ok: false,
250
+ error: { message: loaded.error }
251
+ }, 409);
252
+ return c.json({
253
+ ok: true,
254
+ payload: { providers: listCustomImageProviders(loaded.config) }
255
+ });
256
+ });
257
+ authenticated.put("/api/image-generation/custom-providers/:providerId", strictRateLimitMiddleware, async (c) => {
258
+ const loaded = loadModelsJson(resolveModelsJsonPath());
259
+ if (loaded.error) return c.json({
260
+ ok: false,
261
+ error: { message: loaded.error }
262
+ }, 409);
263
+ try {
264
+ const body = await c.req.json();
265
+ const next = upsertCustomImageProvider(loaded.config, c.req.param("providerId"), body);
266
+ const saved = saveModelsJson(resolveModelsJsonPath(), next);
267
+ if (!saved.success) return c.json({
268
+ ok: false,
269
+ error: { message: saved.error }
270
+ }, 400);
271
+ getModelRegistry().refresh();
272
+ reloadImageGenerationProviders(next);
273
+ service.emit("models-json.updated", { modelCount: getModelRegistry().getAll().length });
274
+ return c.json({
275
+ ok: true,
276
+ payload: {
277
+ provider: listCustomImageProviders(next).find((entry) => entry.providerId === c.req.param("providerId")),
278
+ providers: getImageGenerationCatalog(service.currentConfig)
279
+ }
280
+ });
281
+ } catch (error) {
282
+ return c.json({
283
+ ok: false,
284
+ error: { message: error instanceof Error ? error.message : String(error) }
285
+ }, 400);
286
+ }
287
+ });
288
+ authenticated.delete("/api/image-generation/custom-providers/:providerId", strictRateLimitMiddleware, (c) => {
289
+ const loaded = loadModelsJson(resolveModelsJsonPath());
290
+ if (loaded.error) return c.json({
291
+ ok: false,
292
+ error: { message: loaded.error }
293
+ }, 409);
294
+ try {
295
+ const next = deleteCustomImageProvider(loaded.config, c.req.param("providerId"));
296
+ const saved = saveModelsJson(resolveModelsJsonPath(), next);
297
+ if (!saved.success) return c.json({
298
+ ok: false,
299
+ error: { message: saved.error }
300
+ }, 400);
301
+ getModelRegistry().refresh();
302
+ reloadImageGenerationProviders(next);
303
+ service.emit("models-json.updated", { modelCount: getModelRegistry().getAll().length });
304
+ return c.json({
305
+ ok: true,
306
+ payload: {
307
+ deleted: c.req.param("providerId"),
308
+ providers: getImageGenerationCatalog(service.currentConfig)
309
+ }
310
+ });
311
+ } catch (error) {
312
+ return c.json({
313
+ ok: false,
314
+ error: { message: error instanceof Error ? error.message : String(error) }
315
+ }, 400);
316
+ }
317
+ });
318
+ authenticated.put("/api/image-generation/providers/:providerId/credential", strictRateLimitMiddleware, async (c) => {
319
+ const providerId = c.req.param("providerId").trim();
320
+ const provider = getImageGenerationProvider(providerId);
321
+ if (!provider) return c.json({
322
+ ok: false,
323
+ error: { message: `Unknown image provider: ${providerId}` }
324
+ }, 404);
325
+ if (provider.credentialMode === "none") return c.json({
326
+ ok: false,
327
+ error: { message: "This provider does not use an API key" }
328
+ }, 400);
329
+ const body = await c.req.json().catch(() => null);
330
+ const apiKey = typeof body?.apiKey === "string" ? body.apiKey.trim() : "";
331
+ if (!apiKey) return c.json({
332
+ ok: false,
333
+ error: { message: "API key is required" }
334
+ }, 400);
335
+ await new CredentialResolver().saveApiKey(providerId, apiKey, { profileName: "default" });
336
+ return c.json({
337
+ ok: true,
338
+ payload: {
339
+ providerId,
340
+ saved: true
341
+ }
342
+ });
343
+ });
344
+ authenticated.post("/api/image-generation/providers/:providerId/reveal-api-key", strictRateLimitMiddleware, async (c) => {
345
+ const providerId = c.req.param("providerId").trim();
346
+ if (!getImageGenerationProvider(providerId)) return c.json({
347
+ ok: false,
348
+ error: { message: `Unknown image provider: ${providerId}` }
349
+ }, 404);
350
+ const apiKey = await new CredentialResolver().revealGatewayStoredApiKey(providerId);
351
+ return c.json({
352
+ ok: true,
353
+ payload: {
354
+ providerId,
355
+ apiKey: apiKey ?? null,
356
+ source: apiKey ? "credential" : "none"
357
+ }
358
+ });
359
+ });
360
+ authenticated.delete("/api/image-generation/providers/:providerId/credential", strictRateLimitMiddleware, async (c) => {
361
+ const providerId = c.req.param("providerId").trim();
362
+ if (!getImageGenerationProvider(providerId)) return c.json({
363
+ ok: false,
364
+ error: { message: `Unknown image provider: ${providerId}` }
365
+ }, 404);
366
+ await new CredentialResolver().deleteProviderCredential(providerId);
367
+ return c.json({
368
+ ok: true,
369
+ payload: {
370
+ providerId,
371
+ deleted: true
372
+ }
373
+ });
374
+ });
375
+ authenticated.post("/api/image-generation/providers/:providerId/test", strictRateLimitMiddleware, async (c) => {
376
+ const providerId = c.req.param("providerId").trim();
377
+ const provider = getImageGenerationProvider(providerId);
378
+ if (!provider) return c.json({
379
+ ok: false,
380
+ error: { message: `Unknown image provider: ${providerId}` }
381
+ }, 404);
382
+ if (!provider.isConfigured({ cfg: service.currentConfig })) return c.json({
383
+ ok: false,
384
+ error: { message: "Provider credential is not configured" }
385
+ }, 400);
386
+ const body = await c.req.json().catch(() => ({}));
387
+ const modelId = typeof body.modelId === "string" && body.modelId.trim() ? body.modelId.trim() : provider.defaultModel;
388
+ if (!provider.models.includes(modelId)) return c.json({
389
+ ok: false,
390
+ error: { message: `Unknown ${providerId} image model: ${modelId}` }
391
+ }, 400);
392
+ const prompt = typeof body.prompt === "string" && body.prompt.trim() ? body.prompt.trim() : "A simple blue circle centered on a white background.";
393
+ try {
394
+ const result = await provider.generateImage({
395
+ provider: providerId,
396
+ model: modelId,
397
+ prompt,
398
+ count: 1,
399
+ cfg: service.currentConfig,
400
+ timeoutMs: 12e4
401
+ });
402
+ return c.json({
403
+ ok: true,
404
+ payload: {
405
+ providerId,
406
+ modelId: result.model ?? modelId,
407
+ images: result.images.map((image) => ({
408
+ mimeType: image.mimeType,
409
+ fileName: image.fileName,
410
+ dataUrl: `data:${image.mimeType};base64,${image.buffer.toString("base64")}`,
411
+ ...image.revisedPrompt ? { revisedPrompt: image.revisedPrompt } : {}
412
+ }))
413
+ }
414
+ });
415
+ } catch (error) {
416
+ return c.json({
417
+ ok: false,
418
+ error: { message: error instanceof Error ? error.message : String(error) }
419
+ }, 502);
420
+ }
421
+ });
242
422
  authenticated.get("/api/agents/:agentId/image-generation", (c) => {
243
423
  try {
244
424
  return c.json({
@@ -271,7 +451,8 @@ function registerModelsRoutes(authenticated, deps) {
271
451
  const resolver = new CredentialResolver();
272
452
  const previousKey = await resolver.revealGatewayStoredApiKey(prepared.providerId);
273
453
  const suppliedKey = typeof body.apiKey === "string" ? body.apiKey.trim() : "";
274
- if (!(suppliedKey || await resolver.resolveApiKey(prepared.providerId))) return c.json({
454
+ const effectiveKey = suppliedKey || await resolver.resolveApiKey(prepared.providerId);
455
+ if (getImageGenerationProvider(prepared.providerId).credentialMode !== "none" && !effectiveKey) return c.json({
275
456
  ok: false,
276
457
  error: { message: "API key is required" }
277
458
  }, 400);
@@ -7,9 +7,9 @@ import { messagesToClientHistory } from "../../../session/client-history.js";
7
7
  import "../../../workflows/domain/index.js";
8
8
  import { resolveWorkflowRunArtifactsDir } from "../../../workflows/store/paths.js";
9
9
  import { WorkflowDraftService } from "../../../workflows/draft/workflow-draft-service.js";
10
- import "../../../workflows/draft/index.js";
11
10
  import { WorkflowDraftConflictError, WorkflowDraftStore } from "../../../workflows/authoring/workflow-draft-store.js";
12
11
  import "../../../workflows/authoring/index.js";
12
+ import "../../../workflows/draft/index.js";
13
13
  import { basename, join } from "node:path";
14
14
  import { readFile, stat } from "node:fs/promises";
15
15
  //#region src/gateway/hono/routes/workflows.ts
@@ -64,7 +64,13 @@ function prepareImageGenerationSetup(config, agentIdRaw, input) {
64
64
  }
65
65
  };
66
66
  const entry = next.agents.list[index];
67
- entry.models.imageGenerationModel = { primary: `${providerId}/${modelId}` };
67
+ entry.models = {
68
+ ...entry.models ?? {
69
+ defaultRole: resolveEffectiveAgentManifestForAgent(config, agentId).models.defaultRole,
70
+ roles: {}
71
+ },
72
+ imageGenerationModel: { primary: `${providerId}/${modelId}` }
73
+ };
68
74
  const parsed = ConfigSchema.safeParse(next);
69
75
  if (!parsed.success) return {
70
76
  ok: false,
@@ -3,6 +3,7 @@ import { init_logger } from "../../utils/logger.js";
3
3
  import { loadConfig, saveConfig } from "../../config/loader.js";
4
4
  import { getModelRegistry } from "../../providers/model-registry.js";
5
5
  import { init_providers } from "../../providers/index.js";
6
+ import { reloadImageGenerationProviders } from "../../agent/image/generation/provider-registry.js";
6
7
  import { disposeAllSessionMcpRuntimes } from "../../agent/mcp/bundle-mcp-runtime.js";
7
8
  import "../../agent/mcp/bundle-mcp-tools.js";
8
9
  import { ConfigHotReloader } from "../../config/reload.js";
@@ -214,6 +215,7 @@ var GatewayConfigCoordinator = class {
214
215
  log.debug("Reloading models config...");
215
216
  this.opts.setConfig(newConfig);
216
217
  getModelRegistry().refresh();
218
+ reloadImageGenerationProviders();
217
219
  this.opts.emit("config.reload", { section: "models" });
218
220
  log.debug("Models config reloaded");
219
221
  }
@@ -37,11 +37,11 @@ import { AgentService } from "../agent/service.js";
37
37
  import { ensureStarterAgentsInitialized } from "../agent/starter-agents.js";
38
38
  import { AutomationService } from "../automations/service/automation-service.js";
39
39
  import "../automations/index.js";
40
- import { getWorkspacePath } from "../config/workspace-path-helpers.js";
41
- import "../config/index.js";
42
40
  import { setPairingBroadcastSink } from "../channels/pairing/pairing-events.js";
43
41
  import { registerClarifyBridge } from "./clarify-runtime.js";
44
42
  import { ChannelManager } from "../channels/manager.js";
43
+ import { getWorkspacePath } from "../config/workspace-path-helpers.js";
44
+ import "../config/index.js";
45
45
  import { startConnectedKnowledgeCoordinator } from "../knowledge/coordinator.js";
46
46
  import "../knowledge/index.js";
47
47
  import { startConnectorMemorySyncCoordinator } from "../connectors/memory-sync-coordinator.js";
@@ -1,6 +1,6 @@
1
1
  import { DEFAULT_ACK_MAX_CHARS, HEARTBEAT_OK, NO_REPLY, shouldSilence, stripHeartbeatToken } from "./tokens.js";
2
+ import { appendCronEventLines } from "./event-prompt.js";
2
3
  import { isWithinActiveHours } from "./active-hours.js";
3
4
  import { isHeartbeatContentEmpty } from "./content-check.js";
4
- import { appendCronEventLines } from "./event-prompt.js";
5
5
  import { createHeartbeatWake } from "./wake.js";
6
6
  export { DEFAULT_ACK_MAX_CHARS, HEARTBEAT_OK, NO_REPLY, appendCronEventLines, createHeartbeatWake, isHeartbeatContentEmpty, isWithinActiveHours, shouldSilence, stripHeartbeatToken };
@@ -7,5 +7,5 @@ export { TimeoutAbortError, isTimeoutAbortError, pickEffectiveTimeoutMs, pickTim
7
7
  export { BlockedPrivateNetworkError, assertNotPrivateNetwork, classifyHost, defaultPolicy as defaultPrivateNetworkPolicy, privateNetworkPolicyToSsrfGuardOptions, type HostClass, type PrivateNetworkPolicy, } from './private-network.js';
8
8
  export { resolveProviderHttpRequestConfig, type ResolvedProviderHttpDefaults, type ResolveProviderHttpRequestConfigOptions, } from './resolve-provider-http-request-config.js';
9
9
  export { ProviderHttpError, assertOk, assertOkOrThrowHttpError, assertOkOrThrowProviderError, createProviderHttpError, extractProviderErrorDetail, extractProviderRequestId, extractVendorErrorFields, formatProviderErrorPayload, formatProviderHttpErrorMessage, readResponseTextLimited, truncateErrorDetail, type ProviderHttpErrorParts, } from './provider-http-errors.js';
10
- export { createProviderOperationDeadline, fetchWithTimeoutGuarded, getJsonRequest, normalizeBaseUrl, postJsonRequest, postMultipartRequest, waitProviderOperationPollInterval, type FetchWithGuardOptions, type GetJsonRequestOptions, type PostJsonRequestOptions, type PostMultipartRequestOptions, type ProviderHttpRequestConfig, type ProviderOperationDeadline, } from './provider-http.js';
10
+ export { createProviderOperationDeadline, fetchWithTimeoutGuarded, getJsonRequest, normalizeBaseUrl, postJsonRequest, postMultipartRequest, readJsonResponseLimited, waitProviderOperationPollInterval, type FetchWithGuardOptions, type GetJsonRequestOptions, type PostJsonRequestOptions, type PostMultipartRequestOptions, type ProviderHttpRequestConfig, type ProviderOperationDeadline, } from './provider-http.js';
11
11
  export { SsrfBlockedError, assertSafeUrl, isPrivateIpAddress, isPrivateIpv4, isPrivateIpv6, type ResolvedSsrfTarget, type SsrfGuardOptions, } from './ssrf-guard.js';
@@ -4,5 +4,5 @@ import { BlockedPrivateNetworkError, assertNotPrivateNetwork, classifyHost, defa
4
4
  import { resolveProviderHttpRequestConfig } from "./resolve-provider-http-request-config.js";
5
5
  import { ProviderHttpError, assertOk, assertOkOrThrowHttpError, assertOkOrThrowProviderError, createProviderHttpError, extractProviderErrorDetail, extractProviderRequestId, extractVendorErrorFields, formatProviderErrorPayload, formatProviderHttpErrorMessage, readResponseTextLimited, truncateErrorDetail } from "./provider-http-errors.js";
6
6
  import { SsrfBlockedError, assertSafeUrl, isPrivateIpAddress, isPrivateIpv4, isPrivateIpv6 } from "./ssrf-guard.js";
7
- import { createProviderOperationDeadline, fetchWithTimeoutGuarded, getJsonRequest, normalizeBaseUrl, postJsonRequest, postMultipartRequest, waitProviderOperationPollInterval } from "./provider-http.js";
8
- export { BlockedPrivateNetworkError, ProviderHttpError, SsrfBlockedError, TimeoutAbortError, assertNotPrivateNetwork, assertOk, assertOkOrThrowHttpError, assertOkOrThrowProviderError, assertSafeUrl, classifyHost, collectProviderApiKeysForExecution, createProviderHttpError, createProviderOperationDeadline, defaultPolicy as defaultPrivateNetworkPolicy, executeWithApiKeyRotation, extractProviderErrorDetail, extractProviderRequestId, extractVendorErrorFields, fetchWithTimeoutGuarded, formatProviderErrorPayload, formatProviderHttpErrorMessage, getJsonRequest, isPrivateIpAddress, isPrivateIpv4, isPrivateIpv6, isRotatableAuthFailure, isTimeoutAbortError, normalizeBaseUrl, pickEffectiveTimeoutMs, pickTimeoutMsOrFallback, postJsonRequest, postMultipartRequest, privateNetworkPolicyToSsrfGuardOptions, readResponseTextLimited, resolveDeadline, resolveProviderHttpRequestConfig, truncateErrorDetail, waitProviderOperationPollInterval };
7
+ import { createProviderOperationDeadline, fetchWithTimeoutGuarded, getJsonRequest, normalizeBaseUrl, postJsonRequest, postMultipartRequest, readJsonResponseLimited, waitProviderOperationPollInterval } from "./provider-http.js";
8
+ export { BlockedPrivateNetworkError, ProviderHttpError, SsrfBlockedError, TimeoutAbortError, assertNotPrivateNetwork, assertOk, assertOkOrThrowHttpError, assertOkOrThrowProviderError, assertSafeUrl, classifyHost, collectProviderApiKeysForExecution, createProviderHttpError, createProviderOperationDeadline, defaultPolicy as defaultPrivateNetworkPolicy, executeWithApiKeyRotation, extractProviderErrorDetail, extractProviderRequestId, extractVendorErrorFields, fetchWithTimeoutGuarded, formatProviderErrorPayload, formatProviderHttpErrorMessage, getJsonRequest, isPrivateIpAddress, isPrivateIpv4, isPrivateIpv6, isRotatableAuthFailure, isTimeoutAbortError, normalizeBaseUrl, pickEffectiveTimeoutMs, pickTimeoutMsOrFallback, postJsonRequest, postMultipartRequest, privateNetworkPolicyToSsrfGuardOptions, readJsonResponseLimited, readResponseTextLimited, resolveDeadline, resolveProviderHttpRequestConfig, truncateErrorDetail, waitProviderOperationPollInterval };
@@ -39,6 +39,8 @@ export interface GetJsonRequestOptions extends ProviderHttpRequestConfig {
39
39
  headers?: Record<string, string> | Headers;
40
40
  }
41
41
  export declare function getJsonRequest(url: string | URL, options: GetJsonRequestOptions): Promise<Response>;
42
+ /** Read and parse a JSON response without allowing an unbounded body allocation. */
43
+ export declare function readJsonResponseLimited(response: Response, maxBytes: number): Promise<unknown>;
42
44
  /**
43
45
  * Normalize a base URL: trim trailing slash, force http(s) scheme.
44
46
  */
@@ -49,7 +49,8 @@ async function postJsonRequest(url, options) {
49
49
  init: {
50
50
  method: "POST",
51
51
  headers,
52
- body: JSON.stringify(options.body)
52
+ body: JSON.stringify(options.body),
53
+ redirect: "error"
53
54
  }
54
55
  });
55
56
  await assertOkOrThrowProviderError(response, options.label);
@@ -62,7 +63,8 @@ async function postMultipartRequest(url, options) {
62
63
  init: {
63
64
  method: "POST",
64
65
  headers,
65
- body: options.body
66
+ body: options.body,
67
+ redirect: "error"
66
68
  }
67
69
  });
68
70
  await assertOkOrThrowProviderError(response, options.label);
@@ -75,12 +77,49 @@ async function getJsonRequest(url, options) {
75
77
  ...options,
76
78
  init: {
77
79
  method: "GET",
78
- headers
80
+ headers,
81
+ redirect: "error"
79
82
  }
80
83
  });
81
84
  await assertOkOrThrowProviderError(response, options.label);
82
85
  return response;
83
86
  }
87
+ /** Read and parse a JSON response without allowing an unbounded body allocation. */
88
+ async function readJsonResponseLimited(response, maxBytes) {
89
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new Error("JSON response byte limit must be a positive safe integer.");
90
+ const declaredLength = Number.parseInt(response.headers.get("content-length") ?? "", 10);
91
+ if (Number.isFinite(declaredLength) && declaredLength > maxBytes) throw new Error(`Provider JSON response exceeds ${maxBytes} bytes.`);
92
+ const reader = response.body?.getReader();
93
+ if (!reader) throw new Error("Provider returned an empty JSON response.");
94
+ const chunks = [];
95
+ let total = 0;
96
+ try {
97
+ while (true) {
98
+ const { value, done } = await reader.read();
99
+ if (done) break;
100
+ if (!value?.byteLength) continue;
101
+ total += value.byteLength;
102
+ if (total > maxBytes) {
103
+ await reader.cancel().catch(() => {});
104
+ throw new Error(`Provider JSON response exceeds ${maxBytes} bytes.`);
105
+ }
106
+ chunks.push(value);
107
+ }
108
+ } finally {
109
+ reader.releaseLock();
110
+ }
111
+ const body = new Uint8Array(total);
112
+ let offset = 0;
113
+ for (const chunk of chunks) {
114
+ body.set(chunk, offset);
115
+ offset += chunk.byteLength;
116
+ }
117
+ try {
118
+ return JSON.parse(new TextDecoder().decode(body));
119
+ } catch {
120
+ throw new Error("Provider returned invalid JSON.");
121
+ }
122
+ }
84
123
  /**
85
124
  * Normalize a base URL: trim trailing slash, force http(s) scheme.
86
125
  */
@@ -107,4 +146,4 @@ async function waitProviderOperationPollInterval(intervalMs, signal) {
107
146
  await setTimeout$1(intervalMs, void 0, { signal });
108
147
  }
109
148
  //#endregion
110
- export { createProviderOperationDeadline, fetchWithTimeoutGuarded, getJsonRequest, normalizeBaseUrl, postJsonRequest, postMultipartRequest, waitProviderOperationPollInterval };
149
+ export { createProviderOperationDeadline, fetchWithTimeoutGuarded, getJsonRequest, normalizeBaseUrl, postJsonRequest, postMultipartRequest, readJsonResponseLimited, waitProviderOperationPollInterval };
@@ -11,4 +11,5 @@ export interface ModelReferenceHealth {
11
11
  export declare function auditModelReferences(config: Config, sessionConfigs?: ReadonlyMap<string, SessionAgentConfig>, deps?: {
12
12
  registry?: ModelRegistry;
13
13
  catalog?: ModelCatalogSnapshot;
14
+ resolveImageGenerationModel?: (ref: string) => boolean;
14
15
  }): ModelReferenceHealth[];
@@ -1,22 +1,25 @@
1
1
  import { getModelCatalogStore, init_model_catalog_store } from "./model-catalog-store.js";
2
2
  import { getModelRegistry, init_model_registry } from "./model-registry.js";
3
+ import { parseImageGenerationModelRef } from "../agent/image/generation/model-ref.js";
4
+ import { getImageGenerationProvider } from "../agent/image/generation/provider-registry.js";
3
5
  //#region src/providers/model-reference-auditor.ts
4
6
  init_model_catalog_store();
5
7
  init_model_registry();
6
8
  function asRecord(value) {
7
9
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
8
10
  }
9
- function addRef(out, value, location) {
11
+ function addRef(out, value, location, kind = "text") {
10
12
  if (typeof value === "string" && value.trim()) out.push({
11
13
  ref: value.trim(),
12
- location
14
+ location,
15
+ kind
13
16
  });
14
17
  }
15
- function collectToolModel(out, value, location) {
18
+ function collectToolModel(out, value, location, kind = "text") {
16
19
  const model = asRecord(value);
17
20
  if (!model) return;
18
- addRef(out, model.primary, `${location}.primary`);
19
- if (Array.isArray(model.fallbacks)) model.fallbacks.forEach((ref, index) => addRef(out, ref, `${location}.fallbacks[${index}]`));
21
+ addRef(out, model.primary, `${location}.primary`, kind);
22
+ if (Array.isArray(model.fallbacks)) model.fallbacks.forEach((ref, index) => addRef(out, ref, `${location}.fallbacks[${index}]`, kind));
20
23
  }
21
24
  function collectModelPolicy(out, value, location) {
22
25
  const policy = asRecord(value);
@@ -29,7 +32,7 @@ function collectModelPolicy(out, value, location) {
29
32
  if (Array.isArray(role.fallbacks)) role.fallbacks.forEach((ref, index) => addRef(out, ref, `${location}.roles.${roleId}.fallbacks[${index}]`));
30
33
  }
31
34
  collectToolModel(out, policy.imageModel, `${location}.imageModel`);
32
- collectToolModel(out, policy.imageGenerationModel, `${location}.imageGenerationModel`);
35
+ collectToolModel(out, policy.imageGenerationModel, `${location}.imageGenerationModel`, "image-generation");
33
36
  }
34
37
  function collectReferences(config, sessionConfigs) {
35
38
  const out = [];
@@ -47,22 +50,29 @@ function suggestedRef(ref, catalog) {
47
50
  const first = source.models.find((model) => model.availability === "available");
48
51
  return first ? `${providerId}/${first.id}` : void 0;
49
52
  }
53
+ function hasImageGenerationModel(ref) {
54
+ const parsed = parseImageGenerationModelRef(ref);
55
+ if (!parsed) return false;
56
+ return getImageGenerationProvider(parsed.provider)?.models.includes(parsed.model) ?? false;
57
+ }
50
58
  function auditModelReferences(config, sessionConfigs = /* @__PURE__ */ new Map(), deps = {}) {
51
59
  const registry = deps.registry ?? getModelRegistry();
52
60
  const catalog = deps.catalog ?? getModelCatalogStore().load();
61
+ const resolveImageGenerationModel = deps.resolveImageGenerationModel ?? hasImageGenerationModel;
53
62
  const grouped = /* @__PURE__ */ new Map();
54
63
  for (const reference of collectReferences(config, sessionConfigs)) {
55
- const locations = grouped.get(reference.ref) ?? [];
56
- locations.push(reference.location);
57
- grouped.set(reference.ref, locations);
64
+ const references = grouped.get(reference.ref) ?? [];
65
+ references.push(reference);
66
+ grouped.set(reference.ref, references);
58
67
  }
59
- return [...grouped.entries()].map(([ref, locations]) => {
60
- const availability = registry.resolve(ref) ? "available" : "unavailable";
61
- const suggestion = availability === "unavailable" ? suggestedRef(ref, catalog) : void 0;
68
+ return [...grouped.entries()].map(([ref, references]) => {
69
+ const availability = references.every((reference) => reference.kind === "image-generation" ? resolveImageGenerationModel(ref) : Boolean(registry.resolve(ref))) ? "available" : "unavailable";
70
+ const textOnly = references.every((reference) => reference.kind === "text");
71
+ const suggestion = availability === "unavailable" && textOnly ? suggestedRef(ref, catalog) : void 0;
62
72
  return {
63
73
  ref,
64
74
  availability,
65
- locations,
75
+ locations: references.map((reference) => reference.location),
66
76
  ...suggestion ? { suggestedRef: suggestion } : {}
67
77
  };
68
78
  }).sort((a, b) => a.ref.localeCompare(b.ref));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xopcai/xopc",
3
- "version": "0.0.166",
3
+ "version": "0.0.167",
4
4
  "description": "Local-first AI system that remembers context, coordinates AI, and sustains long-term progress.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -100,7 +100,7 @@
100
100
  "semver": "^7.8.1",
101
101
  "silk-wasm": "^3.7.1",
102
102
  "thread-stream": "^4.2.0",
103
- "undici": "^8.5.0",
103
+ "undici": "^8.9.0",
104
104
  "uuid": "^14.0.0",
105
105
  "ws": "^8.21.0",
106
106
  "zod": "^4.4.3"