editmamei 0.17.4 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/NOTICES.md +43 -0
  2. package/README.md +91 -8
  3. package/dist/api/extendscript/_helpers.js +8 -0
  4. package/dist/bin/editmamei-core-darwin-arm64 +0 -0
  5. package/dist/bin/editmamei-core-darwin-x64 +0 -0
  6. package/dist/bin/editmamei-core-win-x64.exe +0 -0
  7. package/dist/cli/clients/json-config.js +1 -1
  8. package/dist/core/server.js +92 -24
  9. package/dist/core/tool-groups.js +178 -0
  10. package/dist/core/tool-tiers.js +34 -48
  11. package/dist/delivery/bundle.js +8 -0
  12. package/dist/delivery/config.js +23 -0
  13. package/dist/delivery/provision.js +30 -2
  14. package/dist/delivery/store.js +40 -1
  15. package/dist/detection/detect-active-doc.js +84 -0
  16. package/dist/detection/detection-client.js +62 -0
  17. package/dist/detection/face-contour-geometry.js +137 -0
  18. package/dist/detection/face-detector.js +34 -0
  19. package/dist/detection/face-feature-geometry.js +180 -0
  20. package/dist/detection/geometry.js +35 -0
  21. package/dist/detection/landmark-detection-client.js +38 -0
  22. package/dist/detection/landmark-detector.js +63 -0
  23. package/dist/detection/landmark-spec.js +29 -0
  24. package/dist/detection/mesh-face.js +36 -0
  25. package/dist/detection/object-detector.js +135 -0
  26. package/dist/detection/runtime.js +46 -0
  27. package/dist/license/env-activation.js +46 -7
  28. package/dist/models/dfine-s-coco-quant.onnx +0 -0
  29. package/dist/models/ultraface-rfb-320.onnx +0 -0
  30. package/dist/modules/ce/index.js +23 -5
  31. package/dist/perception/facets.js +148 -0
  32. package/dist/perception/region-precompute.js +122 -0
  33. package/dist/perception/region-scorer.js +151 -0
  34. package/dist/perception/scene-model.js +156 -0
  35. package/dist/perception/select-recipes.js +493 -0
  36. package/dist/perception/sky-ground-flood.js +234 -0
  37. package/dist/perception/sky-mask-transfer.js +77 -0
  38. package/dist/platform/macos-executor.js +0 -1
  39. package/dist/platform/windows-executor.js +6 -1
  40. package/dist/preview/coordinate-grid.js +250 -0
  41. package/dist/skills/editmamei-skill.zip +0 -0
  42. package/dist/telemetry/events.js +2 -1
  43. package/dist/telemetry/sanitize.js +15 -1
  44. package/dist/tools/adjustment-tools.js +46 -51
  45. package/dist/tools/detection-tools.js +209 -0
  46. package/dist/tools/document-tools.js +41 -30
  47. package/dist/tools/filter-tools.js +616 -101
  48. package/dist/tools/guide-tools.js +186 -0
  49. package/dist/tools/history-tools.js +1 -39
  50. package/dist/tools/image-tools.js +88 -0
  51. package/dist/tools/inspect-tools.js +86 -0
  52. package/dist/tools/layer-properties-tools.js +195 -106
  53. package/dist/tools/layer-tools.js +55 -14
  54. package/dist/tools/layer-transform-tools.js +236 -73
  55. package/dist/tools/metadata-tools.js +2 -32
  56. package/dist/tools/object-tools.js +193 -0
  57. package/dist/tools/overview-tools.js +69 -43
  58. package/dist/tools/path-tools.js +274 -0
  59. package/dist/tools/portrait-tools.js +146 -0
  60. package/dist/tools/preview-tools.js +78 -3
  61. package/dist/tools/retouch-tools.js +45 -33
  62. package/dist/tools/scene-tools.js +401 -0
  63. package/dist/tools/selection-tools.js +766 -228
  64. package/dist/tools/text-on-object-tools.js +225 -0
  65. package/dist/tools/text-tools.js +47 -56
  66. package/dist/tools/transform-canvas-tools.js +138 -0
  67. package/dist/tools/vector-mask-tools.js +116 -0
  68. package/dist/version.js +1 -1
  69. package/package.json +4 -2
@@ -603,13 +603,32 @@ const shadowsHighlightsSchema = {
603
603
  },
604
604
  },
605
605
  };
606
+ const APPLY_ADJUSTMENT_TYPES = ['shadows_highlights', 'equalize', 'color_lookup'];
607
+ const APPLY_ADJUSTMENT_INPUT_SCHEMA = {
608
+ type: 'object',
609
+ properties: {
610
+ type: {
611
+ type: 'string',
612
+ enum: [...APPLY_ADJUSTMENT_TYPES],
613
+ description: 'Which destructive adjustment to bake (none exist as adjustment layers in PS). ' +
614
+ 'shadows_highlights: one-pass shadow/highlight recovery (shadow_amount, highlight_amount, …). ' +
615
+ 'equalize: parameter-free histogram equalization. ' +
616
+ 'color_lookup: bake a 3DLUT grade — cl_lut_name REQUIRED. ' +
617
+ 'All auto-duplicate the active layer by default (apply_to_active_layer:true to bake in place).',
618
+ },
619
+ ...shadowsHighlightsSchema.properties,
620
+ ...equalizeSchema.properties,
621
+ ...colorLookupBakeSchema.properties,
622
+ },
623
+ required: ['type'],
624
+ };
606
625
  export function createAdjustmentTools(connection, snippetClient) {
607
626
  return [
608
627
  {
609
628
  tool: {
610
- name: 'photoshop_apply_shadows_highlights',
611
- description: "Recover blown highlights and crushed shadows in a single pass. **Photoshop does not ship Shadows/Highlights as an adjustment layer** — it only exists as the destructive Image > Adjustments > Shadows/Highlights command. By default this tool applies it to a DUPLICATE of the active layer (auto-duplicate-first per Bundle O 2026-06-01), so the original is preserved and you can revert by deleting the copy. Pass `apply_to_active_layer: true` to bake into the original. Auto-rasterizes text/smart-object layers in the path that runs the filter. Defaults reproduce Adobe's dialog defaults (35 shadow amount, 0 highlight amount, +20 color correction).",
612
- inputSchema: shadowsHighlightsSchema,
629
+ name: 'photoshop_apply_adjustment',
630
+ description: "Apply a DESTRUCTIVE tonal adjustment that Photoshop does NOT offer as an adjustment layer — chosen via `type`. Runs on a DUPLICATE of the active layer by default (auto-duplicate-first per Bundle O — the original is preserved; revert by deleting the copy); pass `apply_to_active_layer: true` to bake in place. Auto-rasterizes text/smart-object layers. `shadows_highlights` recovers blown highlights + crushed shadows in one pass (defaults match Adobe's dialog: 35 shadow amount, +20 color correction); `equalize` stretches/flattens the histogram (parameter-free); `color_lookup` bakes a 3DLUT grade (cl_lut_name required — leaf name of a file in Presets/3DLUTs/ or an absolute .cube/.3dl/.look path). For EDITABLE tonal/color work, prefer photoshop_add_adjustment_layer.",
631
+ inputSchema: APPLY_ADJUSTMENT_INPUT_SCHEMA,
613
632
  outputSchema: {
614
633
  type: 'object',
615
634
  properties: {
@@ -622,52 +641,6 @@ export function createAdjustmentTools(connection, snippetClient) {
622
641
  highlight_radius: { type: 'number' },
623
642
  color_correction: { type: 'number' },
624
643
  midtone_contrast: { type: 'number' },
625
- target_was_copy: { type: 'boolean' },
626
- target_layer_name: { type: 'string' },
627
- original_layer_name: { type: 'string' },
628
- context: { type: 'object' },
629
- },
630
- },
631
- annotations: {
632
- title: 'Shadows/Highlights (destructive, auto-duplicates)',
633
- destructiveHint: true,
634
- idempotentHint: false,
635
- },
636
- },
637
- handler: async (args) => applyShadowsHighlights(connection, snippetClient, args),
638
- },
639
- {
640
- tool: {
641
- name: 'photoshop_apply_equalize',
642
- description: "Equalize the active layer's tonal distribution — stretches the histogram so it covers the full 0-255 range and tries to flatten the distribution. **Photoshop does not ship Equalize as an adjustment layer** — it only exists as the destructive Image > Adjustments > Equalize command. By default this tool applies it to a DUPLICATE of the active layer (auto-duplicate-first per Bundle O), so the original is preserved and you can revert by deleting the copy. Pass `apply_to_active_layer: true` to bake into the original. Auto-rasterizes text/smart-object layers. Parameter-free — the operation IS the action.",
643
- inputSchema: equalizeSchema,
644
- outputSchema: {
645
- type: 'object',
646
- properties: {
647
- filter: { type: 'string' },
648
- target_was_copy: { type: 'boolean' },
649
- target_layer_name: { type: 'string' },
650
- original_layer_name: { type: 'string' },
651
- context: { type: 'object' },
652
- },
653
- },
654
- annotations: {
655
- title: 'Equalize (destructive, auto-duplicates)',
656
- destructiveHint: true,
657
- idempotentHint: false,
658
- },
659
- },
660
- handler: async (args) => applyEqualize(connection, snippetClient, args),
661
- },
662
- {
663
- tool: {
664
- name: 'photoshop_apply_color_lookup',
665
- description: 'Apply a 3DLUT preset as a destructive color grade, baked onto a duplicate of the active layer (the original is preserved). Pass cl_lut_name as the leaf name of any LUT in Photoshop\'s Presets/3DLUTs/ folder (e.g. "FallColors.look", "Kodak 5205 Fuji 3510 (by Adobe).cube", "TealOrangePlusContrast.3DL") or an absolute path to a .cube/.3dl/.look file. Use for color grading where you don\'t need the LUT to stay editable. NOTE: the non-destructive `add_adjustment_layer type=color_lookup` form CANNOT load a LUT via scripting (PS requires an internal parsed-profile blob no scripting API generates; see docs/20260603-color-lookup-limitation.md) — this bake form is the only working LUT path. For an editable grade, compose selective_color + color_balance + curves to mimic the look.',
666
- inputSchema: colorLookupBakeSchema,
667
- outputSchema: {
668
- type: 'object',
669
- properties: {
670
- filter: { type: 'string' },
671
644
  lut_path: { type: 'string' },
672
645
  lut_format: { type: 'string' },
673
646
  target_was_copy: { type: 'boolean' },
@@ -677,12 +650,12 @@ export function createAdjustmentTools(connection, snippetClient) {
677
650
  },
678
651
  },
679
652
  annotations: {
680
- title: 'Apply Color Lookup (destructive, auto-duplicates)',
653
+ title: 'Apply Adjustment (destructive, auto-duplicates)',
681
654
  destructiveHint: true,
682
655
  idempotentHint: false,
683
656
  },
684
657
  },
685
- handler: async (args) => applyColorLookup(connection, snippetClient, args),
658
+ handler: async (args) => applyAdjustment(connection, snippetClient, args),
686
659
  },
687
660
  {
688
661
  tool: {
@@ -716,6 +689,28 @@ export function createAdjustmentTools(connection, snippetClient) {
716
689
  },
717
690
  ];
718
691
  }
692
+ async function applyAdjustment(connection, snippetClient, rawArgs) {
693
+ const type = rawArgs.type;
694
+ const { type: _omit, ...rest } = rawArgs;
695
+ switch (type) {
696
+ case 'shadows_highlights':
697
+ return applyShadowsHighlights(connection, snippetClient, rest);
698
+ case 'equalize':
699
+ return applyEqualize(connection, snippetClient, rest);
700
+ case 'color_lookup':
701
+ return applyColorLookup(connection, snippetClient, rest);
702
+ default:
703
+ return {
704
+ content: [
705
+ {
706
+ type: 'text',
707
+ text: `Error: unknown adjustment type "${String(type)}". Allowed: ${APPLY_ADJUSTMENT_TYPES.join(', ')}.`,
708
+ },
709
+ ],
710
+ isError: true,
711
+ };
712
+ }
713
+ }
719
714
  const FORWARDED_KEYS = new Set([
720
715
  'curves_preset',
721
716
  'black_point',
@@ -0,0 +1,209 @@
1
+ import { decode, encode } from 'jpeg-js';
2
+ import { validateArgs } from '../utils/validate.js';
3
+ import { detectActiveDoc } from '../detection/detect-active-doc.js';
4
+ import { OnnxDetectionClient, } from '../detection/detection-client.js';
5
+ const detectSchema = {
6
+ type: 'object',
7
+ properties: {
8
+ target: {
9
+ type: 'string',
10
+ enum: ['faces', 'objects', 'both'],
11
+ default: 'both',
12
+ description: "What to detect: 'faces' (forward-facing faces), 'objects' (COCO-80 classes: person, dog, car, chair, …), or 'both'.",
13
+ },
14
+ max_dimension: {
15
+ type: 'number',
16
+ default: 1024,
17
+ minimum: 256,
18
+ maximum: 4096,
19
+ description: 'Long-edge pixels of the JPEG the detectors run on. Smaller is faster; the models downscale internally so 1024 is plenty. Returned coordinates are always in full document pixels regardless.',
20
+ },
21
+ face_threshold: {
22
+ type: 'number',
23
+ default: 0.7,
24
+ minimum: 0,
25
+ maximum: 1,
26
+ description: 'Minimum face confidence (0–1).',
27
+ },
28
+ object_threshold: {
29
+ type: 'number',
30
+ default: 0.4,
31
+ minimum: 0,
32
+ maximum: 1,
33
+ description: 'Minimum object confidence (0–1).',
34
+ },
35
+ max_objects: {
36
+ type: 'number',
37
+ default: 50,
38
+ minimum: 1,
39
+ maximum: 300,
40
+ description: 'Cap on returned objects, highest-confidence first.',
41
+ },
42
+ annotate: {
43
+ type: 'boolean',
44
+ default: true,
45
+ description: 'Return an annotated preview with the detected boxes drawn (faces cyan, objects magenta) so you can visually confirm the detections.',
46
+ },
47
+ },
48
+ };
49
+ function drawBoxes(bytes, boxes) {
50
+ const img = decode(bytes, { useTArray: true, formatAsRGBA: true });
51
+ const { width: w, height: h, data } = img;
52
+ const thickness = Math.max(2, Math.round(Math.max(w, h) / 400));
53
+ const setPx = (x, y, r, g, b) => {
54
+ if (x < 0 || y < 0 || x >= w || y >= h)
55
+ return;
56
+ const i = (y * w + x) * 4;
57
+ data[i] = r;
58
+ data[i + 1] = g;
59
+ data[i + 2] = b;
60
+ data[i + 3] = 255;
61
+ };
62
+ for (const { bbox, rgb } of boxes) {
63
+ const x1 = Math.round(bbox[0]);
64
+ const y1 = Math.round(bbox[1]);
65
+ const x2 = Math.round(bbox[2]);
66
+ const y2 = Math.round(bbox[3]);
67
+ for (let x = x1; x <= x2; x++)
68
+ for (let k = 0; k < thickness; k++) {
69
+ setPx(x, y1 + k, rgb[0], rgb[1], rgb[2]);
70
+ setPx(x, y2 - k, rgb[0], rgb[1], rgb[2]);
71
+ }
72
+ for (let y = y1; y <= y2; y++)
73
+ for (let k = 0; k < thickness; k++) {
74
+ setPx(x1 + k, y, rgb[0], rgb[1], rgb[2]);
75
+ setPx(x2 - k, y, rgb[0], rgb[1], rgb[2]);
76
+ }
77
+ }
78
+ return encode({ data, width: w, height: h }, 88).data;
79
+ }
80
+ function summarizeObjects(objects) {
81
+ if (!objects || objects.length === 0)
82
+ return '';
83
+ const counts = new Map();
84
+ for (const o of objects)
85
+ counts.set(o.label, (counts.get(o.label) ?? 0) + 1);
86
+ return [...counts.entries()].map(([l, n]) => (n > 1 ? `${l}×${n}` : l)).join(', ');
87
+ }
88
+ async function detect(connection, client, rawArgs) {
89
+ try {
90
+ const args = validateArgs(detectSchema, rawArgs);
91
+ const target = args.target ?? 'both';
92
+ const annotate = args.annotate ?? true;
93
+ const wantFaces = target === 'faces' || target === 'both';
94
+ const wantObjects = target === 'objects' || target === 'both';
95
+ const det = await detectActiveDoc(connection, client, {
96
+ faces: wantFaces,
97
+ objects: wantObjects,
98
+ maxDimension: args.max_dimension,
99
+ faceThreshold: args.face_threshold,
100
+ objectThreshold: args.object_threshold,
101
+ maxObjects: args.max_objects,
102
+ });
103
+ const mapped = det.result;
104
+ const content = [];
105
+ if (annotate) {
106
+ try {
107
+ const boxes = [
108
+ ...(det.raw.faces ?? []).map((f) => ({
109
+ bbox: f.bbox,
110
+ rgb: [0, 220, 255],
111
+ })),
112
+ ...(det.raw.objects ?? []).map((o) => ({
113
+ bbox: o.bbox,
114
+ rgb: [255, 0, 220],
115
+ })),
116
+ ];
117
+ const annotated = drawBoxes(det.exportBytes, boxes);
118
+ content.push({
119
+ type: 'image',
120
+ data: annotated.toString('base64'),
121
+ mimeType: 'image/jpeg',
122
+ });
123
+ }
124
+ catch {
125
+ }
126
+ }
127
+ const faceCount = mapped.faces?.length ?? 0;
128
+ const objCount = mapped.objects?.length ?? 0;
129
+ const parts = [];
130
+ if (wantFaces)
131
+ parts.push(`${faceCount} face${faceCount === 1 ? '' : 's'}`);
132
+ if (wantObjects) {
133
+ const tally = summarizeObjects(mapped.objects);
134
+ parts.push(`${objCount} object${objCount === 1 ? '' : 's'}${tally ? ` (${tally})` : ''}`);
135
+ }
136
+ content.push({
137
+ type: 'text',
138
+ text: `Detected ${parts.join(', ')} in the active document (${det.docWidth}×${det.docHeight}). ` +
139
+ `All bounding boxes are [x1, y1, x2, y2] in document pixels.`,
140
+ });
141
+ return {
142
+ content,
143
+ structuredContent: {
144
+ image: mapped.image,
145
+ backends: mapped.backends,
146
+ ...(mapped.faces ? { faces: mapped.faces } : {}),
147
+ ...(mapped.objects ? { objects: mapped.objects } : {}),
148
+ context: det.context,
149
+ },
150
+ };
151
+ }
152
+ catch (error) {
153
+ const msg = error instanceof Error ? error.message : String(error);
154
+ return {
155
+ content: [{ type: 'text', text: `Error during detection: ${msg}` }],
156
+ isError: true,
157
+ };
158
+ }
159
+ }
160
+ export function createDetectionTools(connection, _snippetClient, client = new OnnxDetectionClient()) {
161
+ return [
162
+ {
163
+ tool: {
164
+ name: 'photoshop_detect',
165
+ description: 'Locate faces and/or COCO-80 objects (person, dog, car, chair, sofa, …) in the active document using LOCAL on-device computer vision — the image is never sent anywhere. Returns labeled bounding boxes in DOCUMENT-pixel space plus an annotated preview for visual confirmation. Use this to gain semantic scene awareness ("there are 2 people and a dog") and real coordinates before any spatially-targeted edit — far more reliable than estimating positions from a preview. `target` selects faces / objects / both. Read-only: renders a throwaway duplicate, never modifies the working document. Boxes are [x1, y1, x2, y2]. Validate surprising results against the annotated image before acting on them.',
166
+ inputSchema: detectSchema,
167
+ outputSchema: {
168
+ type: 'object',
169
+ properties: {
170
+ image: {
171
+ type: 'object',
172
+ properties: { width: { type: 'number' }, height: { type: 'number' } },
173
+ },
174
+ backends: { type: 'object' },
175
+ faces: {
176
+ type: 'array',
177
+ items: {
178
+ type: 'object',
179
+ properties: {
180
+ bbox: { type: 'array', items: { type: 'number' } },
181
+ confidence: { type: 'number' },
182
+ },
183
+ },
184
+ },
185
+ objects: {
186
+ type: 'array',
187
+ items: {
188
+ type: 'object',
189
+ properties: {
190
+ label: { type: 'string' },
191
+ class_id: { type: 'number' },
192
+ bbox: { type: 'array', items: { type: 'number' } },
193
+ confidence: { type: 'number' },
194
+ },
195
+ },
196
+ },
197
+ context: { type: 'object' },
198
+ },
199
+ },
200
+ annotations: {
201
+ title: 'Detect Faces & Objects',
202
+ readOnlyHint: true,
203
+ idempotentHint: true,
204
+ },
205
+ },
206
+ handler: async (args) => detect(connection, client, args),
207
+ },
208
+ ];
209
+ }
@@ -132,6 +132,22 @@ const exportPngSchema = {
132
132
  },
133
133
  required: ['output_path'],
134
134
  };
135
+ const EXPORT_FORMATS = ['jpeg', 'png'];
136
+ const exportInputSchema = {
137
+ type: 'object',
138
+ properties: {
139
+ format: {
140
+ type: 'string',
141
+ enum: [...EXPORT_FORMATS],
142
+ description: 'Output image format. jpeg: quality 0-100, optional convert_to_srgb + embed_color_profile. ' +
143
+ 'png: transparent_background (alpha) or flattened-on-white, compression 0-9. ' +
144
+ 'Both take output_path (required) and optional long_edge_px (downscale only).',
145
+ },
146
+ ...exportJpegSchema.properties,
147
+ ...exportPngSchema.properties,
148
+ },
149
+ required: ['format', 'output_path'],
150
+ };
135
151
  export function createDocumentTools(connection, snippetClient) {
136
152
  return [
137
153
  {
@@ -232,9 +248,9 @@ export function createDocumentTools(connection, snippetClient) {
232
248
  },
233
249
  {
234
250
  tool: {
235
- name: 'photoshop_export_jpeg',
236
- description: 'Export the active document as a flattened JPEG. Optionally converts to sRGB and resizes so the long edge equals a target pixel count (downscale only). Operates on a duplicate — working document is unchanged.',
237
- inputSchema: exportJpegSchema,
251
+ name: 'photoshop_export',
252
+ description: 'Export the active document to a flattened image file — JPEG or PNG, chosen via `format`. Operates on a duplicate, so the working document is unchanged. Optionally downscales so the long edge equals long_edge_px. JPEG: quality 0-100 (Save-As scale), optional sRGB convert + profile embed. PNG: transparent background (alpha preserved) or flattened onto white, compression 0-9.',
253
+ inputSchema: exportInputSchema,
238
254
  outputSchema: {
239
255
  type: 'object',
240
256
  properties: {
@@ -244,47 +260,42 @@ export function createDocumentTools(connection, snippetClient) {
244
260
  height_px: { type: 'number' },
245
261
  quality: { type: 'number' },
246
262
  quality_ps_scale: { type: 'number' },
247
- context: { type: 'object' },
248
- },
249
- required: ['success'],
250
- },
251
- annotations: {
252
- title: 'Export JPEG (pipeline)',
253
- destructiveHint: true,
254
- openWorldHint: true,
255
- idempotentHint: true,
256
- },
257
- },
258
- handler: async (args) => exportJpegPipeline(connection, snippetClient, args),
259
- },
260
- {
261
- tool: {
262
- name: 'photoshop_export_png',
263
- description: 'Export the active document as a PNG. Choose transparent background (alpha preserved) or flattened onto white. Optionally resizes (downscale only). Operates on a duplicate — working document is unchanged.',
264
- inputSchema: exportPngSchema,
265
- outputSchema: {
266
- type: 'object',
267
- properties: {
268
- success: { type: 'boolean' },
269
- exported_to: { type: 'string' },
270
- width_px: { type: 'number' },
271
- height_px: { type: 'number' },
272
263
  transparent: { type: 'boolean' },
273
264
  context: { type: 'object' },
274
265
  },
275
266
  required: ['success'],
276
267
  },
277
268
  annotations: {
278
- title: 'Export PNG (pipeline)',
269
+ title: 'Export Image (pipeline)',
279
270
  destructiveHint: true,
280
271
  openWorldHint: true,
281
272
  idempotentHint: true,
282
273
  },
283
274
  },
284
- handler: async (args) => exportPngPipeline(connection, snippetClient, args),
275
+ handler: async (args) => exportImage(connection, snippetClient, args),
285
276
  },
286
277
  ];
287
278
  }
279
+ async function exportImage(connection, snippetClient, rawArgs) {
280
+ const format = rawArgs.format;
281
+ const { format: _omit, ...rest } = rawArgs;
282
+ switch (format) {
283
+ case 'jpeg':
284
+ return exportJpegPipeline(connection, snippetClient, rest);
285
+ case 'png':
286
+ return exportPngPipeline(connection, snippetClient, rest);
287
+ default:
288
+ return {
289
+ content: [
290
+ {
291
+ type: 'text',
292
+ text: `Error: unknown export format "${String(format)}". Allowed: ${EXPORT_FORMATS.join(', ')}.`,
293
+ },
294
+ ],
295
+ isError: true,
296
+ };
297
+ }
298
+ }
288
299
  async function createDocument(connection, snippetClient, rawArgs) {
289
300
  try {
290
301
  const args = validateArgs(createDocumentSchema, rawArgs);