pi-openai-toolkit 0.17.3 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,255 +1,384 @@
1
- import { getAgentDir, type ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import { assertConfigValid, loadToolkitConfig, resolveToolkitConfig } from "../config";
3
- import { notifyConfigIssues } from "../config/notifications";
4
- import { resolveResponsesEnvironment } from "../runtime";
5
- import {
6
- copyImageToExplicitPath,
7
- prepareExplicitOutputPath,
8
- saveCanonicalImage,
9
- } from "./artifacts";
10
- import { requestGeneratedImage } from "./client";
11
- import { isImageGenerationEnabledForModel } from "./eligibility";
12
- import {
13
- buildImageGenerationRequest,
14
- normalizeGenerateImageParams,
15
- selectImageGenerationModel,
16
- } from "./protocol";
17
- import {
18
- clearPreparedReferences,
19
- prepareReferenceImages,
20
- } from "./references";
21
- import {
22
- IMAGE_GENERATION_CAPABLE_APIS,
23
- IMAGE_GENERATION_MIME_TYPE,
24
- ImageGenerationError,
25
- sanitizeImageDiagnostic,
26
- type GenerateImageParams,
27
- type ImageGenerationExecutionResult,
28
- type ImageGenerationFailureCode,
29
- type ParsedGeneratedImage,
30
- } from "./types";
31
-
32
- export type ImageGenerationServiceDependencies = {
33
- loadConfig: typeof loadToolkitConfig;
34
- resolveRuntime: typeof resolveResponsesEnvironment;
35
- getAgentDir: typeof getAgentDir;
36
- prepareOutput: typeof prepareExplicitOutputPath;
37
- prepareReferences: typeof prepareReferenceImages;
38
- requestImage: typeof requestGeneratedImage;
39
- saveCanonical: typeof saveCanonicalImage;
40
- copyExplicit: typeof copyImageToExplicitPath;
41
- clearReferences: typeof clearPreparedReferences;
42
- };
43
-
44
- const DEFAULT_SERVICE_DEPS: ImageGenerationServiceDependencies = {
45
- loadConfig: loadToolkitConfig,
46
- resolveRuntime: resolveResponsesEnvironment,
47
- getAgentDir,
48
- prepareOutput: prepareExplicitOutputPath,
49
- prepareReferences: prepareReferenceImages,
50
- requestImage: requestGeneratedImage,
51
- saveCanonical: saveCanonicalImage,
52
- copyExplicit: copyImageToExplicitPath,
53
- clearReferences: clearPreparedReferences,
54
- };
55
-
56
- function runtimeFailure(
57
- reason: string,
58
- errorMessage?: string,
59
- ): ImageGenerationError {
60
- switch (reason) {
61
- case "disabled":
62
- return new ImageGenerationError("disabled", "Image generation is disabled.");
63
- case "unsupported-api":
64
- case "missing-model":
65
- return new ImageGenerationError(
66
- "unsupported-model",
67
- "Image generation is unavailable for the current model.",
68
- );
69
- case "missing-api-key":
70
- case "auth-resolution-failed":
71
- return new ImageGenerationError(
72
- "authentication",
73
- sanitizeImageDiagnostic(
74
- errorMessage,
75
- "Unable to resolve authentication for image generation with the current model.",
76
- ),
77
- );
78
- default:
79
- return new ImageGenerationError(
80
- "missing-runtime",
81
- "Unable to resolve the current model's Responses endpoint for image generation.",
82
- );
83
- }
84
- }
85
-
86
- function throwIfAborted(signal: AbortSignal | undefined): void {
87
- if (signal?.aborted) {
88
- throw new ImageGenerationError("aborted", "Image generation was cancelled.");
89
- }
90
- }
91
-
92
- function clientFailureCode(reason: string): ImageGenerationFailureCode {
93
- switch (reason) {
94
- case "aborted":
95
- case "timeout":
96
- case "authentication":
97
- case "rate-limit":
98
- case "request-rejected":
99
- case "backend-unavailable":
100
- case "network":
101
- case "oversized-response":
102
- case "malformed-response":
103
- case "no-image":
104
- return reason;
105
- default:
106
- return "network";
107
- }
108
- }
109
-
110
- function formatResultText(args: {
111
- artifactPath: string;
112
- outputPath?: string;
113
- warning?: string;
114
- edited: boolean;
115
- width: number;
116
- height: number;
117
- }): string {
118
- const lines = [
119
- `${args.edited ? "Edited" : "Generated"} PNG image (${args.width}x${args.height}).`,
120
- `Artifact: ${args.artifactPath}`,
121
- ];
122
- if (args.outputPath) lines.push(`Copied to: ${args.outputPath}`);
123
- if (args.warning) lines.push(`Warning: ${args.warning}`);
124
- return lines.join("\n");
125
- }
126
-
127
- export function createImageGenerationExecutor(
128
- deps: ImageGenerationServiceDependencies = DEFAULT_SERVICE_DEPS,
129
- ): (args: {
130
- params: GenerateImageParams;
131
- toolCallId: string;
132
- signal?: AbortSignal;
133
- ctx: ExtensionContext;
134
- }) => Promise<ImageGenerationExecutionResult> {
135
- return async (args) => {
136
- const params = normalizeGenerateImageParams(args.params);
137
- throwIfAborted(args.signal);
138
- const resolved = resolveToolkitConfig(deps.loadConfig(), args.ctx.model);
139
- notifyConfigIssues(args.ctx, resolved);
140
- assertConfigValid(resolved, "imageGeneration", "compatibility");
141
- const { config } = resolved;
142
- if (!isImageGenerationEnabledForModel(args.ctx.model, config.imageGeneration)) {
143
- throw new ImageGenerationError(
144
- "unsupported-model",
145
- "Image generation is not enabled for the current provider/model-id.",
146
- );
147
- }
148
-
149
- const imageModel = selectImageGenerationModel({
150
- requestedModel: params.model,
151
- configuredModels: resolved.policy.imageGeneration.allowedModels,
152
- defaultModel: resolved.policy.imageGeneration.defaultModel,
153
- });
154
-
155
- const runtimeResolution = await deps.resolveRuntime(args.ctx, {
156
- enabled: config.imageGeneration.enabled,
157
- responsesApis: IMAGE_GENERATION_CAPABLE_APIS,
158
- ...(resolved.format === "v2" ? { codexGatewayModels: resolved.gatewayModelKeys } : {}),
159
- });
160
- if (!runtimeResolution.ok) {
161
- throw runtimeFailure(runtimeResolution.reason, runtimeResolution.errorMessage);
162
- }
163
-
164
- const agentDir = deps.getAgentDir();
165
- const explicitOutput = await deps.prepareOutput({
166
- rawPath: params.outputPath,
167
- agentDir,
168
- ctx: args.ctx,
169
- signal: args.signal,
170
- });
171
-
172
- const references = await deps.prepareReferences({
173
- paths: params.referenceImagePaths,
174
- ctx: args.ctx,
175
- signal: args.signal,
176
- });
177
- let generated: ParsedGeneratedImage | undefined;
178
- try {
179
- throwIfAborted(args.signal);
180
- const body = buildImageGenerationRequest({
181
- routingModel: runtimeResolution.runtime.model,
182
- imageModel,
183
- params,
184
- references,
185
- });
186
- const response = await deps.requestImage({
187
- runtime: runtimeResolution.runtime,
188
- body,
189
- signal: args.signal,
190
- });
191
- if (!response.ok) {
192
- throw new ImageGenerationError(
193
- clientFailureCode(response.reason),
194
- response.errorMessage,
195
- );
196
- }
197
- generated = response.image;
198
-
199
- const artifactPath = await deps.saveCanonical({
200
- bytes: generated.bytes,
201
- agentDir,
202
- sessionId: args.ctx.sessionManager.getSessionId(),
203
- imageCallId: generated.imageCallId || args.toolCallId,
204
- });
205
-
206
- let outputPath: string | undefined;
207
- let warning: string | undefined;
208
- if (explicitOutput) {
209
- try {
210
- await deps.copyExplicit({ bytes: generated.bytes, plan: explicitOutput });
211
- outputPath = explicitOutput.path;
212
- } catch (error) {
213
- warning = sanitizeImageDiagnostic(
214
- error instanceof Error ? error.message : error,
215
- `The generated image was saved to the canonical artifact, but copying to ${explicitOutput.path} failed.`,
216
- );
217
- }
218
- }
219
-
220
- const edited = references.length > 0;
221
- const details = {
222
- artifactPath,
223
- ...(outputPath ? { outputPath } : {}),
224
- routingModel: `${runtimeResolution.runtime.provider}/${runtimeResolution.runtime.model}`,
225
- imageModel,
226
- imageCallId: generated.imageCallId,
227
- ...(generated.responseId ? { responseId: generated.responseId } : {}),
228
- mimeType: IMAGE_GENERATION_MIME_TYPE,
229
- byteCount: generated.bytes.length,
230
- width: generated.width,
231
- height: generated.height,
232
- edited,
233
- referenceCount: references.length,
234
- ...(generated.revisedPrompt ? { revisedPrompt: generated.revisedPrompt } : {}),
235
- ...(warning ? { warning } : {}),
236
- };
237
- return {
238
- details,
239
- text: formatResultText({
240
- artifactPath,
241
- outputPath,
242
- warning,
243
- edited,
244
- width: generated.width,
245
- height: generated.height,
246
- }),
247
- };
248
- } finally {
249
- deps.clearReferences(references);
250
- generated?.bytes.fill(0);
251
- }
252
- };
253
- }
254
-
255
- export const executeImageGeneration = createImageGenerationExecutor();
1
+ import { getAgentDir, type ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import { assertConfigValid, loadToolkitConfig, resolveToolkitConfig } from "../config";
3
+ import { notifyConfigIssues } from "../config/notifications";
4
+ import { resolveEffectiveModel, resolveResponsesEnvironment, VIRTUAL_MODEL_API } from "../runtime";
5
+ import {
6
+ copyImageToExplicitPath,
7
+ prepareExplicitOutputPath,
8
+ saveCanonicalImage,
9
+ type ExplicitOutputPlan,
10
+ } from "./artifacts";
11
+ import { requestGeneratedImage } from "./client";
12
+ import { isImageGenerationEnabledForModel } from "./eligibility";
13
+ import {
14
+ buildImageGenerationRequest,
15
+ normalizeGenerateImageParams,
16
+ selectImageGenerationModel,
17
+ } from "./protocol";
18
+ import {
19
+ clearPreparedReferences,
20
+ prepareReferenceImages,
21
+ } from "./references";
22
+ import {
23
+ IMAGE_GENERATION_CAPABLE_APIS,
24
+ MAX_IMAGE_IDENTIFIER_CHARS,
25
+ ImageGenerationError,
26
+ sanitizeImageDiagnostic,
27
+ type GenerateImageParams,
28
+ type ImageGenerationBatchDetails,
29
+ type ImageGenerationExecutionResult,
30
+ type ImageGenerationFailureCode,
31
+ type ImageGenerationDetails,
32
+ type ParsedGeneratedImage,
33
+ } from "./types";
34
+
35
+ export type ImageGenerationServiceDependencies = {
36
+ loadConfig: typeof loadToolkitConfig;
37
+ resolveRuntime: typeof resolveResponsesEnvironment;
38
+ getAgentDir: typeof getAgentDir;
39
+ prepareOutput: typeof prepareExplicitOutputPath;
40
+ prepareReferences: typeof prepareReferenceImages;
41
+ requestImage: typeof requestGeneratedImage;
42
+ saveCanonical: typeof saveCanonicalImage;
43
+ copyExplicit: typeof copyImageToExplicitPath;
44
+ clearReferences: typeof clearPreparedReferences;
45
+ };
46
+
47
+ const DEFAULT_SERVICE_DEPS: ImageGenerationServiceDependencies = {
48
+ loadConfig: loadToolkitConfig,
49
+ resolveRuntime: resolveResponsesEnvironment,
50
+ getAgentDir,
51
+ prepareOutput: prepareExplicitOutputPath,
52
+ prepareReferences: prepareReferenceImages,
53
+ requestImage: requestGeneratedImage,
54
+ saveCanonical: saveCanonicalImage,
55
+ copyExplicit: copyImageToExplicitPath,
56
+ clearReferences: clearPreparedReferences,
57
+ };
58
+
59
+ function runtimeFailure(
60
+ reason: string,
61
+ errorMessage?: string,
62
+ ): ImageGenerationError {
63
+ switch (reason) {
64
+ case "disabled":
65
+ return new ImageGenerationError("disabled", "Image generation is disabled.");
66
+ case "unsupported-api":
67
+ case "missing-model":
68
+ return new ImageGenerationError(
69
+ "unsupported-model",
70
+ "Image generation is unavailable for the current model.",
71
+ );
72
+ case "missing-api-key":
73
+ case "auth-resolution-failed":
74
+ return new ImageGenerationError(
75
+ "authentication",
76
+ sanitizeImageDiagnostic(
77
+ errorMessage,
78
+ "Unable to resolve authentication for image generation with the current model.",
79
+ ),
80
+ );
81
+ default:
82
+ return new ImageGenerationError(
83
+ "missing-runtime",
84
+ "Unable to resolve the current model's Images endpoint for image generation.",
85
+ );
86
+ }
87
+ }
88
+
89
+ function throwIfAborted(signal: AbortSignal | undefined): void {
90
+ if (signal?.aborted) {
91
+ throw new ImageGenerationError("aborted", "Image generation was cancelled.");
92
+ }
93
+ }
94
+
95
+ function clientFailureCode(reason: string): ImageGenerationFailureCode {
96
+ switch (reason) {
97
+ case "aborted":
98
+ case "timeout":
99
+ case "authentication":
100
+ case "rate-limit":
101
+ case "request-rejected":
102
+ case "backend-unavailable":
103
+ case "network":
104
+ case "oversized-response":
105
+ case "malformed-response":
106
+ case "no-image":
107
+ return reason;
108
+ default:
109
+ return "network";
110
+ }
111
+ }
112
+
113
+ function asImageGenerationError(value: unknown): ImageGenerationError {
114
+ if (value instanceof ImageGenerationError) {
115
+ return new ImageGenerationError(
116
+ value.code,
117
+ sanitizeImageDiagnostic(value.message, "Image generation failed."),
118
+ );
119
+ }
120
+ return new ImageGenerationError(
121
+ "network",
122
+ sanitizeImageDiagnostic(value instanceof Error ? value.message : value, "Image generation failed."),
123
+ );
124
+ }
125
+
126
+ function formatImageMimeType(mimeType: ImageGenerationDetails["mimeType"]): string {
127
+ switch (mimeType) {
128
+ case "image/png":
129
+ return "PNG";
130
+ case "image/jpeg":
131
+ return "JPEG";
132
+ case "image/webp":
133
+ return "WebP";
134
+ }
135
+ }
136
+
137
+ function formatResultText(args: {
138
+ artifactPath: string;
139
+ outputPath?: string;
140
+ warning?: string;
141
+ edited: boolean;
142
+ mimeType: ImageGenerationDetails["mimeType"];
143
+ width: number;
144
+ height: number;
145
+ }): string {
146
+ const lines = [
147
+ `${args.edited ? "Edited" : "Generated"} ${formatImageMimeType(args.mimeType)} image (${args.width}x${args.height}).`,
148
+ `Artifact: ${args.artifactPath}`,
149
+ ];
150
+ if (args.outputPath) lines.push(`Copied to: ${args.outputPath}`);
151
+ if (args.warning) lines.push(`Warning: ${args.warning}`);
152
+ return lines.join("\n");
153
+ }
154
+
155
+ function formatBatchResultText(details: ImageGenerationBatchDetails): string {
156
+ const lines = [`Generated ${details.succeededCount}/${details.requestedCount} images.`];
157
+ const failedIndexes = new Set((details.failures ?? []).map((failure) => failure.index));
158
+ let imageIndex = 0;
159
+ for (let batchIndex = 0; batchIndex < details.requestedCount; batchIndex += 1) {
160
+ if (failedIndexes.has(batchIndex)) continue;
161
+ const image = details.images[imageIndex++];
162
+ if (image) lines.push(`Image ${batchIndex + 1}: ${image.artifactPath}`);
163
+ }
164
+ for (const failure of details.failures ?? []) {
165
+ lines.push(`Image ${failure.index + 1} failed: ${failure.message}`);
166
+ }
167
+ return lines.join("\n");
168
+ }
169
+
170
+ function batchImageCallId(imageCallId: string, batchIndex: number, batchSize: number): string {
171
+ if (batchSize === 1) return imageCallId;
172
+ const suffix = `-batch-${batchIndex + 1}`;
173
+ return `${imageCallId.slice(0, Math.max(1, MAX_IMAGE_IDENTIFIER_CHARS - suffix.length))}${suffix}`;
174
+ }
175
+
176
+ async function persistGeneratedImage(args: {
177
+ generated: ParsedGeneratedImage;
178
+ params: ReturnType<typeof normalizeGenerateImageParams>;
179
+ batchIndex: number;
180
+ toolCallId: string;
181
+ batchSize: number;
182
+ imageModel: string;
183
+ routingModel: string;
184
+ agentDir: string;
185
+ sessionId: string;
186
+ referencesCount: number;
187
+ explicitOutput?: ExplicitOutputPlan;
188
+ deps: ImageGenerationServiceDependencies;
189
+ }): Promise<ImageGenerationDetails> {
190
+ const imageCallId = batchImageCallId(args.generated.imageCallId || args.toolCallId, args.batchIndex, args.batchSize);
191
+ try {
192
+ const artifactPath = await args.deps.saveCanonical({
193
+ bytes: args.generated.bytes,
194
+ agentDir: args.agentDir,
195
+ sessionId: args.sessionId,
196
+ imageCallId,
197
+ mimeType: args.generated.mimeType,
198
+ });
199
+
200
+ let outputPath: string | undefined;
201
+ let warning: string | undefined;
202
+ if (args.explicitOutput) {
203
+ try {
204
+ await args.deps.copyExplicit({
205
+ bytes: args.generated.bytes,
206
+ plan: args.explicitOutput,
207
+ mimeType: args.generated.mimeType,
208
+ });
209
+ outputPath = args.explicitOutput.path;
210
+ } catch (error) {
211
+ warning = sanitizeImageDiagnostic(
212
+ error instanceof Error ? error.message : error,
213
+ `The generated image was saved to the canonical artifact, but copying to ${args.explicitOutput.path} failed.`,
214
+ );
215
+ }
216
+ }
217
+
218
+ return {
219
+ artifactPath,
220
+ ...(outputPath ? { outputPath } : {}),
221
+ routingModel: args.routingModel,
222
+ imageModel: args.imageModel,
223
+ imageCallId,
224
+ ...(args.generated.responseId ? { responseId: args.generated.responseId } : {}),
225
+ mimeType: args.generated.mimeType,
226
+ byteCount: args.generated.bytes.length,
227
+ width: args.generated.width,
228
+ height: args.generated.height,
229
+ edited: args.params.action === "edit",
230
+ referenceCount: args.referencesCount,
231
+ ...(args.generated.revisedPrompt ? { revisedPrompt: args.generated.revisedPrompt } : {}),
232
+ ...(warning ? { warning } : {}),
233
+ };
234
+ } finally {
235
+ args.generated.bytes.fill(0);
236
+ }
237
+ }
238
+
239
+ export function createImageGenerationExecutor(
240
+ deps: ImageGenerationServiceDependencies = DEFAULT_SERVICE_DEPS,
241
+ ): (args: {
242
+ params: GenerateImageParams;
243
+ toolCallId: string;
244
+ signal?: AbortSignal;
245
+ ctx: ExtensionContext;
246
+ }) => Promise<ImageGenerationExecutionResult> {
247
+ return async (args) => {
248
+ const params = normalizeGenerateImageParams(args.params);
249
+ throwIfAborted(args.signal);
250
+ const resolved = resolveToolkitConfig(deps.loadConfig(), args.ctx.model);
251
+ notifyConfigIssues(args.ctx, resolved);
252
+ assertConfigValid(resolved, "imageGeneration", "compatibility");
253
+ const { config } = resolved;
254
+ let effectiveModel = args.ctx.model;
255
+ if (args.ctx.model?.api === VIRTUAL_MODEL_API) {
256
+ const effective = await resolveEffectiveModel(args.ctx);
257
+ if (!effective.ok) {
258
+ throw new ImageGenerationError(
259
+ "unsupported-model",
260
+ "Image generation is unavailable because the virtual model could not be resolved to a physical model.",
261
+ );
262
+ }
263
+ effectiveModel = effective.model;
264
+ }
265
+ if (!isImageGenerationEnabledForModel(args.ctx.model, config.imageGeneration, effectiveModel)) {
266
+ throw new ImageGenerationError(
267
+ "unsupported-model",
268
+ "Image generation is not enabled for the current provider/model-id.",
269
+ );
270
+ }
271
+
272
+ const imageModel = selectImageGenerationModel({
273
+ requestedModel: params.model,
274
+ configuredModels: resolved.policy.imageGeneration.allowedModels,
275
+ defaultModel: resolved.policy.imageGeneration.defaultModel,
276
+ });
277
+
278
+ const runtimeResolution = await deps.resolveRuntime(args.ctx, {
279
+ enabled: config.imageGeneration.enabled,
280
+ responsesApis: IMAGE_GENERATION_CAPABLE_APIS,
281
+ ...(resolved.format === "v2" ? { codexGatewayModels: resolved.gatewayModelKeys } : {}),
282
+ });
283
+ if (!runtimeResolution.ok) {
284
+ throw runtimeFailure(runtimeResolution.reason, runtimeResolution.errorMessage);
285
+ }
286
+
287
+ const agentDir = deps.getAgentDir();
288
+ const explicitOutput = await deps.prepareOutput({
289
+ rawPath: params.outputPath,
290
+ agentDir,
291
+ ctx: args.ctx,
292
+ signal: args.signal,
293
+ });
294
+
295
+ const references = await deps.prepareReferences({
296
+ paths: params.referenceImagePaths,
297
+ ctx: args.ctx,
298
+ signal: args.signal,
299
+ });
300
+ try {
301
+ const jobs = Array.from({ length: params.batchSize }, (_, batchIndex) =>
302
+ (async () => {
303
+ throwIfAborted(args.signal);
304
+ const body = buildImageGenerationRequest({
305
+ imageModel,
306
+ params,
307
+ references,
308
+ });
309
+ const response = await deps.requestImage({
310
+ runtime: runtimeResolution.runtime,
311
+ body,
312
+ signal: args.signal,
313
+ });
314
+ if (!response.ok) {
315
+ throw new ImageGenerationError(
316
+ clientFailureCode(response.reason),
317
+ sanitizeImageDiagnostic(response.errorMessage, "Image generation request failed."),
318
+ );
319
+ }
320
+ return persistGeneratedImage({
321
+ generated: response.image,
322
+ params,
323
+ batchIndex,
324
+ toolCallId: args.toolCallId,
325
+ batchSize: params.batchSize,
326
+ imageModel,
327
+ routingModel: `${runtimeResolution.runtime.provider}/${runtimeResolution.runtime.model}`,
328
+ agentDir,
329
+ sessionId: args.ctx.sessionManager.getSessionId(),
330
+ referencesCount: references.length,
331
+ explicitOutput,
332
+ deps,
333
+ });
334
+ })(),
335
+ );
336
+ const settled = await Promise.allSettled(jobs);
337
+ const images: ImageGenerationDetails[] = [];
338
+ const failures: ImageGenerationBatchDetails["failures"] = [];
339
+ for (const [index, result] of settled.entries()) {
340
+ if (result.status === "fulfilled") {
341
+ images.push(result.value);
342
+ } else {
343
+ const error = asImageGenerationError(result.reason);
344
+ failures.push({ index, code: error.code, message: error.message });
345
+ }
346
+ }
347
+
348
+ if (images.length === 0) {
349
+ const firstFailure = settled.find((result) => result.status === "rejected");
350
+ throw asImageGenerationError(firstFailure && firstFailure.status === "rejected" ? firstFailure.reason : "Image generation failed.");
351
+ }
352
+
353
+ if (params.batchSize === 1) {
354
+ const details = images[0]!;
355
+ return {
356
+ details,
357
+ text: formatResultText({
358
+ artifactPath: details.artifactPath,
359
+ outputPath: details.outputPath,
360
+ warning: details.warning,
361
+ edited: details.edited,
362
+ mimeType: details.mimeType,
363
+ width: details.width,
364
+ height: details.height,
365
+ }),
366
+ };
367
+ }
368
+
369
+ const batchDetails: ImageGenerationBatchDetails = {
370
+ batch: true,
371
+ requestedCount: params.batchSize,
372
+ succeededCount: images.length,
373
+ failedCount: failures.length,
374
+ images,
375
+ ...(failures.length > 0 ? { failures } : {}),
376
+ };
377
+ return { details: batchDetails, text: formatBatchResultText(batchDetails) };
378
+ } finally {
379
+ deps.clearReferences(references);
380
+ }
381
+ };
382
+ }
383
+
384
+ export const executeImageGeneration = createImageGenerationExecutor();