@midscene/shared 1.10.5 → 1.10.6-beta-20260717061640.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,9 +1,16 @@
1
1
  import assert from 'node:assert';
2
- import type { PhotonImage as PhotonImageType } from '@silvia-odwyer/photon-node';
2
+ import type { PhotonImage as PhotonImageType } from '@silvia-odwyer/photon';
3
3
  import { NodeType } from '../constants';
4
4
  import type { BaseElement, Rect } from '../types';
5
+ import { ifInNode } from '../utils';
5
6
  import getPhoton from './get-photon';
6
- import { photonFromBase64, photonToBase64 } from './transform';
7
+ import getSharp from './get-sharp';
8
+ import {
9
+ createImgBase64ByFormat,
10
+ parseBase64,
11
+ photonFromBase64,
12
+ photonToBase64,
13
+ } from './transform';
7
14
 
8
15
  // Simple 5x7 bitmap font for digits 0-9
9
16
  const DIGIT_FONT: Record<string, number[][]> = {
@@ -715,6 +722,47 @@ const createSvgOverlay = async (
715
722
  return overlayPixels;
716
723
  };
717
724
 
725
+ async function decodeImageWithSharp(
726
+ inputImgBase64: string,
727
+ size?: { width: number; height: number },
728
+ ) {
729
+ const { body } = parseBase64(inputImgBase64);
730
+ const Sharp = await getSharp();
731
+ let pipeline = Sharp(Buffer.from(body, 'base64'));
732
+ if (size) {
733
+ pipeline = pipeline.resize(size.width, size.height, {
734
+ fit: 'fill',
735
+ kernel: 'nearest',
736
+ });
737
+ }
738
+ const { data, info } = await pipeline
739
+ .ensureAlpha()
740
+ .raw()
741
+ .toBuffer({ resolveWithObject: true });
742
+ if (!info.width || !info.height || info.channels !== 4) {
743
+ throw new Error('Image processing failed to produce RGBA pixels');
744
+ }
745
+ return {
746
+ pixels: new Uint8Array(data),
747
+ width: info.width,
748
+ height: info.height,
749
+ };
750
+ }
751
+
752
+ async function encodeRgbaWithSharp(
753
+ pixels: Uint8Array,
754
+ width: number,
755
+ height: number,
756
+ ) {
757
+ const Sharp = await getSharp();
758
+ const output = await Sharp(Buffer.from(pixels), {
759
+ raw: { width, height, channels: 4 },
760
+ })
761
+ .jpeg({ quality: 90, chromaSubsampling: '4:4:4' })
762
+ .toBuffer();
763
+ return createImgBase64ByFormat('jpeg', output.toString('base64'));
764
+ }
765
+
718
766
  export const compositeElementInfoImg = async (options: {
719
767
  inputImgBase64: string;
720
768
  elementsPositionInfo: Array<ElementForOverlay>;
@@ -725,6 +773,27 @@ export const compositeElementInfoImg = async (options: {
725
773
  prompt?: string;
726
774
  }) => {
727
775
  assert(options.inputImgBase64, 'inputImgBase64 is required');
776
+ if (ifInNode) {
777
+ const { pixels, width, height } = await decodeImageWithSharp(
778
+ options.inputImgBase64,
779
+ options.size,
780
+ );
781
+ const overlayPixels = await createSvgOverlay(
782
+ options.elementsPositionInfo,
783
+ width,
784
+ height,
785
+ options.annotationPadding,
786
+ options.borderThickness,
787
+ options.prompt,
788
+ options.centerPoint,
789
+ );
790
+ return encodeRgbaWithSharp(
791
+ blendPixels(pixels, overlayPixels, width, height),
792
+ width,
793
+ height,
794
+ );
795
+ }
796
+
728
797
  const { PhotonImage, SamplingFilter, resize } = await getPhoton();
729
798
 
730
799
  let width = 0;
@@ -800,6 +869,27 @@ export const compositePointMarkerImg = async (options: {
800
869
  indexId?: number;
801
870
  }) => {
802
871
  assert(options.inputImgBase64, 'inputImgBase64 is required');
872
+ if (ifInNode) {
873
+ const { pixels, width, height } = await decodeImageWithSharp(
874
+ options.inputImgBase64,
875
+ options.size,
876
+ );
877
+ const overlayPixels = new Uint8Array(width * height * 4);
878
+ drawPointMarker(
879
+ overlayPixels,
880
+ width,
881
+ height,
882
+ options.point,
883
+ options.radius ?? 14,
884
+ options.indexId ?? 1,
885
+ );
886
+ return encodeRgbaWithSharp(
887
+ blendPixels(pixels, overlayPixels, width, height),
888
+ width,
889
+ height,
890
+ );
891
+ }
892
+
803
893
  const { PhotonImage, SamplingFilter, resize } = await getPhoton();
804
894
 
805
895
  let width = 0;
@@ -1,5 +1,5 @@
1
1
  import { getDebug } from '../logger';
2
- import { ifInBrowser, ifInNode, ifInWorker } from '../utils';
2
+ import { ifInBrowser, ifInWorker } from '../utils';
3
3
 
4
4
  const debug = getDebug('img');
5
5
 
@@ -8,31 +8,25 @@ let isInitialized = false;
8
8
  let usingCanvasFallback = false;
9
9
 
10
10
  export default async function getPhoton(): Promise<{
11
- PhotonImage: typeof import('@silvia-odwyer/photon-node').PhotonImage;
12
- SamplingFilter: typeof import('@silvia-odwyer/photon-node').SamplingFilter;
13
- resize: typeof import('@silvia-odwyer/photon-node').resize;
14
- crop: typeof import('@silvia-odwyer/photon-node').crop;
15
- open_image: typeof import('@silvia-odwyer/photon-node').open_image;
16
- base64_to_image: typeof import('@silvia-odwyer/photon-node').base64_to_image;
17
- padding_uniform: typeof import('@silvia-odwyer/photon-node').padding_uniform;
18
- padding_left: typeof import('@silvia-odwyer/photon-node').padding_left;
19
- padding_right: typeof import('@silvia-odwyer/photon-node').padding_right;
20
- padding_top: typeof import('@silvia-odwyer/photon-node').padding_top;
21
- padding_bottom: typeof import('@silvia-odwyer/photon-node').padding_bottom;
22
- watermark: typeof import('@silvia-odwyer/photon-node').watermark;
23
- Rgba: typeof import('@silvia-odwyer/photon-node').Rgba;
11
+ PhotonImage: typeof import('@silvia-odwyer/photon').PhotonImage;
12
+ SamplingFilter: typeof import('@silvia-odwyer/photon').SamplingFilter;
13
+ resize: typeof import('@silvia-odwyer/photon').resize;
14
+ crop: typeof import('@silvia-odwyer/photon').crop;
15
+ open_image: typeof import('@silvia-odwyer/photon').open_image;
16
+ base64_to_image: typeof import('@silvia-odwyer/photon').base64_to_image;
17
+ padding_uniform: typeof import('@silvia-odwyer/photon').padding_uniform;
18
+ padding_left: typeof import('@silvia-odwyer/photon').padding_left;
19
+ padding_right: typeof import('@silvia-odwyer/photon').padding_right;
20
+ padding_top: typeof import('@silvia-odwyer/photon').padding_top;
21
+ padding_bottom: typeof import('@silvia-odwyer/photon').padding_bottom;
22
+ watermark: typeof import('@silvia-odwyer/photon').watermark;
23
+ Rgba: typeof import('@silvia-odwyer/photon').Rgba;
24
24
  }> {
25
25
  if (photonModule && isInitialized) {
26
26
  return photonModule;
27
27
  }
28
28
 
29
- const env = ifInBrowser
30
- ? 'browser'
31
- : ifInWorker
32
- ? 'worker'
33
- : ifInNode
34
- ? 'node'
35
- : 'unknown';
29
+ const env = ifInBrowser ? 'browser' : ifInWorker ? 'worker' : 'unknown';
36
30
  debug(`Loading photon module in ${env} environment`);
37
31
 
38
32
  // Try to load Photon first
@@ -46,10 +40,8 @@ export default async function getPhoton(): Promise<{
46
40
  }
47
41
  debug('Photon loaded: @silvia-odwyer/photon (browser/worker)');
48
42
  photonModule = photon;
49
- } else if (ifInNode) {
50
- // Node.js environment: use @silvia-odwyer/photon-node
51
- photonModule = await import('@silvia-odwyer/photon-node');
52
- debug('Photon loaded: @silvia-odwyer/photon-node (node)');
43
+ } else {
44
+ throw new Error('Photon is only available in browser environments');
53
45
  }
54
46
 
55
47
  // verify that the critical functions exist (only for Photon, not Canvas fallback)
package/src/img/info.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import assert from 'node:assert';
2
2
  import { Buffer } from 'node:buffer';
3
3
  import type { Size } from '../types';
4
+ import { ifInNode } from '../utils';
4
5
  import getPhoton from './get-photon';
6
+ import getSharp from './get-sharp';
5
7
 
6
8
  export interface ImageInfo extends Size {}
7
9
 
@@ -15,7 +17,6 @@ export interface ImageInfo extends Size {}
15
17
  export async function imageInfoOfBase64(
16
18
  imageBase64: string,
17
19
  ): Promise<ImageInfo> {
18
- const { PhotonImage } = await getPhoton();
19
20
  const base64Data = imageBase64
20
21
  .replace(/^data:image\/\w+;base64,/, '')
21
22
  .replace(/\s/g, '');
@@ -26,6 +27,25 @@ export async function imageInfoOfBase64(
26
27
  );
27
28
  const imageBuffer = Buffer.from(base64Data, 'base64');
28
29
  assert(isValidImageBuffer(imageBuffer), 'Invalid image: unsupported format');
30
+ if (ifInNode) {
31
+ let metadata;
32
+ try {
33
+ const Sharp = await getSharp();
34
+ metadata = await Sharp(imageBuffer).metadata();
35
+ } catch (error) {
36
+ throw new Error(
37
+ `Invalid image: failed to decode base64 data (${error instanceof Error ? error.message : String(error)})`,
38
+ { cause: error },
39
+ );
40
+ }
41
+ assert(
42
+ metadata.width && metadata.height,
43
+ 'Invalid image: cannot get width or height',
44
+ );
45
+ return { width: metadata.width, height: metadata.height };
46
+ }
47
+
48
+ const { PhotonImage } = await getPhoton();
29
49
  // Support both sync (Photon) and async (Canvas fallback) versions
30
50
  let result: ReturnType<typeof PhotonImage.new_from_base64>;
31
51
  try {
@@ -3,7 +3,7 @@ import { Buffer } from 'node:buffer';
3
3
  import { readFileSync } from 'node:fs';
4
4
  import { writeFile } from 'node:fs/promises';
5
5
  import path from 'node:path';
6
- import type { PhotonImage as PhotonImageType } from '@silvia-odwyer/photon-node';
6
+ import type { PhotonImage as PhotonImageType } from '@silvia-odwyer/photon';
7
7
  import { getDebug } from '../logger';
8
8
  import type { Rect } from '../types';
9
9
  import { ifInNode } from '../utils';
@@ -61,44 +61,36 @@ export async function resizeAndConvertImgBuffer(
61
61
  imgDebug(`resizeImg start, target size: ${newSize.width}x${newSize.height}`);
62
62
 
63
63
  if (ifInNode) {
64
- // Node.js environment: use Sharp
65
- try {
66
- const Sharp = await getSharp();
67
- const metadata = await Sharp(inputData).metadata();
68
- const { width: originalWidth, height: originalHeight } = metadata;
69
-
70
- if (!originalWidth || !originalHeight) {
71
- throw Error('Undefined width or height from the input image.');
72
- }
73
-
74
- if (
75
- newSize.width === originalWidth &&
76
- newSize.height === originalHeight
77
- ) {
78
- return {
79
- buffer: inputData,
80
- format: inputFormat,
81
- };
82
- }
64
+ const Sharp = await getSharp();
65
+ const metadata = await Sharp(inputData).metadata();
66
+ const { width: originalWidth, height: originalHeight } = metadata;
83
67
 
84
- const resizedBuffer = await Sharp(inputData)
85
- .resize(newSize.width, newSize.height)
86
- .jpeg({ quality: 90 })
87
- .toBuffer();
88
-
89
- const resizeEndTime = Date.now();
90
- imgDebug(
91
- `resizeImg done (Sharp), target size: ${newSize.width}x${newSize.height}, cost: ${resizeEndTime - resizeStartTime}ms`,
92
- );
68
+ if (!originalWidth || !originalHeight) {
69
+ throw Error('Undefined width or height from the input image.');
70
+ }
93
71
 
72
+ if (newSize.width === originalWidth && newSize.height === originalHeight) {
94
73
  return {
95
- buffer: resizedBuffer,
96
- // by Sharp.jpeg()
97
- format: 'jpeg',
74
+ buffer: inputData,
75
+ format: inputFormat,
98
76
  };
99
- } catch (error) {
100
- imgDebug('Sharp failed, falling back to Photon:', error);
101
77
  }
78
+
79
+ const resizedBuffer = await Sharp(inputData)
80
+ .resize(newSize.width, newSize.height)
81
+ .jpeg({ quality: 90 })
82
+ .toBuffer();
83
+
84
+ const resizeEndTime = Date.now();
85
+ imgDebug(
86
+ `resizeImg done (Sharp), target size: ${newSize.width}x${newSize.height}, cost: ${resizeEndTime - resizeStartTime}ms`,
87
+ );
88
+
89
+ return {
90
+ buffer: resizedBuffer,
91
+ // by Sharp.jpeg()
92
+ format: 'jpeg',
93
+ };
102
94
  }
103
95
 
104
96
  // browser environment: use Photon (or Canvas fallback)
@@ -410,6 +402,38 @@ export async function paddingToMatchBlockByBase64(
410
402
  height: number;
411
403
  imageBase64: string;
412
404
  }> {
405
+ if (ifInNode) {
406
+ const { body } = parseBase64(imageBase64);
407
+ const inputBuffer = Buffer.from(body, 'base64');
408
+ const Sharp = await getSharp();
409
+ const metadata = await Sharp(inputBuffer).metadata();
410
+ const width = metadata.width;
411
+ const height = metadata.height;
412
+ if (!width || !height) {
413
+ throw new Error('Failed to get image dimensions');
414
+ }
415
+
416
+ const targetWidth = Math.ceil(width / blockSize) * blockSize;
417
+ const targetHeight = Math.ceil(height / blockSize) * blockSize;
418
+ if (targetWidth === width && targetHeight === height) {
419
+ return { width, height, imageBase64 };
420
+ }
421
+
422
+ const output = await Sharp(inputBuffer)
423
+ .extend({
424
+ right: targetWidth - width,
425
+ bottom: targetHeight - height,
426
+ background: { r: 255, g: 255, b: 255, alpha: 1 },
427
+ })
428
+ .jpeg({ quality: 90 })
429
+ .toBuffer();
430
+ return {
431
+ width: targetWidth,
432
+ height: targetHeight,
433
+ imageBase64: createImgBase64ByFormat('jpeg', output.toString('base64')),
434
+ };
435
+ }
436
+
413
437
  const photonImage = await photonFromBase64(imageBase64);
414
438
  try {
415
439
  const paddedResult = await paddingToMatchBlock(photonImage, blockSize);
@@ -435,6 +459,29 @@ export async function cropByRect(
435
459
  height: number;
436
460
  imageBase64: string;
437
461
  }> {
462
+ if (ifInNode) {
463
+ const { body } = parseBase64(imageBase64);
464
+ const Sharp = await getSharp();
465
+ const left = Math.trunc(rect.left);
466
+ const top = Math.trunc(rect.top);
467
+ const width = Math.trunc(rect.left + rect.width) - left;
468
+ const height = Math.trunc(rect.top + rect.height) - top;
469
+ const output = await Sharp(Buffer.from(body, 'base64'))
470
+ .extract({
471
+ left,
472
+ top,
473
+ width,
474
+ height,
475
+ })
476
+ .jpeg({ quality: 90 })
477
+ .toBuffer();
478
+ return {
479
+ width,
480
+ height,
481
+ imageBase64: createImgBase64ByFormat('jpeg', output.toString('base64')),
482
+ };
483
+ }
484
+
438
485
  const { crop } = await getPhoton();
439
486
  const photonImage = await photonFromBase64(imageBase64);
440
487
  const { left, top, width, height } = rect;
@@ -593,48 +640,43 @@ export async function scaleImage(
593
640
  imgDebug(`scaleImage start, scale factor: ${scale}`);
594
641
 
595
642
  if (ifInNode) {
596
- // Node.js environment: use Sharp
597
- try {
598
- const Sharp = await getSharp();
599
- const metadata = await Sharp(buffer).metadata();
600
- const originalWidth = metadata.width || 0;
601
- const originalHeight = metadata.height || 0;
643
+ const Sharp = await getSharp();
644
+ const metadata = await Sharp(buffer).metadata();
645
+ const originalWidth = metadata.width || 0;
646
+ const originalHeight = metadata.height || 0;
602
647
 
603
- if (originalWidth === 0 || originalHeight === 0) {
604
- throw new Error('Failed to get image dimensions');
605
- }
648
+ if (originalWidth === 0 || originalHeight === 0) {
649
+ throw new Error('Failed to get image dimensions');
650
+ }
606
651
 
607
- const newWidth = Math.round(originalWidth * scale);
608
- const newHeight = Math.round(originalHeight * scale);
609
-
610
- const resizedBuffer = await Sharp(buffer)
611
- .resize(newWidth, newHeight, {
612
- kernel: 'lanczos3',
613
- fit: 'fill',
614
- })
615
- .jpeg({
616
- quality: 90,
617
- })
618
- .toBuffer();
619
-
620
- const scaleEndTime = Date.now();
621
- imgDebug(
622
- `scaleImage done (Sharp): ${originalWidth}x${originalHeight} -> ${newWidth}x${newHeight} (scale=${scale}), cost: ${scaleEndTime - scaleStartTime}ms`,
623
- );
652
+ const newWidth = Math.round(originalWidth * scale);
653
+ const newHeight = Math.round(originalHeight * scale);
654
+
655
+ const resizedBuffer = await Sharp(buffer)
656
+ .resize(newWidth, newHeight, {
657
+ kernel: 'lanczos3',
658
+ fit: 'fill',
659
+ })
660
+ .jpeg({
661
+ quality: 90,
662
+ })
663
+ .toBuffer();
664
+
665
+ const scaleEndTime = Date.now();
666
+ imgDebug(
667
+ `scaleImage done (Sharp): ${originalWidth}x${originalHeight} -> ${newWidth}x${newHeight} (scale=${scale}), cost: ${scaleEndTime - scaleStartTime}ms`,
668
+ );
624
669
 
625
- const base64 = `data:image/jpeg;base64,${resizedBuffer.toString('base64')}`;
670
+ const base64 = `data:image/jpeg;base64,${resizedBuffer.toString('base64')}`;
626
671
 
627
- return {
628
- width: newWidth,
629
- height: newHeight,
630
- imageBase64: base64,
631
- };
632
- } catch (error) {
633
- imgDebug('Sharp failed, falling back to Photon:', error);
634
- }
672
+ return {
673
+ width: newWidth,
674
+ height: newHeight,
675
+ imageBase64: base64,
676
+ };
635
677
  }
636
678
 
637
- // Browser environment or Sharp failed: use Photon (or Canvas fallback)
679
+ // Browser environment: use Photon (or Canvas fallback)
638
680
  const { PhotonImage, SamplingFilter, resize } = await getPhoton();
639
681
  const inputBytes = new Uint8Array(buffer);
640
682
  // Support both sync (Photon) and async (Canvas fallback) versions
package/src/logger.ts CHANGED
@@ -8,28 +8,53 @@ import { ifInNode } from './utils';
8
8
  const topicPrefix = 'midscene';
9
9
  // Map to store file streams
10
10
  const logStreams = new Map<string, fs.WriteStream>();
11
+ // A WriteStream queues every write made after it reports backpressure. The
12
+ // main process can keep running while macOS has deprioritized a backgrounded
13
+ // app, so retaining diagnostic logs here can otherwise grow without bound.
14
+ // Drop best-effort logs until the stream drains instead.
15
+ const backpressuredLogStreams = new Set<string>();
16
+ const unavailableLogStreams = new Set<string>();
11
17
  // Map to store debug instances
12
18
  const debugInstances = new Map<string, DebugFunction>();
13
19
 
14
20
  // Function to get or create a log stream
15
- function getLogStream(topic: string): fs.WriteStream {
21
+ function getLogStream(topic: string): fs.WriteStream | null {
16
22
  const topicFileName = topic.replace(/:/g, '-');
23
+ if (unavailableLogStreams.has(topicFileName)) {
24
+ return null;
25
+ }
17
26
  if (!logStreams.has(topicFileName)) {
18
27
  const logFile = path.join(
19
28
  getMidsceneRunSubDir('log'),
20
29
  `${topicFileName}.log`,
21
30
  );
22
31
  const stream = fs.createWriteStream(logFile, { flags: 'a' });
32
+ // A stream error without a listener terminates the Electron main process.
33
+ // Logging must remain best-effort, so disable this topic after a file error
34
+ // rather than repeatedly queuing writes to a broken stream.
35
+ stream.on('error', () => {
36
+ unavailableLogStreams.add(topicFileName);
37
+ backpressuredLogStreams.delete(topicFileName);
38
+ if (logStreams.get(topicFileName) === stream) {
39
+ logStreams.delete(topicFileName);
40
+ }
41
+ });
23
42
  logStreams.set(topicFileName, stream);
24
43
  }
25
- return logStreams.get(topicFileName)!;
44
+ return logStreams.get(topicFileName) ?? null;
26
45
  }
27
46
 
28
47
  // Function to write log to file
29
48
  function writeLogToFile(topic: string, message: string): void {
30
49
  if (!ifInNode) return;
31
50
 
51
+ const topicFileName = topic.replace(/:/g, '-');
52
+ if (backpressuredLogStreams.has(topicFileName)) {
53
+ return;
54
+ }
55
+
32
56
  const stream = getLogStream(topic);
57
+ if (!stream) return;
33
58
  // Generate ISO format timestamp with local timezone
34
59
  const now = new Date();
35
60
  // Use sv-SE locale to get ISO-like format (YYYY-MM-DD HH:mm:ss)
@@ -47,7 +72,17 @@ function writeLogToFile(topic: string, message: string): void {
47
72
  .padStart(2, '0');
48
73
  const timezoneString = `${sign}${hours}:${minutes}`;
49
74
  const localISOTime = `${isoDate}T${isoTime}.${milliseconds}${timezoneString}`;
50
- stream.write(`[${localISOTime}] ${message}\n`);
75
+ try {
76
+ if (!stream.write(`[${localISOTime}] ${message}\n`)) {
77
+ backpressuredLogStreams.add(topicFileName);
78
+ stream.once('drain', () => {
79
+ backpressuredLogStreams.delete(topicFileName);
80
+ });
81
+ }
82
+ } catch {
83
+ unavailableLogStreams.add(topicFileName);
84
+ backpressuredLogStreams.delete(topicFileName);
85
+ }
51
86
  }
52
87
 
53
88
  export type DebugFunction = (...args: unknown[]) => void;
package/src/recorder.ts CHANGED
@@ -34,6 +34,19 @@ export interface MidsceneRecorderPageInfo {
34
34
  height: number;
35
35
  }
36
36
 
37
+ /**
38
+ * A screenshot stored outside the recording event payload.
39
+ *
40
+ * Recorder events are persisted in the Studio renderer. Keeping full data
41
+ * URLs there makes long recordings retain every screenshot in the renderer
42
+ * heap, so screenshot bytes live in the Playground run directory instead.
43
+ */
44
+ export interface MidsceneRecorderScreenshotAssetRef {
45
+ id: string;
46
+ mimeType: string;
47
+ bytes: number;
48
+ }
49
+
37
50
  export interface MidsceneRecorderEvent {
38
51
  type: MidsceneRecorderEventType;
39
52
  source?: MidsceneRecorderSourceKind;
@@ -46,6 +59,8 @@ export interface MidsceneRecorderEvent {
46
59
  pageInfo: MidsceneRecorderPageInfo;
47
60
  screenshotBefore?: string;
48
61
  screenshotAfter?: string;
62
+ /** The single screenshot retained for AI description and Markdown export. */
63
+ screenshotAsset?: MidsceneRecorderScreenshotAssetRef;
49
64
  semantic?: MidsceneRecorderSemantic;
50
65
  elementDescription?: string;
51
66
  descriptionLoading?: boolean;
@@ -33,9 +33,7 @@ export function unwrapZodField(field: unknown): unknown {
33
33
 
34
34
  /**
35
35
  * Check if a field is a Midscene locator field
36
- * Checks for either:
37
- * 1. midscene_location_field_flag in shape (result schema)
38
- * 2. prompt field in shape (input schema)
36
+ * Locator input schemas are identified by their prompt field.
39
37
  */
40
38
  export function isMidsceneLocatorField(field: unknown): boolean {
41
39
  const actualField = unwrapZodField(field) as {
@@ -45,11 +43,6 @@ export function isMidsceneLocatorField(field: unknown): boolean {
45
43
  if (actualField._def?.typeName === 'ZodObject') {
46
44
  const shape = actualField._def.shape?.();
47
45
  if (shape) {
48
- // Method 1: Check for the location field flag (for result schema)
49
- if ('midscene_location_field_flag' in shape) {
50
- return true;
51
- }
52
- // Method 2: Check if it's the input schema by checking for 'prompt' field
53
46
  if ('prompt' in shape && shape.prompt) {
54
47
  return true;
55
48
  }