@aiscene/shared 8.0.3 → 8.0.5

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 (105) hide show
  1. package/dist/es/agent-tools/agent-behavior-init-args.mjs +44 -0
  2. package/dist/es/agent-tools/base-tools.mjs +163 -0
  3. package/dist/es/agent-tools/chrome-path.mjs +50 -0
  4. package/dist/es/agent-tools/cli-report-session.mjs +78 -0
  5. package/dist/es/agent-tools/error-formatter.mjs +106 -0
  6. package/dist/es/agent-tools/index.mjs +9 -0
  7. package/dist/es/agent-tools/init-arg-utils.mjs +38 -0
  8. package/dist/es/agent-tools/observation-artifact.mjs +5 -0
  9. package/dist/es/agent-tools/observation-record.mjs +248 -0
  10. package/dist/es/agent-tools/tool-defaults.mjs +54 -0
  11. package/dist/es/agent-tools/tool-generator.mjs +529 -0
  12. package/dist/es/agent-tools/types.mjs +3 -0
  13. package/dist/es/agent-tools/user-prompt.mjs +66 -0
  14. package/dist/es/cli/interrupt.mjs +116 -0
  15. package/dist/es/cli/record-command.mjs +130 -0
  16. package/dist/es/cli/screenshot-file.mjs +24 -0
  17. package/dist/es/cli/verbose-ai-act.mjs +230 -0
  18. package/dist/es/cli/verbose-screenshot.mjs +134 -0
  19. package/dist/es/cli/verbose.mjs +404 -0
  20. package/dist/es/env/parse-model-config.mjs +1 -1
  21. package/dist/es/env/types.mjs +18 -3
  22. package/dist/es/img/index.mjs +3 -3
  23. package/dist/es/img/info.mjs +45 -1
  24. package/dist/es/img/photon-loader.mjs +4 -0
  25. package/dist/es/img/transform.mjs +113 -3
  26. package/dist/es/recorder.mjs +249 -0
  27. package/dist/lib/agent-tools/agent-behavior-init-args.js +87 -0
  28. package/dist/lib/agent-tools/base-tools.js +197 -0
  29. package/dist/lib/agent-tools/chrome-path.js +87 -0
  30. package/dist/lib/agent-tools/cli-report-session.js +121 -0
  31. package/dist/lib/agent-tools/error-formatter.js +149 -0
  32. package/dist/lib/agent-tools/index.js +114 -0
  33. package/dist/lib/agent-tools/init-arg-utils.js +78 -0
  34. package/dist/lib/agent-tools/observation-artifact.js +42 -0
  35. package/dist/lib/agent-tools/observation-record.js +297 -0
  36. package/dist/lib/agent-tools/tool-defaults.js +97 -0
  37. package/dist/lib/agent-tools/tool-generator.js +569 -0
  38. package/dist/lib/agent-tools/types.js +40 -0
  39. package/dist/lib/agent-tools/user-prompt.js +103 -0
  40. package/dist/lib/cli/interrupt.js +156 -0
  41. package/dist/lib/cli/record-command.js +164 -0
  42. package/dist/lib/cli/screenshot-file.js +58 -0
  43. package/dist/lib/cli/verbose-ai-act.js +273 -0
  44. package/dist/lib/cli/verbose-screenshot.js +177 -0
  45. package/dist/lib/cli/verbose.js +465 -0
  46. package/dist/lib/env/parse-model-config.js +1 -1
  47. package/dist/lib/env/types.js +32 -2
  48. package/dist/lib/img/index.js +29 -5
  49. package/dist/lib/img/info.js +48 -1
  50. package/dist/lib/img/photon-loader.js +38 -0
  51. package/dist/lib/img/transform.js +135 -4
  52. package/dist/lib/recorder.js +307 -0
  53. package/dist/types/agent-tools/agent-behavior-init-args.d.ts +17 -0
  54. package/dist/types/agent-tools/base-tools.d.ts +158 -0
  55. package/dist/types/agent-tools/chrome-path.d.ts +2 -0
  56. package/dist/types/agent-tools/cli-report-session.d.ts +12 -0
  57. package/dist/types/agent-tools/error-formatter.d.ts +30 -0
  58. package/dist/types/agent-tools/index.d.ts +9 -0
  59. package/dist/types/agent-tools/init-arg-utils.d.ts +13 -0
  60. package/dist/types/agent-tools/observation-artifact.d.ts +10 -0
  61. package/dist/types/agent-tools/observation-record.d.ts +38 -0
  62. package/dist/types/agent-tools/tool-defaults.d.ts +63 -0
  63. package/dist/types/agent-tools/tool-generator.d.ts +13 -0
  64. package/dist/types/agent-tools/types.d.ts +213 -0
  65. package/dist/types/agent-tools/user-prompt.d.ts +13 -0
  66. package/dist/types/cli/interrupt.d.ts +49 -0
  67. package/dist/types/cli/record-command.d.ts +3 -0
  68. package/dist/types/cli/screenshot-file.d.ts +10 -0
  69. package/dist/types/cli/verbose-ai-act.d.ts +44 -0
  70. package/dist/types/cli/verbose-screenshot.d.ts +10 -0
  71. package/dist/types/cli/verbose.d.ts +40 -0
  72. package/dist/types/env/types.d.ts +16 -7
  73. package/dist/types/img/index.d.ts +2 -2
  74. package/dist/types/img/info.d.ts +2 -0
  75. package/dist/types/img/photon-loader.d.ts +2 -0
  76. package/dist/types/img/transform.d.ts +22 -2
  77. package/dist/types/mcp/types.d.ts +1 -0
  78. package/dist/types/recorder.d.ts +113 -0
  79. package/package.json +1 -1
  80. package/src/agent-tools/agent-behavior-init-args.ts +109 -0
  81. package/src/agent-tools/base-tools.ts +399 -0
  82. package/src/agent-tools/chrome-path.ts +74 -0
  83. package/src/agent-tools/cli-report-session.ts +130 -0
  84. package/src/agent-tools/error-formatter.ts +177 -0
  85. package/src/agent-tools/index.ts +9 -0
  86. package/src/agent-tools/init-arg-utils.ts +105 -0
  87. package/src/agent-tools/observation-artifact.ts +29 -0
  88. package/src/agent-tools/observation-record.ts +331 -0
  89. package/src/agent-tools/tool-defaults.ts +119 -0
  90. package/src/agent-tools/tool-generator.ts +866 -0
  91. package/src/agent-tools/types.ts +250 -0
  92. package/src/agent-tools/user-prompt.ts +102 -0
  93. package/src/cli/interrupt.ts +207 -0
  94. package/src/cli/record-command.ts +177 -0
  95. package/src/cli/screenshot-file.ts +61 -0
  96. package/src/cli/verbose-ai-act.ts +387 -0
  97. package/src/cli/verbose-screenshot.ts +269 -0
  98. package/src/cli/verbose.ts +753 -0
  99. package/src/env/types.ts +30 -2
  100. package/src/img/index.ts +12 -0
  101. package/src/img/info.ts +61 -0
  102. package/src/img/photon-loader.ts +5 -0
  103. package/src/img/transform.ts +262 -3
  104. package/src/mcp/types.ts +2 -0
  105. package/src/recorder.ts +625 -0
package/src/env/types.ts CHANGED
@@ -10,6 +10,7 @@ export const MIDSCENE_DANGEROUSLY_PRINT_ALL_CONFIG =
10
10
  export const MIDSCENE_DEBUG_MODE = 'MIDSCENE_DEBUG_MODE';
11
11
  export const MIDSCENE_MCP_USE_PUPPETEER_MODE =
12
12
  'MIDSCENE_MCP_USE_PUPPETEER_MODE';
13
+ export const MIDSCENE_CHROME_PATH = 'MIDSCENE_CHROME_PATH';
13
14
  export const MIDSCENE_MCP_CHROME_PATH = 'MIDSCENE_MCP_CHROME_PATH';
14
15
  export const MIDSCENE_MCP_ANDROID_MODE = 'MIDSCENE_MCP_ANDROID_MODE';
15
16
  export const DOCKER_CONTAINER = 'DOCKER_CONTAINER';
@@ -35,6 +36,10 @@ export const MIDSCENE_MODEL_REASONING_ENABLED =
35
36
  'MIDSCENE_MODEL_REASONING_ENABLED';
36
37
  export const MIDSCENE_MODEL_REASONING_BUDGET =
37
38
  'MIDSCENE_MODEL_REASONING_BUDGET';
39
+ export const MIDSCENE_MODEL_RESPONSE_FORMAT = 'MIDSCENE_MODEL_RESPONSE_FORMAT';
40
+
41
+ export type TModelReasoningEnabled = boolean | 'default';
42
+ export type TModelResponseFormat = 'none' | 'auto';
38
43
 
39
44
  /**
40
45
  * @deprecated Use MIDSCENE_MODEL_API_KEY instead. This is kept for backward compatibility.
@@ -92,6 +97,7 @@ export const MIDSCENE_REPLANNING_CYCLE_LIMIT =
92
97
  'MIDSCENE_REPLANNING_CYCLE_LIMIT';
93
98
 
94
99
  export const MIDSCENE_RUN_DIR = 'MIDSCENE_RUN_DIR';
100
+ export const MIDSCENE_RECORD_MODEL_CALL = 'MIDSCENE_RECORD_MODEL_CALL';
95
101
 
96
102
  // INSIGHT (unified VQA and Grounding)
97
103
  export const MIDSCENE_INSIGHT_MODEL_NAME = 'MIDSCENE_INSIGHT_MODEL_NAME';
@@ -120,6 +126,8 @@ export const MIDSCENE_INSIGHT_MODEL_REASONING_ENABLED =
120
126
  'MIDSCENE_INSIGHT_MODEL_REASONING_ENABLED';
121
127
  export const MIDSCENE_INSIGHT_MODEL_REASONING_BUDGET =
122
128
  'MIDSCENE_INSIGHT_MODEL_REASONING_BUDGET';
129
+ export const MIDSCENE_INSIGHT_MODEL_RESPONSE_FORMAT =
130
+ 'MIDSCENE_INSIGHT_MODEL_RESPONSE_FORMAT';
123
131
 
124
132
  // PLANNING
125
133
  export const MIDSCENE_PLANNING_MODEL_NAME = 'MIDSCENE_PLANNING_MODEL_NAME';
@@ -150,6 +158,8 @@ export const MIDSCENE_PLANNING_MODEL_REASONING_ENABLED =
150
158
  'MIDSCENE_PLANNING_MODEL_REASONING_ENABLED';
151
159
  export const MIDSCENE_PLANNING_MODEL_REASONING_BUDGET =
152
160
  'MIDSCENE_PLANNING_MODEL_REASONING_BUDGET';
161
+ export const MIDSCENE_PLANNING_MODEL_RESPONSE_FORMAT =
162
+ 'MIDSCENE_PLANNING_MODEL_RESPONSE_FORMAT';
153
163
  export const MIDSCENE_MODEL_FAMILY = 'MIDSCENE_MODEL_FAMILY';
154
164
 
155
165
  /**
@@ -166,6 +176,7 @@ export const BASIC_ENV_KEYS = [
166
176
  MIDSCENE_DEBUG_MODEL_PROFILE,
167
177
  MIDSCENE_DEBUG_MODEL_RESPONSE,
168
178
  MIDSCENE_RUN_DIR,
179
+ MIDSCENE_RECORD_MODEL_CALL,
169
180
  ] as const;
170
181
 
171
182
  export const BOOLEAN_ENV_KEYS = [
@@ -195,6 +206,7 @@ export const STRING_ENV_KEYS = [
195
206
  MIDSCENE_REPORT_TAG_NAME,
196
207
  MIDSCENE_PREFERRED_LANGUAGE,
197
208
  MATCH_BY_POSITION,
209
+ MIDSCENE_CHROME_PATH,
198
210
  MIDSCENE_MCP_CHROME_PATH,
199
211
  DOCKER_CONTAINER,
200
212
  ] as const;
@@ -231,6 +243,7 @@ export const MODEL_ENV_KEYS = [
231
243
  MIDSCENE_MODEL_REASONING_EFFORT,
232
244
  MIDSCENE_MODEL_REASONING_ENABLED,
233
245
  MIDSCENE_MODEL_REASONING_BUDGET,
246
+ MIDSCENE_MODEL_RESPONSE_FORMAT,
234
247
  MIDSCENE_USE_VLM_UI_TARS,
235
248
  MIDSCENE_USE_QWEN_VL,
236
249
  MIDSCENE_USE_QWEN3_VL,
@@ -259,6 +272,7 @@ export const MODEL_ENV_KEYS = [
259
272
  MIDSCENE_INSIGHT_MODEL_REASONING_EFFORT,
260
273
  MIDSCENE_INSIGHT_MODEL_REASONING_ENABLED,
261
274
  MIDSCENE_INSIGHT_MODEL_REASONING_BUDGET,
275
+ MIDSCENE_INSIGHT_MODEL_RESPONSE_FORMAT,
262
276
  // PLANNING
263
277
  MIDSCENE_PLANNING_MODEL_NAME,
264
278
  MIDSCENE_PLANNING_MODEL_SOCKS_PROXY,
@@ -275,6 +289,7 @@ export const MODEL_ENV_KEYS = [
275
289
  MIDSCENE_PLANNING_MODEL_REASONING_EFFORT,
276
290
  MIDSCENE_PLANNING_MODEL_REASONING_ENABLED,
277
291
  MIDSCENE_PLANNING_MODEL_REASONING_BUDGET,
292
+ MIDSCENE_PLANNING_MODEL_RESPONSE_FORMAT,
278
293
  MIDSCENE_MODEL_FAMILY,
279
294
  ] as const;
280
295
 
@@ -294,6 +309,7 @@ export type TGlobalConfig = Record<TEnvKeys, string | undefined>;
294
309
  export type TModelFamily =
295
310
  | 'qwen2.5-vl'
296
311
  | 'qwen3-vl'
312
+ | 'qwen3'
297
313
  | 'qwen3.5'
298
314
  | 'qwen3.6'
299
315
  | 'doubao-vision'
@@ -305,7 +321,11 @@ export type TModelFamily =
305
321
  | 'glm-v'
306
322
  | 'auto-glm'
307
323
  | 'auto-glm-multilingual'
308
- | 'gpt-5';
324
+ | 'gpt-5'
325
+ | 'deepseek'
326
+ | 'kimi'
327
+ | 'kimi3'
328
+ | 'xiaomi-mimo';
309
329
 
310
330
  export const MODEL_FAMILY_VALUES: TModelFamily[] = [
311
331
  'doubao-vision',
@@ -313,6 +333,7 @@ export const MODEL_FAMILY_VALUES: TModelFamily[] = [
313
333
  'gemini',
314
334
  'qwen2.5-vl',
315
335
  'qwen3-vl',
336
+ 'qwen3',
316
337
  'qwen3.5',
317
338
  'qwen3.6',
318
339
  'vlm-ui-tars',
@@ -322,6 +343,10 @@ export const MODEL_FAMILY_VALUES: TModelFamily[] = [
322
343
  'auto-glm',
323
344
  'auto-glm-multilingual',
324
345
  'gpt-5',
346
+ 'deepseek',
347
+ 'kimi',
348
+ 'kimi3',
349
+ 'xiaomi-mimo',
325
350
  ];
326
351
 
327
352
  export interface IModelConfigForInsight {
@@ -391,6 +416,8 @@ export interface IModelConfigForDefault {
391
416
  [MIDSCENE_MODEL_REASONING_ENABLED]?: string;
392
417
  // reasoning budget (number as string)
393
418
  [MIDSCENE_MODEL_REASONING_BUDGET]?: string;
419
+ // Response format strategy (none/auto)
420
+ [MIDSCENE_MODEL_RESPONSE_FORMAT]?: TModelResponseFormat;
394
421
  }
395
422
 
396
423
  export interface IModelConfigForDefaultLegacy {
@@ -507,12 +534,13 @@ export interface IModelConfig {
507
534
  * Enable/disable reasoning for the model.
508
535
  * Passed through to model-family-specific parameters (e.g., enable_thinking for qwen, thinking.type for doubao/glm-v).
509
536
  */
510
- reasoningEnabled?: boolean;
537
+ reasoningEnabled?: TModelReasoningEnabled;
511
538
  /**
512
539
  * Reasoning token budget for the model.
513
540
  * Passed through to model-family-specific parameters (e.g., thinking_budget for qwen).
514
541
  */
515
542
  reasoningBudget?: number;
543
+ responseFormat?: TModelResponseFormat;
516
544
  /**
517
545
  * Model family - unified model configuration
518
546
  * Maps directly to model families like 'qwen2.5-vl', 'qwen3-vl', 'doubao-vision', 'doubao-seed', etc.
package/src/img/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export {
2
+ encodedImageInfoOfBuffer,
2
3
  imageInfoOfBase64,
3
4
  isValidPNGImageBuffer,
4
5
  isValidJPEGImageBuffer,
@@ -8,6 +9,10 @@ export {
8
9
  } from './info';
9
10
  export {
10
11
  resizeAndConvertImgBuffer,
12
+ convertImgBufferToJpeg,
13
+ convertBase64ImageToJpeg,
14
+ constrainBase64ImageToMaxSize,
15
+ resizeBase64ImageToJpeg,
11
16
  resizeImgBase64,
12
17
  zoomForGPT4o,
13
18
  saveBase64Image,
@@ -19,6 +24,13 @@ export {
19
24
  preProcessImageUrl,
20
25
  parseBase64,
21
26
  createImgBase64ByFormat,
27
+ inferBase64ImageFormat,
28
+ normalizeBase64Image,
29
+ normalizeScreenshotBase64,
30
+ type NormalizeScreenshotBase64Options,
31
+ type ConstrainBase64ImageToMaxSizeOptions,
32
+ type JpegBase64DataUrl,
33
+ type ResizeBase64ImageToJpegOptions,
22
34
  } from './transform';
23
35
  export {
24
36
  processImageElementInfo,
package/src/img/info.ts CHANGED
@@ -1,9 +1,70 @@
1
1
  import assert from 'node:assert';
2
+ import { Buffer } from 'node:buffer';
2
3
  import type { Size } from '../types';
3
4
  import getPhoton from './get-photon';
4
5
 
5
6
  export interface ImageInfo extends Size {}
6
7
 
8
+ const isJpegStartOfFrameMarker = (marker: number): boolean =>
9
+ marker >= 0xc0 &&
10
+ marker <= 0xcf &&
11
+ marker !== 0xc4 &&
12
+ marker !== 0xc8 &&
13
+ marker !== 0xcc;
14
+
15
+ function jpegInfoFromBuffer(imageBuffer: Buffer): ImageInfo {
16
+ let offset = 2;
17
+ while (offset < imageBuffer.length) {
18
+ if (imageBuffer[offset] !== 0xff) {
19
+ offset += 1;
20
+ continue;
21
+ }
22
+
23
+ while (offset < imageBuffer.length && imageBuffer[offset] === 0xff) {
24
+ offset += 1;
25
+ }
26
+ if (offset >= imageBuffer.length) break;
27
+
28
+ const marker = imageBuffer[offset];
29
+ offset += 1;
30
+ if (marker === 0xd9 || marker === 0xda) break;
31
+ if (
32
+ marker === 0x01 ||
33
+ marker === 0xd8 ||
34
+ (marker >= 0xd0 && marker <= 0xd7)
35
+ ) {
36
+ continue;
37
+ }
38
+ if (offset + 2 > imageBuffer.length) break;
39
+
40
+ const segmentLength = imageBuffer.readUInt16BE(offset);
41
+ if (segmentLength < 2 || offset + segmentLength > imageBuffer.length) break;
42
+ if (isJpegStartOfFrameMarker(marker)) {
43
+ if (segmentLength < 7) break;
44
+ const height = imageBuffer.readUInt16BE(offset + 3);
45
+ const width = imageBuffer.readUInt16BE(offset + 5);
46
+ assert(width && height, 'Invalid image: cannot get width or height');
47
+ return { width, height };
48
+ }
49
+ offset += segmentLength;
50
+ }
51
+
52
+ throw new Error('Invalid image: cannot get JPEG width or height');
53
+ }
54
+
55
+ export function encodedImageInfoOfBuffer(imageBuffer: Buffer): ImageInfo {
56
+ if (isValidPNGImageBuffer(imageBuffer)) {
57
+ const width = imageBuffer.readUInt32BE(16);
58
+ const height = imageBuffer.readUInt32BE(20);
59
+ assert(width && height, 'Invalid image: cannot get width or height');
60
+ return { width, height };
61
+ }
62
+ if (isValidJPEGImageBuffer(imageBuffer)) {
63
+ return jpegInfoFromBuffer(imageBuffer);
64
+ }
65
+ throw new Error('Invalid image: unsupported format');
66
+ }
67
+
7
68
  /**
8
69
  * Retrieves the dimensions of an image from a base64-encoded string
9
70
  *
@@ -0,0 +1,5 @@
1
+ export type PhotonModule = typeof import('@silvia-odwyer/photon');
2
+
3
+ export async function loadPhotonModule(): Promise<PhotonModule> {
4
+ return import('@silvia-odwyer/photon');
5
+ }
@@ -4,11 +4,12 @@ import { readFileSync } from 'node:fs';
4
4
  import { writeFile } from 'node:fs/promises';
5
5
  import path from 'node:path';
6
6
  import type { PhotonImage as PhotonImageType } from '@silvia-odwyer/photon-node';
7
- import type { Rect } from 'src/types';
7
+ import type { Rect, Size } from '../types';
8
8
  import { getDebug } from '../logger';
9
9
  import { ifInNode } from '../utils';
10
10
  import getPhoton from './get-photon';
11
11
  import getSharp from './get-sharp';
12
+ import { encodedImageInfoOfBuffer } from './info';
12
13
 
13
14
  const imgDebug = getDebug('img');
14
15
 
@@ -154,10 +155,268 @@ export async function resizeAndConvertImgBuffer(
154
155
  };
155
156
  }
156
157
 
158
+ const rawBase64BodyPattern = /^[A-Za-z0-9+/]+={0,2}$/;
159
+ const base64ImageDataUrlPattern =
160
+ /^data:image\/(?:png|jpe?g);base64,[A-Za-z0-9+/=\s]+$/i;
161
+ const supportedScreenshotDataUriPattern =
162
+ /^data:image\/(png|jpe?g);base64,([A-Za-z0-9+/=\s]+)$/i;
163
+
164
+ function normalizeBase64Body(body: string): string {
165
+ return body.replace(/\s/g, '');
166
+ }
167
+
168
+ function detectImageMimeTypeFromBuffer(buffer: Buffer): string | undefined {
169
+ if (
170
+ buffer.length >= 8 &&
171
+ buffer[0] === 0x89 &&
172
+ buffer[1] === 0x50 &&
173
+ buffer[2] === 0x4e &&
174
+ buffer[3] === 0x47 &&
175
+ buffer[4] === 0x0d &&
176
+ buffer[5] === 0x0a &&
177
+ buffer[6] === 0x1a &&
178
+ buffer[7] === 0x0a
179
+ ) {
180
+ return 'image/png';
181
+ }
182
+ if (
183
+ buffer.length >= 3 &&
184
+ buffer[0] === 0xff &&
185
+ buffer[1] === 0xd8 &&
186
+ buffer[2] === 0xff
187
+ ) {
188
+ return 'image/jpeg';
189
+ }
190
+ return undefined;
191
+ }
192
+
193
+ export function inferBase64ImageFormat(base64Body: string): string {
194
+ const buffer = Buffer.from(normalizeBase64Body(base64Body), 'base64');
195
+ const mimeType = detectImageMimeTypeFromBuffer(buffer);
196
+ if (mimeType === 'image/jpeg') return 'jpeg';
197
+ if (mimeType === 'image/png') return 'png';
198
+ return 'png';
199
+ }
200
+
157
201
  export const createImgBase64ByFormat = (format: string, body: string) => {
158
- return `data:image/${format};base64,${body}`;
202
+ return `data:image/${format};base64,${normalizeBase64Body(body)}`;
159
203
  };
160
204
 
205
+ export interface NormalizeScreenshotBase64Options {
206
+ label?: string;
207
+ }
208
+
209
+ export const normalizeScreenshotBase64 = (
210
+ base64: string,
211
+ options?: NormalizeScreenshotBase64Options,
212
+ ) => {
213
+ const label = options?.label ?? 'screenshot base64';
214
+ const trimmedBase64 = base64.trim();
215
+ if (!trimmedBase64) {
216
+ throw new Error(`${label} cannot be empty`);
217
+ }
218
+
219
+ const dataUriMatch = trimmedBase64.match(supportedScreenshotDataUriPattern);
220
+ if (dataUriMatch) {
221
+ const imageFormat =
222
+ dataUriMatch[1].toLowerCase() === 'jpg'
223
+ ? 'jpeg'
224
+ : dataUriMatch[1].toLowerCase();
225
+ const body = normalizeBase64Body(dataUriMatch[2]);
226
+ if (!body) {
227
+ throw new Error(`${label} cannot be empty`);
228
+ }
229
+ return createImgBase64ByFormat(imageFormat, body);
230
+ }
231
+
232
+ if (trimmedBase64.startsWith('data:')) {
233
+ throw new Error(
234
+ `${label} must be a PNG/JPEG data URI or raw PNG base64 string`,
235
+ );
236
+ }
237
+
238
+ if (!rawBase64BodyPattern.test(normalizeBase64Body(trimmedBase64))) {
239
+ throw new Error(
240
+ `${label} must be a PNG/JPEG data URI or raw PNG base64 string`,
241
+ );
242
+ }
243
+
244
+ return createImgBase64ByFormat('png', trimmedBase64);
245
+ };
246
+
247
+ export const normalizeBase64Image = (base64: string) => {
248
+ const trimmedBase64 = base64.trim();
249
+ if (base64ImageDataUrlPattern.test(trimmedBase64)) {
250
+ return trimmedBase64.replace(/\s/g, '');
251
+ }
252
+
253
+ const base64Body = normalizeBase64Body(trimmedBase64);
254
+ assert(base64Body, 'base64 image must include image data');
255
+ return createImgBase64ByFormat(
256
+ inferBase64ImageFormat(base64Body),
257
+ base64Body,
258
+ );
259
+ };
260
+
261
+ export type JpegBase64DataUrl = `data:image/jpeg;base64,${string}`;
262
+
263
+ export interface ResizeBase64ImageToJpegOptions {
264
+ sourceSize?: Size;
265
+ targetSize: Size;
266
+ jpegQuality?: number;
267
+ }
268
+
269
+ export interface ConstrainBase64ImageToMaxSizeOptions {
270
+ maxSize: number;
271
+ jpegQuality?: number;
272
+ }
273
+
274
+ function assertValidJpegQuality(jpegQuality: number): void {
275
+ if (!Number.isInteger(jpegQuality) || jpegQuality < 1 || jpegQuality > 100) {
276
+ throw new Error(
277
+ `jpegQuality must be an integer between 1 and 100. Received: ${jpegQuality}`,
278
+ );
279
+ }
280
+ }
281
+
282
+ function assertValidImageSize(size: Size, label: string): void {
283
+ if (
284
+ !Number.isInteger(size.width) ||
285
+ !Number.isInteger(size.height) ||
286
+ size.width <= 0 ||
287
+ size.height <= 0
288
+ ) {
289
+ throw new Error(
290
+ `${label} width and height must be positive integers. Received width: ${size.width}, height: ${size.height}`,
291
+ );
292
+ }
293
+ }
294
+
295
+ export async function convertImgBufferToJpeg(
296
+ inputData: Buffer,
297
+ jpegQuality = 90,
298
+ ): Promise<Buffer> {
299
+ assertValidJpegQuality(jpegQuality);
300
+
301
+ if (ifInNode) {
302
+ try {
303
+ const Sharp = await getSharp();
304
+ return await Sharp(inputData).jpeg({ quality: jpegQuality }).toBuffer();
305
+ } catch (error) {
306
+ imgDebug('Sharp failed, falling back to Photon:', error);
307
+ }
308
+ }
309
+
310
+ const { PhotonImage } = await getPhoton();
311
+ const inputBytes = new Uint8Array(inputData);
312
+ const bytesliceResult = PhotonImage.new_from_byteslice(inputBytes);
313
+ const image =
314
+ bytesliceResult instanceof Promise
315
+ ? await bytesliceResult
316
+ : bytesliceResult;
317
+ try {
318
+ return Buffer.from(image.get_bytes_jpeg(jpegQuality));
319
+ } finally {
320
+ image.free();
321
+ }
322
+ }
323
+
324
+ export async function convertBase64ImageToJpeg(
325
+ inputBase64: string,
326
+ jpegQuality = 90,
327
+ ): Promise<JpegBase64DataUrl> {
328
+ assertValidJpegQuality(jpegQuality);
329
+ const normalized = normalizeBase64Image(inputBase64);
330
+ const { body } = parseBase64(normalized);
331
+ const imageBuffer = Buffer.from(body, 'base64');
332
+ const detectedMimeType = detectImageMimeTypeFromBuffer(imageBuffer);
333
+ if (detectedMimeType === 'image/jpeg') {
334
+ return createImgBase64ByFormat('jpeg', body) as JpegBase64DataUrl;
335
+ }
336
+ if (detectedMimeType !== 'image/png') {
337
+ throw new Error(
338
+ `inputBase64 must contain a PNG or JPEG image. Detected: ${detectedMimeType ?? 'unsupported format'}`,
339
+ );
340
+ }
341
+
342
+ const jpegBuffer = await convertImgBufferToJpeg(imageBuffer, jpegQuality);
343
+ return createImgBase64ByFormat(
344
+ 'jpeg',
345
+ jpegBuffer.toString('base64'),
346
+ ) as JpegBase64DataUrl;
347
+ }
348
+
349
+ export async function resizeBase64ImageToJpeg(
350
+ inputBase64: string,
351
+ options: ResizeBase64ImageToJpegOptions,
352
+ ): Promise<JpegBase64DataUrl> {
353
+ const jpegQuality = options.jpegQuality ?? 90;
354
+ assertValidJpegQuality(jpegQuality);
355
+ assertValidImageSize(options.targetSize, 'targetSize');
356
+
357
+ const normalized = normalizeBase64Image(inputBase64);
358
+ const { body, mimeType } = parseBase64(normalized);
359
+ const imageBuffer = Buffer.from(body, 'base64');
360
+ const sourceSize = encodedImageInfoOfBuffer(imageBuffer);
361
+ if (options.sourceSize) {
362
+ assertValidImageSize(options.sourceSize, 'sourceSize');
363
+ if (
364
+ options.sourceSize.width !== sourceSize.width ||
365
+ options.sourceSize.height !== sourceSize.height
366
+ ) {
367
+ throw new Error(
368
+ `sourceSize ${options.sourceSize.width}x${options.sourceSize.height} does not match encoded image dimensions ${sourceSize.width}x${sourceSize.height}`,
369
+ );
370
+ }
371
+ }
372
+
373
+ const dimensionsUnchanged =
374
+ sourceSize.width === options.targetSize.width &&
375
+ sourceSize.height === options.targetSize.height;
376
+ if (dimensionsUnchanged) {
377
+ return convertBase64ImageToJpeg(normalized, jpegQuality);
378
+ }
379
+
380
+ const { buffer } = await resizeAndConvertImgBuffer(
381
+ mimeType.split('/')[1],
382
+ imageBuffer,
383
+ options.targetSize,
384
+ );
385
+ return createImgBase64ByFormat(
386
+ 'jpeg',
387
+ buffer.toString('base64'),
388
+ ) as JpegBase64DataUrl;
389
+ }
390
+
391
+ export async function constrainBase64ImageToMaxSize(
392
+ inputBase64: string,
393
+ options: ConstrainBase64ImageToMaxSizeOptions,
394
+ ): Promise<string> {
395
+ if (!Number.isInteger(options.maxSize) || options.maxSize <= 0) {
396
+ throw new Error(
397
+ `maxSize must be a positive integer. Received: ${options.maxSize}`,
398
+ );
399
+ }
400
+
401
+ const normalized = normalizeBase64Image(inputBase64);
402
+ const { body } = parseBase64(normalized);
403
+ const sourceSize = encodedImageInfoOfBuffer(Buffer.from(body, 'base64'));
404
+ const largestDimension = Math.max(sourceSize.width, sourceSize.height);
405
+ if (largestDimension <= options.maxSize) {
406
+ return normalized;
407
+ }
408
+
409
+ const scale = options.maxSize / largestDimension;
410
+ return resizeBase64ImageToJpeg(normalized, {
411
+ targetSize: {
412
+ width: Math.max(1, Math.round(sourceSize.width * scale)),
413
+ height: Math.max(1, Math.round(sourceSize.height * scale)),
414
+ },
415
+ sourceSize,
416
+ jpegQuality: options.jpegQuality,
417
+ });
418
+ }
419
+
161
420
  export async function resizeImgBase64(
162
421
  inputBase64: string,
163
422
  newSize: {
@@ -297,7 +556,7 @@ export async function paddingToMatchBlockByBase64(
297
556
  export async function cropByRect(
298
557
  imageBase64: string,
299
558
  rect: Rect,
300
- paddingImage: boolean,
559
+ paddingImage = false,
301
560
  ): Promise<{
302
561
  width: number;
303
562
  height: number;
package/src/mcp/types.ts CHANGED
@@ -111,6 +111,8 @@ export interface BaseAgent {
111
111
  msg?: string,
112
112
  options?: Record<string, unknown>,
113
113
  ) => Promise<unknown>;
114
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
115
+ aiQuery?: (demand: any, opt?: any) => Promise<any>;
114
116
  }
115
117
 
116
118
  /**