@aiscene/shared 8.0.2 → 8.0.4
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/cli-args.mjs +95 -0
- package/dist/es/cli/cli-error.mjs +24 -0
- package/dist/es/cli/cli-runner.mjs +10 -40
- package/dist/es/cli/index.mjs +4 -2
- 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/constants/example-code.mjs +2 -2
- package/dist/es/constants/index.mjs +6 -1
- package/dist/es/env/model-config-manager.mjs +3 -0
- package/dist/es/env/parse-model-config.mjs +6 -1
- package/dist/es/env/types.mjs +4 -1
- package/dist/es/env/utils.mjs +1 -9
- package/dist/es/extractor/index.mjs +2 -2
- package/dist/es/img/index.mjs +2 -2
- package/dist/es/img/info.mjs +58 -17
- package/dist/es/img/photon-loader.mjs +4 -0
- package/dist/es/key-alias-utils.mjs +19 -0
- package/dist/es/mcp/base-server.mjs +10 -9
- package/dist/es/mcp/base-tools.mjs +72 -4
- package/dist/es/mcp/cli-report-session.mjs +78 -0
- package/dist/es/mcp/error-formatter.mjs +19 -0
- package/dist/es/mcp/index.mjs +2 -0
- package/dist/es/mcp/init-arg-utils.mjs +38 -0
- package/dist/es/mcp/inject-report-html-plugin.mjs +1 -1
- package/dist/es/mcp/tool-generator.mjs +65 -17
- package/dist/es/node/fs.mjs +1 -1
- 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/cli-args.js +138 -0
- package/dist/lib/cli/cli-error.js +61 -0
- package/dist/lib/cli/cli-runner.js +19 -46
- package/dist/lib/cli/index.js +8 -3
- 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/constants/example-code.js +2 -2
- package/dist/lib/constants/index.js +23 -3
- package/dist/lib/env/model-config-manager.js +3 -0
- package/dist/lib/env/parse-model-config.js +6 -1
- package/dist/lib/env/types.js +9 -0
- package/dist/lib/env/utils.js +0 -11
- package/dist/lib/extractor/index.js +3 -0
- package/dist/lib/img/index.js +3 -0
- package/dist/lib/img/info.js +65 -18
- package/dist/lib/img/photon-loader.js +38 -0
- package/dist/lib/key-alias-utils.js +62 -0
- package/dist/lib/mcp/base-server.js +11 -10
- package/dist/lib/mcp/base-tools.js +74 -6
- package/dist/lib/mcp/cli-report-session.js +121 -0
- package/dist/lib/mcp/error-formatter.js +53 -0
- package/dist/lib/mcp/index.js +24 -10
- package/dist/lib/mcp/init-arg-utils.js +78 -0
- package/dist/lib/mcp/inject-report-html-plugin.js +1 -1
- package/dist/lib/mcp/tool-generator.js +74 -26
- package/dist/lib/node/fs.js +1 -1
- 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/cli-args.d.ts +8 -0
- package/dist/types/cli/cli-error.d.ts +5 -0
- package/dist/types/cli/cli-runner.d.ts +4 -7
- package/dist/types/cli/index.d.ts +3 -1
- 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/constants/example-code.d.ts +1 -1
- package/dist/types/constants/index.d.ts +5 -0
- package/dist/types/env/types.d.ts +14 -5
- package/dist/types/env/utils.d.ts +0 -31
- package/dist/types/extractor/index.d.ts +1 -1
- package/dist/types/img/index.d.ts +1 -1
- package/dist/types/img/info.d.ts +7 -0
- package/dist/types/img/photon-loader.d.ts +2 -0
- package/dist/types/key-alias-utils.d.ts +9 -0
- package/dist/types/mcp/base-tools.d.ts +74 -5
- package/dist/types/mcp/cli-report-session.d.ts +12 -0
- package/dist/types/mcp/error-formatter.d.ts +12 -0
- package/dist/types/mcp/index.d.ts +2 -0
- package/dist/types/mcp/init-arg-utils.d.ts +13 -0
- package/dist/types/mcp/inject-report-html-plugin.d.ts +2 -2
- package/dist/types/mcp/launcher-helper.d.ts +3 -3
- package/dist/types/mcp/tool-generator.d.ts +3 -3
- package/dist/types/mcp/types.d.ts +10 -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/cli-args.ts +173 -0
- package/src/cli/cli-error.ts +24 -0
- package/src/cli/cli-runner.ts +37 -56
- package/src/cli/index.ts +3 -7
- 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/constants/example-code.ts +2 -2
- package/src/constants/index.ts +10 -0
- package/src/env/model-config-manager.ts +3 -0
- package/src/env/parse-model-config.ts +19 -1
- package/src/env/types.ts +13 -2
- package/src/env/utils.ts +0 -50
- package/src/extractor/index.ts +1 -1
- package/src/img/index.ts +14 -0
- package/src/img/info.ts +97 -45
- package/src/img/photon-loader.ts +5 -0
- package/src/img/transform.ts +261 -2
- package/src/key-alias-utils.ts +23 -0
- package/src/mcp/base-server.ts +10 -12
- package/src/mcp/base-tools.ts +218 -11
- package/src/mcp/cli-report-session.ts +130 -0
- package/src/mcp/error-formatter.ts +52 -0
- package/src/mcp/index.ts +2 -0
- package/src/mcp/init-arg-utils.ts +105 -0
- package/src/mcp/inject-report-html-plugin.ts +3 -3
- package/src/mcp/launcher-helper.ts +3 -3
- package/src/mcp/tool-generator.ts +98 -20
- package/src/mcp/types.ts +18 -1
- package/src/recorder.ts +625 -0
|
@@ -28,8 +28,8 @@ aiQuery<T>(queryObject: Record<string, string>): Promise<T> // Extracts data fro
|
|
|
28
28
|
// The following is test code for Midscene AI, for reference
|
|
29
29
|
// The following is Playwright syntax, you can use Playwright to assist in test generation
|
|
30
30
|
import { test as base } from '@playwright/test';
|
|
31
|
-
import type { PlayWrightAiFixtureType } from '@
|
|
32
|
-
import { PlaywrightAiFixture } from '@
|
|
31
|
+
import type { PlayWrightAiFixtureType } from '@aiscene/web/playwright';
|
|
32
|
+
import { PlaywrightAiFixture } from '@aiscene/web/playwright';
|
|
33
33
|
|
|
34
34
|
const test = base.extend<PlayWrightAiFixtureType>(PlaywrightAiFixture({
|
|
35
35
|
waitForNetworkIdleTimeout: 2000, // optional, the timeout for waiting for network idle between each action, default is 2000ms
|
package/src/constants/index.ts
CHANGED
|
@@ -17,6 +17,16 @@ export enum NodeType {
|
|
|
17
17
|
|
|
18
18
|
export const PLAYGROUND_SERVER_PORT = 5800;
|
|
19
19
|
export const SCRCPY_SERVER_PORT = 5700;
|
|
20
|
+
export const SCRCPY_ADB_CONNECT_TIMEOUT_MS = 10_000;
|
|
21
|
+
export const SCRCPY_PUSH_TIMEOUT_MS = 10_000;
|
|
22
|
+
export const SCRCPY_START_TIMEOUT_MS = 15_000;
|
|
23
|
+
export const SCRCPY_VIDEO_STREAM_TIMEOUT_MS = 15_000;
|
|
24
|
+
export const SCRCPY_PREVIEW_METADATA_TIMEOUT_MS =
|
|
25
|
+
SCRCPY_ADB_CONNECT_TIMEOUT_MS +
|
|
26
|
+
SCRCPY_PUSH_TIMEOUT_MS +
|
|
27
|
+
SCRCPY_START_TIMEOUT_MS +
|
|
28
|
+
SCRCPY_VIDEO_STREAM_TIMEOUT_MS +
|
|
29
|
+
5_000;
|
|
20
30
|
|
|
21
31
|
// WebDriver constants
|
|
22
32
|
export const WEBDRIVER_ELEMENT_ID_KEY = 'element-6066-11e4-a52e-4f735466cecf';
|
|
@@ -68,14 +68,17 @@ export class ModelConfigManager {
|
|
|
68
68
|
this.modelConfigMap = {
|
|
69
69
|
default: {
|
|
70
70
|
...defaultConfig,
|
|
71
|
+
intent: 'default',
|
|
71
72
|
createOpenAIClient: this.createOpenAIClientFn,
|
|
72
73
|
},
|
|
73
74
|
insight: {
|
|
74
75
|
...(insightConfig || defaultConfig),
|
|
76
|
+
intent: 'insight',
|
|
75
77
|
createOpenAIClient: this.createOpenAIClientFn,
|
|
76
78
|
},
|
|
77
79
|
planning: {
|
|
78
80
|
...(planningConfig || defaultConfig),
|
|
81
|
+
intent: 'planning',
|
|
79
82
|
createOpenAIClient: this.createOpenAIClientFn,
|
|
80
83
|
},
|
|
81
84
|
};
|
|
@@ -24,10 +24,26 @@ import {
|
|
|
24
24
|
} from './types';
|
|
25
25
|
|
|
26
26
|
import { getDebug } from '../logger';
|
|
27
|
+
|
|
27
28
|
import { assert } from '../utils';
|
|
28
29
|
import { maskConfig, parseJson } from './helper';
|
|
29
30
|
import { initDebugConfig } from './init-debug';
|
|
30
31
|
|
|
32
|
+
declare const __VERSION__: string | undefined;
|
|
33
|
+
|
|
34
|
+
const MODEL_CONFIG_DOC_URL = 'https://midscenejs.com/model-common-config.html';
|
|
35
|
+
|
|
36
|
+
const getCurrentVersion = (): string => {
|
|
37
|
+
if (typeof __VERSION__ !== 'undefined' && __VERSION__) {
|
|
38
|
+
return __VERSION__;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return 'unknown';
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const getInvalidModelFamilyMessage = (modelFamily: TModelFamily): string =>
|
|
45
|
+
`Invalid MIDSCENE_MODEL_FAMILY value: ${modelFamily}. Current version v${getCurrentVersion()} accepts the following model families: ${MODEL_FAMILY_VALUES.join(', ')}. You can also visit ${MODEL_CONFIG_DOC_URL} for the latest configuration information.`;
|
|
46
|
+
|
|
31
47
|
type TModelConfigKeys =
|
|
32
48
|
| typeof INSIGHT_MODEL_CONFIG_KEYS
|
|
33
49
|
| typeof PLANNING_MODEL_CONFIG_KEYS
|
|
@@ -70,7 +86,7 @@ export const getUITarsModelVersion = (
|
|
|
70
86
|
*/
|
|
71
87
|
export const validateModelFamily = (modelFamily?: TModelFamily): void => {
|
|
72
88
|
if (modelFamily && !MODEL_FAMILY_VALUES.includes(modelFamily as any)) {
|
|
73
|
-
throw new Error(
|
|
89
|
+
throw new Error(getInvalidModelFamilyMessage(modelFamily));
|
|
74
90
|
}
|
|
75
91
|
};
|
|
76
92
|
|
|
@@ -228,6 +244,7 @@ export const parseOpenaiSdkConfig = ({
|
|
|
228
244
|
modelName: modelName!,
|
|
229
245
|
modelDescription,
|
|
230
246
|
intent: '-' as any,
|
|
247
|
+
slot: '-' as any,
|
|
231
248
|
timeout: provider[keys.timeout]
|
|
232
249
|
? Number(provider[keys.timeout])
|
|
233
250
|
: undefined,
|
|
@@ -292,6 +309,7 @@ export const decideModelConfigFromIntentConfig = (
|
|
|
292
309
|
useLegacyLogic: intent === 'default',
|
|
293
310
|
});
|
|
294
311
|
finalResult.intent = intent;
|
|
312
|
+
finalResult.slot = intent;
|
|
295
313
|
|
|
296
314
|
debugLog(
|
|
297
315
|
'decideModelConfig result by agent.modelConfig() with intent',
|
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';
|
|
@@ -69,6 +70,8 @@ export const MIDSCENE_ANDROID_IME_STRATEGY = 'MIDSCENE_ANDROID_IME_STRATEGY';
|
|
|
69
70
|
|
|
70
71
|
export const MIDSCENE_IOS_DEVICE_UDID = 'MIDSCENE_IOS_DEVICE_UDID';
|
|
71
72
|
export const MIDSCENE_IOS_SIMULATOR_UDID = 'MIDSCENE_IOS_SIMULATOR_UDID';
|
|
73
|
+
export const MIDSCENE_IOS_DEVICE_CLASS_OVERRIDE =
|
|
74
|
+
'MIDSCENE_IOS_DEVICE_CLASS_OVERRIDE';
|
|
72
75
|
|
|
73
76
|
export const MIDSCENE_CACHE = 'MIDSCENE_CACHE';
|
|
74
77
|
export const MIDSCENE_USE_VLM_UI_TARS = 'MIDSCENE_USE_VLM_UI_TARS';
|
|
@@ -193,6 +196,7 @@ export const STRING_ENV_KEYS = [
|
|
|
193
196
|
MIDSCENE_REPORT_TAG_NAME,
|
|
194
197
|
MIDSCENE_PREFERRED_LANGUAGE,
|
|
195
198
|
MATCH_BY_POSITION,
|
|
199
|
+
MIDSCENE_CHROME_PATH,
|
|
196
200
|
MIDSCENE_MCP_CHROME_PATH,
|
|
197
201
|
DOCKER_CONTAINER,
|
|
198
202
|
] as const;
|
|
@@ -425,7 +429,7 @@ export enum UITarsModelVersion {
|
|
|
425
429
|
|
|
426
430
|
/**
|
|
427
431
|
* Callback to create custom OpenAI client instance
|
|
428
|
-
* @param config - Resolved model configuration including apiKey, baseURL, modelName, intent, etc.
|
|
432
|
+
* @param config - Resolved model configuration including apiKey, baseURL, modelName, intent, slot, etc.
|
|
429
433
|
* @returns OpenAI client instance (can be wrapped with langsmith, langfuse, etc.)
|
|
430
434
|
*
|
|
431
435
|
* Note: Wrapper functions like langsmith's wrapOpenAI() return the same OpenAI instance
|
|
@@ -519,9 +523,16 @@ export interface IModelConfig {
|
|
|
519
523
|
uiTarsModelVersion?: UITarsModelVersion;
|
|
520
524
|
modelDescription: string;
|
|
521
525
|
/**
|
|
522
|
-
*
|
|
526
|
+
* The semantic intent this config is requested for.
|
|
527
|
+
* For example, getModelConfig('planning') always returns intent === 'planning'.
|
|
523
528
|
*/
|
|
524
529
|
intent: TIntent;
|
|
530
|
+
/**
|
|
531
|
+
* The model-config slot this config was resolved from.
|
|
532
|
+
* For example, getModelConfig('planning') may resolve from slot === 'default'
|
|
533
|
+
* when MIDSCENE_PLANNING_MODEL_NAME is not configured.
|
|
534
|
+
*/
|
|
535
|
+
slot: TIntent;
|
|
525
536
|
/**
|
|
526
537
|
* Custom OpenAI client factory function
|
|
527
538
|
*
|
package/src/env/utils.ts
CHANGED
|
@@ -13,56 +13,6 @@ export const globalConfigManager = new GlobalConfigManager();
|
|
|
13
13
|
globalConfigManager.registerModelConfigManager(globalModelConfigManager);
|
|
14
14
|
globalModelConfigManager.registerGlobalConfigManager(globalConfigManager);
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
* Interface for devices that support getTimestamp method.
|
|
18
|
-
* This is a minimal interface to avoid circular dependencies with @midscene/core.
|
|
19
|
-
*/
|
|
20
|
-
export interface DeviceWithTimestamp {
|
|
21
|
-
getTimestamp?: () => Promise<number>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Get the current timestamp, optionally from the target device.
|
|
26
|
-
*
|
|
27
|
-
* When useDeviceTimestamp is enabled and a device with getTimestamp is provided,
|
|
28
|
-
* this function will return the device's time. Otherwise, it returns the system time.
|
|
29
|
-
*
|
|
30
|
-
* This is useful when:
|
|
31
|
-
* - Testing on devices with different time zones
|
|
32
|
-
* - Debugging time-sensitive features
|
|
33
|
-
* - The system clock and device clock are not synchronized
|
|
34
|
-
*
|
|
35
|
-
* @param device Optional device interface that supports getTimestamp
|
|
36
|
-
* @param useDeviceTimestamp Whether to use device timestamp (from agent config)
|
|
37
|
-
* @returns Timestamp in milliseconds
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* // Without device - always returns system time
|
|
41
|
-
* const systemTime = await getCurrentTime();
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* // With device and config enabled - returns device time
|
|
45
|
-
* const deviceTime = await getCurrentTime(androidDevice, true);
|
|
46
|
-
*/
|
|
47
|
-
export async function getCurrentTime(
|
|
48
|
-
device?: DeviceWithTimestamp,
|
|
49
|
-
useDeviceTimestamp?: boolean,
|
|
50
|
-
): Promise<number> {
|
|
51
|
-
if (useDeviceTimestamp && device?.getTimestamp) {
|
|
52
|
-
try {
|
|
53
|
-
return await device.getTimestamp();
|
|
54
|
-
} catch (error) {
|
|
55
|
-
// Fall back to system time if device time retrieval fails
|
|
56
|
-
console.warn(
|
|
57
|
-
`Failed to get device time, falling back to system time: ${error}`,
|
|
58
|
-
);
|
|
59
|
-
return Date.now();
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return Date.now();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
16
|
export const getPreferredLanguage = () => {
|
|
67
17
|
const prefer = globalConfigManager.getEnvConfigValue(
|
|
68
18
|
MIDSCENE_PREFERRED_LANGUAGE,
|
package/src/extractor/index.ts
CHANGED
package/src/img/index.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
export {
|
|
2
|
+
encodedImageInfoOfBuffer,
|
|
2
3
|
imageInfoOfBase64,
|
|
3
4
|
isValidPNGImageBuffer,
|
|
4
5
|
isValidJPEGImageBuffer,
|
|
5
6
|
isValidImageBuffer,
|
|
7
|
+
validateScreenshotBuffer,
|
|
8
|
+
type ValidateScreenshotBufferOptions,
|
|
6
9
|
} from './info';
|
|
7
10
|
export {
|
|
8
11
|
resizeAndConvertImgBuffer,
|
|
12
|
+
convertImgBufferToJpeg,
|
|
13
|
+
convertBase64ImageToJpeg,
|
|
14
|
+
constrainBase64ImageToMaxSize,
|
|
15
|
+
resizeBase64ImageToJpeg,
|
|
9
16
|
resizeImgBase64,
|
|
10
17
|
zoomForGPT4o,
|
|
11
18
|
saveBase64Image,
|
|
@@ -17,6 +24,13 @@ export {
|
|
|
17
24
|
preProcessImageUrl,
|
|
18
25
|
parseBase64,
|
|
19
26
|
createImgBase64ByFormat,
|
|
27
|
+
inferBase64ImageFormat,
|
|
28
|
+
normalizeBase64Image,
|
|
29
|
+
normalizeScreenshotBase64,
|
|
30
|
+
type NormalizeScreenshotBase64Options,
|
|
31
|
+
type ConstrainBase64ImageToMaxSizeOptions,
|
|
32
|
+
type JpegBase64DataUrl,
|
|
33
|
+
type ResizeBase64ImageToJpegOptions,
|
|
20
34
|
} from './transform';
|
|
21
35
|
export {
|
|
22
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
|
*
|
|
@@ -14,52 +75,16 @@ export interface ImageInfo extends Size {}
|
|
|
14
75
|
export async function imageInfoOfBase64(
|
|
15
76
|
imageBase64: string,
|
|
16
77
|
): Promise<ImageInfo> {
|
|
17
|
-
|
|
18
|
-
if (!imageBase64 || typeof imageBase64 !== 'string') {
|
|
19
|
-
throw new Error('Invalid image base64: data is empty or not a string');
|
|
20
|
-
}
|
|
21
|
-
|
|
78
|
+
const { PhotonImage } = await getPhoton();
|
|
22
79
|
const base64Data = imageBase64.replace(/^data:image\/\w+;base64,/, '');
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64Data)) {
|
|
32
|
-
throw new Error(
|
|
33
|
-
'Invalid image base64: contains invalid characters',
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Check minimum length for a valid image (at least a few bytes)
|
|
38
|
-
if (base64Data.length < 4) {
|
|
39
|
-
throw new Error(
|
|
40
|
-
`Invalid image base64: data too short (${base64Data.length} characters)`,
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
const { PhotonImage } = await getPhoton();
|
|
46
|
-
// Support both sync (Photon) and async (Canvas fallback) versions
|
|
47
|
-
const result = PhotonImage.new_from_base64(base64Data);
|
|
48
|
-
const image = result instanceof Promise ? await result : result;
|
|
49
|
-
const width = image.get_width();
|
|
50
|
-
const height = image.get_height();
|
|
51
|
-
image.free();
|
|
52
|
-
assert(width && height, 'Invalid image: cannot get width or height');
|
|
53
|
-
return { width, height };
|
|
54
|
-
} catch (error) {
|
|
55
|
-
// Provide more helpful error message for Photon failures
|
|
56
|
-
if (error instanceof Error && error.message.includes('unreachable')) {
|
|
57
|
-
throw new Error(
|
|
58
|
-
`Failed to decode image: invalid or corrupted image data. Original error: ${error.message}`,
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
throw error;
|
|
62
|
-
}
|
|
80
|
+
// Support both sync (Photon) and async (Canvas fallback) versions
|
|
81
|
+
const result = PhotonImage.new_from_base64(base64Data);
|
|
82
|
+
const image = result instanceof Promise ? await result : result;
|
|
83
|
+
const width = image.get_width();
|
|
84
|
+
const height = image.get_height();
|
|
85
|
+
image.free();
|
|
86
|
+
assert(width && height, 'Invalid image: cannot get width or height');
|
|
87
|
+
return { width, height };
|
|
63
88
|
}
|
|
64
89
|
|
|
65
90
|
/**
|
|
@@ -109,3 +134,30 @@ export function isValidJPEGImageBuffer(buffer: Buffer): boolean {
|
|
|
109
134
|
export function isValidImageBuffer(buffer: Buffer): boolean {
|
|
110
135
|
return isValidPNGImageBuffer(buffer) || isValidJPEGImageBuffer(buffer);
|
|
111
136
|
}
|
|
137
|
+
|
|
138
|
+
export interface ValidateScreenshotBufferOptions {
|
|
139
|
+
label: string;
|
|
140
|
+
minBufferSize?: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function validateScreenshotBuffer(
|
|
144
|
+
screenshotBuffer: Buffer | undefined,
|
|
145
|
+
{ label, minBufferSize = 0 }: ValidateScreenshotBufferOptions,
|
|
146
|
+
): asserts screenshotBuffer is Buffer {
|
|
147
|
+
const bufferSize = screenshotBuffer?.length ?? 0;
|
|
148
|
+
if (!screenshotBuffer || bufferSize === 0) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`${label} validation failed: buffer size ${bufferSize} bytes`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!isValidImageBuffer(screenshotBuffer)) {
|
|
155
|
+
throw new Error(`${label} buffer has invalid image format`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (minBufferSize > 0 && bufferSize < minBufferSize) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`${label} validation failed: buffer size ${bufferSize} bytes (minimum: ${minBufferSize})`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|