@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.
- package/dist/es/agent-tools/agent-behavior-init-args.mjs +44 -0
- package/dist/es/agent-tools/base-tools.mjs +163 -0
- package/dist/es/agent-tools/chrome-path.mjs +50 -0
- package/dist/es/agent-tools/cli-report-session.mjs +78 -0
- package/dist/es/agent-tools/error-formatter.mjs +106 -0
- package/dist/es/agent-tools/index.mjs +9 -0
- package/dist/es/agent-tools/init-arg-utils.mjs +38 -0
- package/dist/es/agent-tools/observation-artifact.mjs +5 -0
- package/dist/es/agent-tools/observation-record.mjs +248 -0
- package/dist/es/agent-tools/tool-defaults.mjs +54 -0
- package/dist/es/agent-tools/tool-generator.mjs +529 -0
- package/dist/es/agent-tools/types.mjs +3 -0
- package/dist/es/agent-tools/user-prompt.mjs +66 -0
- package/dist/es/cli/interrupt.mjs +116 -0
- package/dist/es/cli/record-command.mjs +130 -0
- package/dist/es/cli/screenshot-file.mjs +24 -0
- package/dist/es/cli/verbose-ai-act.mjs +230 -0
- package/dist/es/cli/verbose-screenshot.mjs +134 -0
- package/dist/es/cli/verbose.mjs +404 -0
- package/dist/es/env/parse-model-config.mjs +1 -1
- package/dist/es/env/types.mjs +18 -3
- package/dist/es/img/index.mjs +3 -3
- package/dist/es/img/info.mjs +45 -1
- package/dist/es/img/photon-loader.mjs +4 -0
- package/dist/es/img/transform.mjs +113 -3
- package/dist/es/recorder.mjs +249 -0
- package/dist/lib/agent-tools/agent-behavior-init-args.js +87 -0
- package/dist/lib/agent-tools/base-tools.js +197 -0
- package/dist/lib/agent-tools/chrome-path.js +87 -0
- package/dist/lib/agent-tools/cli-report-session.js +121 -0
- package/dist/lib/agent-tools/error-formatter.js +149 -0
- package/dist/lib/agent-tools/index.js +114 -0
- package/dist/lib/agent-tools/init-arg-utils.js +78 -0
- package/dist/lib/agent-tools/observation-artifact.js +42 -0
- package/dist/lib/agent-tools/observation-record.js +297 -0
- package/dist/lib/agent-tools/tool-defaults.js +97 -0
- package/dist/lib/agent-tools/tool-generator.js +569 -0
- package/dist/lib/agent-tools/types.js +40 -0
- package/dist/lib/agent-tools/user-prompt.js +103 -0
- package/dist/lib/cli/interrupt.js +156 -0
- package/dist/lib/cli/record-command.js +164 -0
- package/dist/lib/cli/screenshot-file.js +58 -0
- package/dist/lib/cli/verbose-ai-act.js +273 -0
- package/dist/lib/cli/verbose-screenshot.js +177 -0
- package/dist/lib/cli/verbose.js +465 -0
- package/dist/lib/env/parse-model-config.js +1 -1
- package/dist/lib/env/types.js +32 -2
- package/dist/lib/img/index.js +29 -5
- package/dist/lib/img/info.js +48 -1
- package/dist/lib/img/photon-loader.js +38 -0
- package/dist/lib/img/transform.js +135 -4
- package/dist/lib/recorder.js +307 -0
- package/dist/types/agent-tools/agent-behavior-init-args.d.ts +17 -0
- package/dist/types/agent-tools/base-tools.d.ts +158 -0
- package/dist/types/agent-tools/chrome-path.d.ts +2 -0
- package/dist/types/agent-tools/cli-report-session.d.ts +12 -0
- package/dist/types/agent-tools/error-formatter.d.ts +30 -0
- package/dist/types/agent-tools/index.d.ts +9 -0
- package/dist/types/agent-tools/init-arg-utils.d.ts +13 -0
- package/dist/types/agent-tools/observation-artifact.d.ts +10 -0
- package/dist/types/agent-tools/observation-record.d.ts +38 -0
- package/dist/types/agent-tools/tool-defaults.d.ts +63 -0
- package/dist/types/agent-tools/tool-generator.d.ts +13 -0
- package/dist/types/agent-tools/types.d.ts +213 -0
- package/dist/types/agent-tools/user-prompt.d.ts +13 -0
- package/dist/types/cli/interrupt.d.ts +49 -0
- package/dist/types/cli/record-command.d.ts +3 -0
- package/dist/types/cli/screenshot-file.d.ts +10 -0
- package/dist/types/cli/verbose-ai-act.d.ts +44 -0
- package/dist/types/cli/verbose-screenshot.d.ts +10 -0
- package/dist/types/cli/verbose.d.ts +40 -0
- package/dist/types/env/types.d.ts +16 -7
- package/dist/types/img/index.d.ts +2 -2
- package/dist/types/img/info.d.ts +2 -0
- package/dist/types/img/photon-loader.d.ts +2 -0
- package/dist/types/img/transform.d.ts +22 -2
- package/dist/types/mcp/types.d.ts +1 -0
- package/dist/types/recorder.d.ts +113 -0
- package/package.json +1 -1
- package/src/agent-tools/agent-behavior-init-args.ts +109 -0
- package/src/agent-tools/base-tools.ts +399 -0
- package/src/agent-tools/chrome-path.ts +74 -0
- package/src/agent-tools/cli-report-session.ts +130 -0
- package/src/agent-tools/error-formatter.ts +177 -0
- package/src/agent-tools/index.ts +9 -0
- package/src/agent-tools/init-arg-utils.ts +105 -0
- package/src/agent-tools/observation-artifact.ts +29 -0
- package/src/agent-tools/observation-record.ts +331 -0
- package/src/agent-tools/tool-defaults.ts +119 -0
- package/src/agent-tools/tool-generator.ts +866 -0
- package/src/agent-tools/types.ts +250 -0
- package/src/agent-tools/user-prompt.ts +102 -0
- package/src/cli/interrupt.ts +207 -0
- package/src/cli/record-command.ts +177 -0
- package/src/cli/screenshot-file.ts +61 -0
- package/src/cli/verbose-ai-act.ts +387 -0
- package/src/cli/verbose-screenshot.ts +269 -0
- package/src/cli/verbose.ts +753 -0
- package/src/env/types.ts +30 -2
- package/src/img/index.ts +12 -0
- package/src/img/info.ts +61 -0
- package/src/img/photon-loader.ts +5 -0
- package/src/img/transform.ts +262 -3
- package/src/mcp/types.ts +2 -0
- 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?:
|
|
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
|
*
|
package/src/img/transform.ts
CHANGED
|
@@ -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 '
|
|
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
|
|
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
|
/**
|