@aiscene/shared 8.0.1 → 8.0.3

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 (92) hide show
  1. package/dist/es/cli/cli-args.mjs +95 -0
  2. package/dist/es/cli/cli-error.mjs +24 -0
  3. package/dist/es/cli/cli-runner.mjs +10 -40
  4. package/dist/es/cli/index.mjs +4 -2
  5. package/dist/es/constants/example-code.mjs +2 -2
  6. package/dist/es/constants/index.mjs +6 -1
  7. package/dist/es/env/model-config-manager.mjs +3 -0
  8. package/dist/es/env/parse-model-config.mjs +6 -1
  9. package/dist/es/env/types.mjs +2 -1
  10. package/dist/es/env/utils.mjs +1 -9
  11. package/dist/es/extractor/index.mjs +2 -2
  12. package/dist/es/img/index.mjs +2 -2
  13. package/dist/es/img/info.mjs +7 -1
  14. package/dist/es/key-alias-utils.mjs +19 -0
  15. package/dist/es/mcp/base-server.mjs +10 -9
  16. package/dist/es/mcp/base-tools.mjs +72 -4
  17. package/dist/es/mcp/cli-report-session.mjs +78 -0
  18. package/dist/es/mcp/error-formatter.mjs +19 -0
  19. package/dist/es/mcp/index.mjs +2 -0
  20. package/dist/es/mcp/init-arg-utils.mjs +38 -0
  21. package/dist/es/mcp/inject-report-html-plugin.mjs +1 -1
  22. package/dist/es/mcp/tool-generator.mjs +65 -17
  23. package/dist/es/node/fs.mjs +1 -1
  24. package/dist/lib/cli/cli-args.js +138 -0
  25. package/dist/lib/cli/cli-error.js +61 -0
  26. package/dist/lib/cli/cli-runner.js +19 -46
  27. package/dist/lib/cli/index.js +8 -3
  28. package/dist/lib/constants/example-code.js +2 -2
  29. package/dist/lib/constants/index.js +23 -3
  30. package/dist/lib/env/model-config-manager.js +3 -0
  31. package/dist/lib/env/parse-model-config.js +6 -1
  32. package/dist/lib/env/types.js +4 -0
  33. package/dist/lib/env/utils.js +0 -11
  34. package/dist/lib/extractor/index.js +3 -0
  35. package/dist/lib/img/index.js +3 -0
  36. package/dist/lib/img/info.js +10 -1
  37. package/dist/lib/key-alias-utils.js +62 -0
  38. package/dist/lib/mcp/base-server.js +11 -10
  39. package/dist/lib/mcp/base-tools.js +74 -6
  40. package/dist/lib/mcp/cli-report-session.js +121 -0
  41. package/dist/lib/mcp/error-formatter.js +53 -0
  42. package/dist/lib/mcp/index.js +24 -10
  43. package/dist/lib/mcp/init-arg-utils.js +78 -0
  44. package/dist/lib/mcp/inject-report-html-plugin.js +1 -1
  45. package/dist/lib/mcp/tool-generator.js +74 -26
  46. package/dist/lib/node/fs.js +1 -1
  47. package/dist/types/cli/cli-args.d.ts +8 -0
  48. package/dist/types/cli/cli-error.d.ts +5 -0
  49. package/dist/types/cli/cli-runner.d.ts +4 -7
  50. package/dist/types/cli/index.d.ts +3 -1
  51. package/dist/types/constants/example-code.d.ts +1 -1
  52. package/dist/types/constants/index.d.ts +5 -0
  53. package/dist/types/env/types.d.ts +10 -2
  54. package/dist/types/env/utils.d.ts +0 -31
  55. package/dist/types/extractor/index.d.ts +1 -1
  56. package/dist/types/img/index.d.ts +1 -1
  57. package/dist/types/img/info.d.ts +5 -0
  58. package/dist/types/key-alias-utils.d.ts +9 -0
  59. package/dist/types/mcp/base-tools.d.ts +74 -5
  60. package/dist/types/mcp/cli-report-session.d.ts +12 -0
  61. package/dist/types/mcp/error-formatter.d.ts +12 -0
  62. package/dist/types/mcp/index.d.ts +2 -0
  63. package/dist/types/mcp/init-arg-utils.d.ts +13 -0
  64. package/dist/types/mcp/inject-report-html-plugin.d.ts +2 -2
  65. package/dist/types/mcp/launcher-helper.d.ts +3 -3
  66. package/dist/types/mcp/tool-generator.d.ts +3 -3
  67. package/dist/types/mcp/types.d.ts +9 -0
  68. package/package.json +1 -1
  69. package/src/cli/cli-args.ts +173 -0
  70. package/src/cli/cli-error.ts +24 -0
  71. package/src/cli/cli-runner.ts +37 -56
  72. package/src/cli/index.ts +3 -7
  73. package/src/constants/example-code.ts +2 -2
  74. package/src/constants/index.ts +10 -0
  75. package/src/env/model-config-manager.ts +3 -0
  76. package/src/env/parse-model-config.ts +19 -1
  77. package/src/env/types.ts +11 -2
  78. package/src/env/utils.ts +0 -50
  79. package/src/extractor/index.ts +1 -1
  80. package/src/img/index.ts +2 -0
  81. package/src/img/info.ts +27 -0
  82. package/src/key-alias-utils.ts +23 -0
  83. package/src/mcp/base-server.ts +10 -12
  84. package/src/mcp/base-tools.ts +218 -11
  85. package/src/mcp/cli-report-session.ts +130 -0
  86. package/src/mcp/error-formatter.ts +52 -0
  87. package/src/mcp/index.ts +2 -0
  88. package/src/mcp/init-arg-utils.ts +105 -0
  89. package/src/mcp/inject-report-html-plugin.ts +3 -3
  90. package/src/mcp/launcher-helper.ts +3 -3
  91. package/src/mcp/tool-generator.ts +98 -20
  92. package/src/mcp/types.ts +16 -1
@@ -12,11 +12,8 @@ export interface CLIRunnerOptions {
12
12
  version?: string;
13
13
  extraCommands?: CLIExtraCommand[];
14
14
  }
15
- export declare class CLIError extends Error {
16
- exitCode: number;
17
- constructor(message: string, exitCode?: number);
18
- }
19
- export declare function parseValue(raw: string): unknown;
20
- export declare function parseCliArgs(args: string[]): Record<string, unknown>;
15
+ export { parseCliArgs, parseValue } from './cli-args';
16
+ export { CLIError, reportCLIError } from './cli-error';
21
17
  export declare function removePrefix(name: string, prefix?: string): string;
22
- export declare function runToolsCLI(tools: BaseMidsceneTools, scriptName: string, options?: CLIRunnerOptions): Promise<void>;
18
+ type AnyMidsceneTools = BaseMidsceneTools<any, any>;
19
+ export declare function runToolsCLI(tools: AnyMidsceneTools, scriptName: string, options?: CLIRunnerOptions): Promise<void>;
@@ -1,2 +1,4 @@
1
- export { runToolsCLI, CLIError, parseValue, parseCliArgs, removePrefix, } from './cli-runner';
1
+ export { CLIError, reportCLIError } from './cli-error';
2
+ export { parseCliArgs, parseValue } from './cli-args';
3
+ export { runToolsCLI, removePrefix } from './cli-runner';
2
4
  export type { CLIRunnerOptions, CLIExtraCommand } from './cli-runner';
@@ -1,2 +1,2 @@
1
- export declare const PLAYWRIGHT_EXAMPLE_CODE = "\n// Reference the following code to generate Midscene test cases\n// The following is test code for Midscene AI, for reference\n// The following is Playwright syntax, you can use Playwright to assist in test generation\nIMPORTANT: Follow these exact type signatures for AI functions:\n\n// Type signatures for AI functions:\naiAct(prompt: string, options?: { cacheable?: boolean, deepThink?: 'unset' | true | false }): Promise<void>\naiInput(text: string, locate: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiTap(locate: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiHover(locate: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiDoubleClick(locate: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiKeyboardPress(key: string, locate?: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiScroll(locate: string | undefined, options: {\n direction?: 'up' | 'down' | 'left' | 'right',\n scrollType?: 'singleAction' | 'scrollToBottom' | 'scrollToTop' | 'scrollToRight' | 'scrollToLeft',\n distance?: number | null,\n deepLocate?: boolean,\n xpath?: string,\n cacheable?: boolean\n}): Promise<void>\naiAssert(assertion: string, options?: { errorMessage?: string }): Promise<void>\naiWaitFor(prompt: string, options?: { timeout?: number }): Promise<void>\naiQuery<T>(queryObject: Record<string, string>): Promise<T> // Extracts data from page based on descriptions\n\n// examples:\n// Reference the following code to generate Midscene test cases\n// The following is test code for Midscene AI, for reference\n// The following is Playwright syntax, you can use Playwright to assist in test generation\nimport { test as base } from '@playwright/test';\nimport type { PlayWrightAiFixtureType } from '@midscene/web/playwright';\nimport { PlaywrightAiFixture } from '@midscene/web/playwright';\n\nconst test = base.extend<PlayWrightAiFixtureType>(PlaywrightAiFixture({\n waitForNetworkIdleTimeout: 2000, // optional, the timeout for waiting for network idle between each action, default is 2000ms\n}));\n\n\ntest.beforeEach(async ({ page }) => {\n await page.goto('https://www.xxx.com/');\n await page.setViewportSize({ width: 1920, height: 1080 });\n});\n\ntest('ai shop', async ({\n aiAct,\n aiInput,\n aiAssert,\n aiQuery,\n aiKeyboardPress,\n aiHover,\n aiTap,\n aiWaitFor,\n agentForPage,\n page,\n}) => {\n // login\n await aiAssert('The page shows the login interface');\n await aiInput('user_name', 'in user name input');\n await aiInput('password', 'in password input');\n await aiKeyboardPress('Enter', 'Login Button');\n\n // check the login success\n await aiWaitFor('The page shows that the loading is complete');\n await aiAssert('The current page shows the product detail page');\n\n // check the product info\n const dataA = await aiQuery({\n userInfo: 'User information in the format {name: string}',\n theFirstProductInfo: 'The first product info in the format {name: string, price: number}',\n });\n expect(dataA.theFirstProductInfo.name).toBe('xxx');\n expect(dataA.theFirstProductInfo.price).toBe(100);\n\n\n // add to cart\n await aiTap('click add to cart button');\n \n await aiTap('click right top cart icon');\n await aiAssert('The cart icon shows the number 1');\n});\n";
1
+ export declare const PLAYWRIGHT_EXAMPLE_CODE = "\n// Reference the following code to generate Midscene test cases\n// The following is test code for Midscene AI, for reference\n// The following is Playwright syntax, you can use Playwright to assist in test generation\nIMPORTANT: Follow these exact type signatures for AI functions:\n\n// Type signatures for AI functions:\naiAct(prompt: string, options?: { cacheable?: boolean, deepThink?: 'unset' | true | false }): Promise<void>\naiInput(text: string, locate: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiTap(locate: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiHover(locate: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiDoubleClick(locate: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiKeyboardPress(key: string, locate?: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiScroll(locate: string | undefined, options: {\n direction?: 'up' | 'down' | 'left' | 'right',\n scrollType?: 'singleAction' | 'scrollToBottom' | 'scrollToTop' | 'scrollToRight' | 'scrollToLeft',\n distance?: number | null,\n deepLocate?: boolean,\n xpath?: string,\n cacheable?: boolean\n}): Promise<void>\naiAssert(assertion: string, options?: { errorMessage?: string }): Promise<void>\naiWaitFor(prompt: string, options?: { timeout?: number }): Promise<void>\naiQuery<T>(queryObject: Record<string, string>): Promise<T> // Extracts data from page based on descriptions\n\n// examples:\n// Reference the following code to generate Midscene test cases\n// The following is test code for Midscene AI, for reference\n// The following is Playwright syntax, you can use Playwright to assist in test generation\nimport { test as base } from '@playwright/test';\nimport type { PlayWrightAiFixtureType } from '@aiscene/web/playwright';\nimport { PlaywrightAiFixture } from '@aiscene/web/playwright';\n\nconst test = base.extend<PlayWrightAiFixtureType>(PlaywrightAiFixture({\n waitForNetworkIdleTimeout: 2000, // optional, the timeout for waiting for network idle between each action, default is 2000ms\n}));\n\n\ntest.beforeEach(async ({ page }) => {\n await page.goto('https://www.xxx.com/');\n await page.setViewportSize({ width: 1920, height: 1080 });\n});\n\ntest('ai shop', async ({\n aiAct,\n aiInput,\n aiAssert,\n aiQuery,\n aiKeyboardPress,\n aiHover,\n aiTap,\n aiWaitFor,\n agentForPage,\n page,\n}) => {\n // login\n await aiAssert('The page shows the login interface');\n await aiInput('user_name', 'in user name input');\n await aiInput('password', 'in password input');\n await aiKeyboardPress('Enter', 'Login Button');\n\n // check the login success\n await aiWaitFor('The page shows that the loading is complete');\n await aiAssert('The current page shows the product detail page');\n\n // check the product info\n const dataA = await aiQuery({\n userInfo: 'User information in the format {name: string}',\n theFirstProductInfo: 'The first product info in the format {name: string, price: number}',\n });\n expect(dataA.theFirstProductInfo.name).toBe('xxx');\n expect(dataA.theFirstProductInfo.price).toBe(100);\n\n\n // add to cart\n await aiTap('click add to cart button');\n \n await aiTap('click right top cart icon');\n await aiAssert('The cart icon shows the number 1');\n});\n";
2
2
  export declare const YAML_EXAMPLE_CODE = "\nCRITICAL - YAML Indentation Rules:\nFor actions with additional parameters (aiScroll, aiInput, aiKeyboardPress), the parameters must be SIBLING keys at the SAME indentation level as the action key, NOT nested children indented further.\nCORRECT (parameters align with the action key):\n - aiScroll:\n direction: 'down'\n scrollType: 'singleAction'\n distance: 500\n locate: \"main content area\"\n - aiInput: 'text value'\n locate: 'input field description'\nWRONG (parameters are indented further than the action key, DO NOT do this):\n - aiScroll:\n direction: 'down'\n scrollType: 'singleAction'\n - aiInput: 'text value'\n locate: 'input field description'\n\n1. Format:\n\nweb:\n url: \"starting_url\"\n viewportWidth: 1280\n viewportHeight: 960\n\ntasks:\n - name: \"descriptive task name\"\n flow:\n - aiTap: \"element description\"\n xpath: '/html/body/div[1]/button[1]'\n - aiInput: 'text value'\n locate: 'input field description'\n xpath: '/html/body/div[1]/input[1]'\n - aiScroll:\n direction: 'down'\n scrollType: 'singleAction'\n distance: 500\n locate: \"scrollable area description\"\n xpath: '/html/body/div[1]/main[1]'\n - aiAssert: \"expected state\"\n - sleep: 1000\n\n2. Action Types:\n- aiTap: for clicks (natural language targeting)\n- aiInput: for text input with 'locate' field\n- aiScroll: with direction and scrollType\n- aiAssert: for validations\n- sleep: for delays (milliseconds)\n\n\n\nYAML type\ntasks:\n - name: <name>\n continueOnError: <boolean> # Optional, whether to continue to the next task on error, defaults to false.\n flow:\n # Auto Planning (.ai)\n # ----------------\n\n # Perform an interaction. `ai` is a shorthand for `aiAct`.\n - ai: <prompt>\n cacheable: <boolean> # Optional, whether to cache the result of this API call when the [caching feature](./caching.mdx) is enabled. Defaults to True.\n\n # This usage is the same as `ai`.\n - aiAct: <prompt>\n cacheable: <boolean> # Optional, whether to cache the result of this API call when the [caching feature](./caching.mdx) is enabled. Defaults to True.\n\n # Instant Action (.aiTap, .aiDoubleClick, .aiHover, .aiInput, .aiKeyboardPress, .aiScroll)\n # ----------------\n\n # Tap an element described by a prompt.\n - aiTap: <prompt>\n deepLocate: <boolean> # Optional, whether to use deepLocate to precisely locate the element. Defaults to False.\n xpath: <xpath> # Optional, the xpath of the target element for the operation. If provided, Midscene will prioritize this xpath to find the element before using the cache and the AI model. Defaults to empty.\n cacheable: <boolean> # Optional, whether to cache the result of this API call when the [caching feature](./caching.mdx) is enabled. Defaults to True.\n\n # Double click an element described by a prompt.\n - aiDoubleClick: <prompt>\n deepLocate: <boolean> # Optional, whether to use deepLocate to precisely locate the element. Defaults to False.\n xpath: <xpath> # Optional, the xpath of the target element for the operation. If provided, Midscene will prioritize this xpath to find the element before using the cache and the AI model. Defaults to empty.\n cacheable: <boolean> # Optional, whether to cache the result of this API call when the [caching feature](./caching.mdx) is enabled. Defaults to True.\n\n # Hover over an element described by a prompt.\n - aiHover: <prompt>\n deepLocate: <boolean> # Optional, whether to use deepLocate to precisely locate the element. Defaults to False.\n xpath: <xpath> # Optional, the xpath of the target element for the operation. If provided, Midscene will prioritize this xpath to find the element before using the cache and the AI model. Defaults to empty.\n cacheable: <boolean> # Optional, whether to cache the result of this API call when the [caching feature](./caching.mdx) is enabled. Defaults to True.\n\n # Input text into an element described by a prompt.\n - aiInput: <final text content of the input>\n locate: <prompt>\n deepLocate: <boolean> # Optional, whether to use deepLocate to precisely locate the element. Defaults to False.\n xpath: <xpath> # Optional, the xpath of the target element for the operation. If provided, Midscene will prioritize this xpath to find the element before using the cache and the AI model. Defaults to empty.\n cacheable: <boolean> # Optional, whether to cache the result of this API call when the [caching feature](./caching.mdx) is enabled. Defaults to True.\n\n # Press a key (e.g., Enter, Tab, Escape) on an element described by a prompt.\n - aiKeyboardPress: <key>\n locate: <prompt>\n deepLocate: <boolean> # Optional, whether to use deepLocate to precisely locate the element. Defaults to False.\n xpath: <xpath> # Optional, the xpath of the target element for the operation. If provided, Midscene will prioritize this xpath to find the element before using the cache and the AI model. Defaults to empty.\n cacheable: <boolean> # Optional, whether to cache the result of this API call when the [caching feature](./caching.mdx) is enabled. Defaults to True.\n\n # Scroll globally or on an element described by a prompt.\n - aiScroll:\n direction: 'up' # or 'down' | 'left' | 'right'\n scrollType: 'singleAction' # or 'scrollToTop' | 'scrollToBottom' | 'scrollToLeft' | 'scrollToRight'\n distance: <number> # Optional, the scroll distance in pixels.\n locate: <prompt> # Optional, the element to scroll on.\n deepLocate: <boolean> # Optional, whether to use deepLocate to precisely locate the element. Defaults to False.\n xpath: <xpath> # Optional, the xpath of the target element for the operation. If provided, Midscene will prioritize this xpath to find the element before using the cache and the AI model. Defaults to empty.\n cacheable: <boolean> # Optional, whether to cache the result of this API call when the [caching feature](./caching.mdx) is enabled. Defaults to True.\n\n # Record the current screenshot with a description in the report file.\n - recordToReport: <title> # Optional, the title of the screenshot. If not provided, the title will be 'untitled'.\n content: <content> # Optional, the description of the screenshot.\n\n # Data Extraction\n # ----------------\n\n # Perform a query that returns a JSON object.\n - aiQuery: <prompt> # Remember to describe the format of the result in the prompt.\n name: <name> # The key for the query result in the JSON output.\n\n # More APIs\n # ----------------\n\n # Wait for a condition to be met, with a timeout (in ms, optional, defaults to 30000).\n - aiWaitFor: <prompt>\n timeout: <ms>\n\n # Perform an assertion.\n - aiAssert: <prompt>\n errorMessage: <error-message> # Optional, the error message to print if the assertion fails.\n\n # Wait for a specified amount of time.\n - sleep: <ms>\n\n # Execute a piece of JavaScript code in the web page context.\n - javascript: <javascript>\n name: <name> # Optional, assign a name to the return value, which will be used as a key in the JSON output.\n\n - name: <name>\n flow:\n # ...\n";
@@ -13,6 +13,11 @@ export declare enum NodeType {
13
13
  }
14
14
  export declare const PLAYGROUND_SERVER_PORT = 5800;
15
15
  export declare const SCRCPY_SERVER_PORT = 5700;
16
+ export declare const SCRCPY_ADB_CONNECT_TIMEOUT_MS = 10000;
17
+ export declare const SCRCPY_PUSH_TIMEOUT_MS = 10000;
18
+ export declare const SCRCPY_START_TIMEOUT_MS = 15000;
19
+ export declare const SCRCPY_VIDEO_STREAM_TIMEOUT_MS = 15000;
20
+ export declare const SCRCPY_PREVIEW_METADATA_TIMEOUT_MS: number;
16
21
  export declare const WEBDRIVER_ELEMENT_ID_KEY = "element-6066-11e4-a52e-4f735466cecf";
17
22
  export declare const DEFAULT_WDA_PORT = 8100;
18
23
  export declare const DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT = 5000;
@@ -53,6 +53,7 @@ export declare const MIDSCENE_ADB_REMOTE_PORT = "MIDSCENE_ADB_REMOTE_PORT";
53
53
  export declare const MIDSCENE_ANDROID_IME_STRATEGY = "MIDSCENE_ANDROID_IME_STRATEGY";
54
54
  export declare const MIDSCENE_IOS_DEVICE_UDID = "MIDSCENE_IOS_DEVICE_UDID";
55
55
  export declare const MIDSCENE_IOS_SIMULATOR_UDID = "MIDSCENE_IOS_SIMULATOR_UDID";
56
+ export declare const MIDSCENE_IOS_DEVICE_CLASS_OVERRIDE = "MIDSCENE_IOS_DEVICE_CLASS_OVERRIDE";
56
57
  export declare const MIDSCENE_CACHE = "MIDSCENE_CACHE";
57
58
  export declare const MIDSCENE_USE_VLM_UI_TARS = "MIDSCENE_USE_VLM_UI_TARS";
58
59
  export declare const MIDSCENE_USE_QWEN_VL = "MIDSCENE_USE_QWEN_VL";
@@ -203,7 +204,7 @@ export declare enum UITarsModelVersion {
203
204
  }
204
205
  /**
205
206
  * Callback to create custom OpenAI client instance
206
- * @param config - Resolved model configuration including apiKey, baseURL, modelName, intent, etc.
207
+ * @param config - Resolved model configuration including apiKey, baseURL, modelName, intent, slot, etc.
207
208
  * @returns OpenAI client instance (can be wrapped with langsmith, langfuse, etc.)
208
209
  *
209
210
  * Note: Wrapper functions like langsmith's wrapOpenAI() return the same OpenAI instance
@@ -293,9 +294,16 @@ export interface IModelConfig {
293
294
  uiTarsModelVersion?: UITarsModelVersion;
294
295
  modelDescription: string;
295
296
  /**
296
- * original intent from the config
297
+ * The semantic intent this config is requested for.
298
+ * For example, getModelConfig('planning') always returns intent === 'planning'.
297
299
  */
298
300
  intent: TIntent;
301
+ /**
302
+ * The model-config slot this config was resolved from.
303
+ * For example, getModelConfig('planning') may resolve from slot === 'default'
304
+ * when MIDSCENE_PLANNING_MODEL_NAME is not configured.
305
+ */
306
+ slot: TIntent;
299
307
  /**
300
308
  * Custom OpenAI client factory function
301
309
  *
@@ -3,36 +3,5 @@ import { ModelConfigManager } from './model-config-manager';
3
3
  import { type GLOBAL_ENV_KEYS, type MODEL_ENV_KEYS } from './types';
4
4
  export declare const globalModelConfigManager: ModelConfigManager;
5
5
  export declare const globalConfigManager: GlobalConfigManager;
6
- /**
7
- * Interface for devices that support getTimestamp method.
8
- * This is a minimal interface to avoid circular dependencies with @midscene/core.
9
- */
10
- export interface DeviceWithTimestamp {
11
- getTimestamp?: () => Promise<number>;
12
- }
13
- /**
14
- * Get the current timestamp, optionally from the target device.
15
- *
16
- * When useDeviceTimestamp is enabled and a device with getTimestamp is provided,
17
- * this function will return the device's time. Otherwise, it returns the system time.
18
- *
19
- * This is useful when:
20
- * - Testing on devices with different time zones
21
- * - Debugging time-sensitive features
22
- * - The system clock and device clock are not synchronized
23
- *
24
- * @param device Optional device interface that supports getTimestamp
25
- * @param useDeviceTimestamp Whether to use device timestamp (from agent config)
26
- * @returns Timestamp in milliseconds
27
- *
28
- * @example
29
- * // Without device - always returns system time
30
- * const systemTime = await getCurrentTime();
31
- *
32
- * @example
33
- * // With device and config enabled - returns device time
34
- * const deviceTime = await getCurrentTime(androidDevice, true);
35
- */
36
- export declare function getCurrentTime(device?: DeviceWithTimestamp, useDeviceTimestamp?: boolean): Promise<number>;
37
6
  export declare const getPreferredLanguage: () => string;
38
7
  export declare const overrideAIConfig: (newConfig: Partial<Record<(typeof GLOBAL_ENV_KEYS)[number] | (typeof MODEL_ENV_KEYS)[number], string>>, extendMode?: boolean) => void;
@@ -28,5 +28,5 @@ export { extractTextWithPosition as webExtractTextWithPosition } from './web-ext
28
28
  export { extractTreeNode as webExtractNodeTree } from './web-extractor';
29
29
  export { extractTreeNodeAsString as webExtractNodeTreeAsString } from './web-extractor';
30
30
  export { getXpathsByPoint, getXpathsById, getNodeInfoByXpath, getElementInfoByXpath, getElementXpath, } from './locator';
31
- export { generateElementByRect } from './dom-util';
31
+ export { generateElementByPoint, generateElementByRect } from './dom-util';
32
32
  export { isNotContainerElement } from './dom-util';
@@ -1,3 +1,3 @@
1
- export { imageInfoOfBase64, isValidPNGImageBuffer, isValidJPEGImageBuffer, isValidImageBuffer, } from './info';
1
+ export { imageInfoOfBase64, isValidPNGImageBuffer, isValidJPEGImageBuffer, isValidImageBuffer, validateScreenshotBuffer, type ValidateScreenshotBufferOptions, } from './info';
2
2
  export { resizeAndConvertImgBuffer, resizeImgBase64, zoomForGPT4o, saveBase64Image, paddingToMatchBlockByBase64, cropByRect, scaleImage, localImg2Base64, httpImg2Base64, preProcessImageUrl, parseBase64, createImgBase64ByFormat, } from './transform';
3
3
  export { processImageElementInfo, compositeElementInfoImg, annotateRects, } from './box-select';
@@ -27,3 +27,8 @@ export declare function isValidJPEGImageBuffer(buffer: Buffer): boolean;
27
27
  * @returns true if the Buffer is a valid PNG or JPEG image, otherwise false
28
28
  */
29
29
  export declare function isValidImageBuffer(buffer: Buffer): boolean;
30
+ export interface ValidateScreenshotBufferOptions {
31
+ label: string;
32
+ minBufferSize?: number;
33
+ }
34
+ export declare function validateScreenshotBuffer(screenshotBuffer: Buffer | undefined, { label, minBufferSize }: ValidateScreenshotBufferOptions): asserts screenshotBuffer is Buffer;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Internal-only helpers for CLI/MCP argument key aliasing.
3
+ * Not re-exported from the package entry point — keep consumers within
4
+ * `cli/` and `mcp/`.
5
+ */
6
+ export declare function kebabToCamel(str: string): string;
7
+ export declare function camelToKebab(str: string): string;
8
+ export declare function getKeyAliases(key: string): string[];
9
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
@@ -1,13 +1,53 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { BaseAgent, BaseDevice, IMidsceneTools, ToolDefinition } from './types';
2
+ import type { z } from 'zod';
3
+ import { type CliReportSession } from './cli-report-session';
4
+ import type { BaseAgent, BaseDevice, IMidsceneTools, ToolCliMetadata, ToolDefinition, ToolSchema } from './types';
3
5
  /**
4
- * Base class for platform-specific MCP tools
5
- * Generic type TAgent allows subclasses to use their specific agent types
6
+ * Declarative description of a platform's agent init args.
7
+ * Collapses the `extractAgentInitParam` / `sanitizeToolArgs` /
8
+ * `getAgentInitArgSchema` trio into a single data declaration.
6
9
  */
7
- export declare abstract class BaseMidsceneTools<TAgent extends BaseAgent = BaseAgent> implements IMidsceneTools {
10
+ export interface InitArgSpec<TInitParam> {
11
+ /** Arg namespace, e.g. `android`, `ios`. */
12
+ namespace: string;
13
+ /** Zod shape describing the init args. Field names drive the MCP schema. */
14
+ shape: Record<string, z.ZodTypeAny>;
15
+ /**
16
+ * Optional CLI presentation hints. These affect `--help` output for
17
+ * single-platform CLIs but do not alter MCP/YAML protocol keys.
18
+ */
19
+ cli?: {
20
+ /** Prefer bare `--device-id`-style options in platform CLI help output. */
21
+ preferBareKeys?: boolean;
22
+ /** Override the displayed option name for specific init arg fields. */
23
+ preferredNames?: Record<string, string>;
24
+ };
25
+ /**
26
+ * Adapt extracted namespaced args into the concrete `TInitParam` passed to
27
+ * `ensureAgent`. Defaults to returning the raw extracted record.
28
+ */
29
+ adapt?: (extracted: Record<string, unknown> | undefined) => TInitParam | undefined;
30
+ }
31
+ /**
32
+ * Base class for platform-specific MCP tools.
33
+ * @typeParam TAgent - Platform-specific agent type.
34
+ * @typeParam TInitParam - Platform-specific init parameter consumed by
35
+ * `ensureAgent`. Defaults to `undefined` for platforms that take no args.
36
+ */
37
+ export declare abstract class BaseMidsceneTools<TAgent extends BaseAgent = BaseAgent, TInitParam = unknown> implements IMidsceneTools {
8
38
  protected mcpServer?: McpServer;
9
39
  protected agent?: TAgent;
10
40
  protected toolDefinitions: ToolDefinition[];
41
+ /**
42
+ * Declarative init-arg spec. Subclasses that accept CLI/MCP init args should
43
+ * set this once and get `extractAgentInitParam` / `sanitizeToolArgs` /
44
+ * `getAgentInitArgSchema` auto-implemented.
45
+ *
46
+ * Declared with `declare` so that TS doesn't emit an `Object.defineProperty`
47
+ * for this field on the base constructor, which would otherwise overwrite
48
+ * a subclass field initializer under `useDefineForClassFields`.
49
+ */
50
+ protected readonly initArgSpec?: InitArgSpec<TInitParam>;
11
51
  /**
12
52
  * Ensure agent is initialized and ready for use.
13
53
  * Must be implemented by subclasses to create platform-specific agent.
@@ -15,11 +55,40 @@ export declare abstract class BaseMidsceneTools<TAgent extends BaseAgent = BaseA
15
55
  * @returns Promise resolving to initialized agent instance
16
56
  * @throws Error if agent initialization fails
17
57
  */
18
- protected abstract ensureAgent(initParam?: string): Promise<TAgent>;
58
+ protected abstract ensureAgent(initParam?: TInitParam): Promise<TAgent>;
59
+ private getInitArgKeys;
60
+ /**
61
+ * Extract a platform-specific agent init parameter from CLI/MCP tool args.
62
+ */
63
+ protected extractAgentInitParam(args: Record<string, unknown>): TInitParam | undefined;
64
+ /**
65
+ * Remove platform-specific init args before dispatching a tool payload to the action itself.
66
+ */
67
+ protected sanitizeToolArgs(args: Record<string, unknown>): Record<string, unknown>;
68
+ /**
69
+ * Expose platform-specific init args on action/common tool schemas.
70
+ */
71
+ protected getAgentInitArgSchema(): ToolSchema;
72
+ /**
73
+ * Expose CLI-only metadata for platform init args so single-platform help can
74
+ * show ergonomic bare flags while the underlying schema stays namespaced.
75
+ * When `preferBareKeys` is enabled, single-platform CLIs only accept the
76
+ * bare spellings; namespaced dotted spellings remain available through the
77
+ * MCP/YAML schema instead of the platform CLI surface.
78
+ */
79
+ protected getAgentInitArgCliMetadata(): ToolCliMetadata | undefined;
19
80
  /**
20
81
  * Optional: prepare platform-specific tools (e.g., device connection)
21
82
  */
22
83
  protected preparePlatformTools(): ToolDefinition[];
84
+ protected getCliReportSessionName(): string | undefined;
85
+ protected createNewCliReportSession(targetIdentity?: string): CliReportSession | undefined;
86
+ protected commitCliReportSession(session?: CliReportSession): void;
87
+ protected readCliReportFileName(): string | undefined;
88
+ protected readCliReportAgentOptions(): {
89
+ reportFileName: string;
90
+ reportAttributes: Record<string, string>;
91
+ } | undefined;
23
92
  /**
24
93
  * Must be implemented by subclasses to create a temporary device instance
25
94
  * This allows getting real actionSpace without connecting to device
@@ -0,0 +1,12 @@
1
+ export interface CliReportSession {
2
+ version: 1;
3
+ sessionName: string;
4
+ targetIdentity?: string;
5
+ reportFileName: string;
6
+ reportPath: string;
7
+ createdAt: number;
8
+ }
9
+ export declare function generateCliReportSession(sessionName: string, targetIdentity?: string): CliReportSession;
10
+ export declare function writeCliReportSession(session: CliReportSession): void;
11
+ export declare function createCliReportSession(sessionName: string, targetIdentity?: string): CliReportSession;
12
+ export declare function readCliReportSession(sessionName: string): CliReportSession | undefined;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Extract a human-readable message from an unknown thrown value.
3
+ *
4
+ * Many SDK/transport layers reject with structured objects (e.g.
5
+ * `{ code, message }`, `{ error: { message } }`, `{ cause: { message } }`)
6
+ * rather than `Error` instances. `String(obj)` collapses those to
7
+ * `"[object Object]"`, which is useless for diagnostics. This helper walks
8
+ * the common shapes, falls back to `JSON.stringify`, and finally to
9
+ * `Object.prototype.toString.call` so that callers always get something
10
+ * actionable in logs and surfaced tool results.
11
+ */
12
+ export declare function getErrorMessage(error: unknown): string;
@@ -1,5 +1,7 @@
1
1
  export * from './base-server';
2
2
  export * from './base-tools';
3
+ export * from './init-arg-utils';
4
+ export * from './error-formatter';
3
5
  export * from './tool-generator';
4
6
  export * from './types';
5
7
  export * from './inject-report-html-plugin';
@@ -0,0 +1,13 @@
1
+ import type { z } from 'zod';
2
+ import type { ToolSchema } from './types';
3
+ export declare function extractNamespacedArgs<TFieldName extends string, TArgs extends Record<string, unknown> = Record<string, unknown>>(args: Record<string, unknown>, namespace: string, keys: readonly TFieldName[]): TArgs | undefined;
4
+ export declare function sanitizeNamespacedArgs(args: Record<string, unknown>, namespace: string, keys: readonly string[]): Record<string, unknown>;
5
+ /**
6
+ * Build a flat MCP tool schema whose keys are dotted `"<namespace>.<field>"`.
7
+ *
8
+ * We intentionally stay flat (rather than `{ namespace: z.object({...}) }`) so
9
+ * that CLI (`--android.device-id`), MCP clients, and `--help` output all share
10
+ * the same spelling. `readNamespacedArg` understands all three input shapes:
11
+ * nested namespace object, dotted flat key, and bare key fallback.
12
+ */
13
+ export declare function createNamespacedInitArgSchema(namespace: string, shape: Record<string, z.ZodTypeAny>): ToolSchema;
@@ -2,12 +2,12 @@ interface RslibPluginApi {
2
2
  onAfterBuild: (callback: () => void) => void;
3
3
  }
4
4
  /**
5
- * Rslib plugin to inject report HTML from @midscene/core dist into MCP bundle.
5
+ * Rslib plugin to inject report HTML from @aiscene/core dist into MCP bundle.
6
6
  * This runs after build and reads the already-injected HTML from core.
7
7
  *
8
8
  * Prerequisites:
9
9
  * - @midscene/report must be in devDependencies to ensure correct build order
10
- * - @midscene/core dist must exist with injected HTML
10
+ * - @aiscene/core dist must exist with injected HTML
11
11
  *
12
12
  * @param packageDir - The directory of the MCP package (use __dirname)
13
13
  */
@@ -9,7 +9,7 @@ export interface LaunchMCPServerOptions extends HttpLaunchOptions {
9
9
  verbose?: boolean;
10
10
  }
11
11
  /**
12
- * Generic agent type (avoid importing from @midscene/core to prevent circular deps)
12
+ * Generic agent type (avoid importing from @aiscene/core to prevent circular deps)
13
13
  */
14
14
  export interface GenericAgent<TDevice = any> {
15
15
  interface: TDevice;
@@ -58,8 +58,8 @@ export interface MCPServerLauncherConfig<AgentType extends GenericAgent = Generi
58
58
  *
59
59
  * @example
60
60
  * ```typescript
61
- * import { createMCPServerLauncher } from '@midscene/shared/mcp';
62
- * import { Agent } from '@midscene/core/agent';
61
+ * import { createMCPServerLauncher } from '@aiscene/shared/mcp';
62
+ * import { Agent } from '@aiscene/core/agent';
63
63
  * import { WebMidsceneTools } from './web-tools';
64
64
  * import { WebMCPServer } from './server';
65
65
  *
@@ -1,10 +1,10 @@
1
- import type { ActionSpaceItem, BaseAgent, ToolDefinition } from './types';
1
+ import type { ActionSpaceItem, BaseAgent, ToolCliMetadata, ToolDefinition, ToolSchema } from './types';
2
2
  /**
3
3
  * Converts DeviceAction from actionSpace into MCP ToolDefinition
4
4
  * This is the core logic that removes need for hardcoded tool definitions
5
5
  */
6
- export declare function generateToolsFromActionSpace(actionSpace: ActionSpaceItem[], getAgent: () => Promise<BaseAgent>): ToolDefinition[];
6
+ export declare function generateToolsFromActionSpace(actionSpace: ActionSpaceItem[], getAgent: (args?: Record<string, unknown>) => Promise<BaseAgent>, sanitizeArgs?: (args: Record<string, unknown>) => Record<string, unknown>, initArgSchema?: ToolSchema, initArgCliMetadata?: ToolCliMetadata): ToolDefinition[];
7
7
  /**
8
8
  * Generate common tools (screenshot, act)
9
9
  */
10
- export declare function generateCommonTools(getAgent: () => Promise<BaseAgent>): ToolDefinition[];
10
+ export declare function generateCommonTools(getAgent: (args?: Record<string, unknown>) => Promise<BaseAgent>, initArgSchema?: ToolSchema, initArgCliMetadata?: ToolCliMetadata): ToolDefinition[];
@@ -49,6 +49,13 @@ export type ToolHandler<T = Record<string, unknown>> = (args: T) => Promise<Tool
49
49
  * Tool schema type using Zod
50
50
  */
51
51
  export type ToolSchema = Record<string, z.ZodTypeAny>;
52
+ export interface ToolCliOption {
53
+ preferredName?: string;
54
+ aliases?: string[];
55
+ }
56
+ export interface ToolCliMetadata {
57
+ options?: Record<string, ToolCliOption>;
58
+ }
52
59
  /**
53
60
  * Tool definition for MCP server
54
61
  */
@@ -57,6 +64,7 @@ export interface ToolDefinition<T = Record<string, unknown>> {
57
64
  description: string;
58
65
  schema: ToolSchema;
59
66
  handler: ToolHandler<T>;
67
+ cli?: ToolCliMetadata;
60
68
  }
61
69
  /**
62
70
  * Tool type for mcpKitForAgent return value
@@ -86,6 +94,7 @@ export interface BaseAgent {
86
94
  callActionInActionSpace?: (actionName: string, params?: unknown) => Promise<unknown>;
87
95
  aiAction?: (description: string, params?: Record<string, unknown>) => Promise<unknown>;
88
96
  aiWaitFor?: (assertion: string, options: Record<string, unknown>) => Promise<unknown>;
97
+ aiAssert?: (assertion: string, msg?: string, options?: Record<string, unknown>) => Promise<unknown>;
89
98
  }
90
99
  /**
91
100
  * Base device interface for temporary device instances
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiscene/shared",
3
- "version": "8.0.1",
3
+ "version": "8.0.3",
4
4
  "repository": "https://github.com/web-infra-dev/midscene",
5
5
  "homepage": "https://midscenejs.com/",
6
6
  "types": "./dist/types/index.d.ts",
@@ -0,0 +1,173 @@
1
+ import { z } from 'zod';
2
+ import { getKeyAliases } from '../key-alias-utils';
3
+ import type { ToolCliOption, ToolDefinition } from '../mcp/types';
4
+
5
+ export function parseValue(raw: string): unknown {
6
+ if (raw.startsWith('{') || raw.startsWith('[')) {
7
+ try {
8
+ return JSON.parse(raw);
9
+ } catch {
10
+ // Not valid JSON, treat as string below
11
+ }
12
+ }
13
+
14
+ if (/^-?\d+(\.\d+)?$/.test(raw)) {
15
+ return Number(raw);
16
+ }
17
+
18
+ return raw;
19
+ }
20
+
21
+ function walkCliArgs(
22
+ args: string[],
23
+ setArgValue: (key: string, value: unknown) => void,
24
+ ): void {
25
+ for (let i = 0; i < args.length; i++) {
26
+ const arg = args[i];
27
+ if (!arg.startsWith('--')) continue;
28
+
29
+ const body = arg.slice(2);
30
+ const eqIdx = body.indexOf('=');
31
+
32
+ if (eqIdx >= 0) {
33
+ setArgValue(body.slice(0, eqIdx), parseValue(body.slice(eqIdx + 1)));
34
+ } else if (args[i + 1] && !args[i + 1].startsWith('--')) {
35
+ i++;
36
+ setArgValue(body, parseValue(args[i]));
37
+ } else {
38
+ setArgValue(body, true);
39
+ }
40
+ }
41
+ }
42
+
43
+ export function parseCliArgs(args: string[]): Record<string, unknown> {
44
+ const result: Record<string, unknown> = {};
45
+
46
+ walkCliArgs(args, (key, value) => {
47
+ result[key] = value;
48
+ });
49
+
50
+ return result;
51
+ }
52
+
53
+ function formatCliOptionName(name: string): string {
54
+ return `--${name}`;
55
+ }
56
+
57
+ export function getCliOptionDisplay(
58
+ key: string,
59
+ cliOption?: ToolCliOption,
60
+ ): { label: string; aliases: string[] } {
61
+ const label = formatCliOptionName(cliOption?.preferredName ?? key);
62
+ const aliases = [...new Set(cliOption?.aliases ?? [])]
63
+ .map((alias) => formatCliOptionName(alias))
64
+ .filter((alias) => alias !== label);
65
+
66
+ return { label, aliases };
67
+ }
68
+
69
+ function getAcceptedCliOptionNames(
70
+ key: string,
71
+ cliOption?: ToolCliOption,
72
+ ): string[] {
73
+ return [
74
+ ...new Set(
75
+ cliOption
76
+ ? [cliOption.preferredName ?? key, ...(cliOption.aliases ?? [])]
77
+ : [key, ...getKeyAliases(key)],
78
+ ),
79
+ ];
80
+ }
81
+
82
+ function toOptionalCliSchemaField(field: unknown): z.ZodTypeAny {
83
+ if (
84
+ typeof field === 'object' &&
85
+ field !== null &&
86
+ typeof (field as z.ZodTypeAny).optional === 'function'
87
+ ) {
88
+ return (field as z.ZodTypeAny).optional();
89
+ }
90
+
91
+ const description =
92
+ typeof field === 'object' &&
93
+ field !== null &&
94
+ 'description' in field &&
95
+ typeof (field as { description?: unknown }).description === 'string'
96
+ ? (field as { description: string }).description
97
+ : undefined;
98
+ return description ? z.any().describe(description) : z.any();
99
+ }
100
+
101
+ function buildCliArgSchema(def: ToolDefinition): Record<string, z.ZodTypeAny> {
102
+ return Object.fromEntries(
103
+ Object.entries(def.schema).flatMap(([key, zodType]) =>
104
+ getAcceptedCliOptionNames(key, def.cli?.options?.[key]).map((cliKey) => [
105
+ cliKey,
106
+ toOptionalCliSchemaField(zodType),
107
+ ]),
108
+ ),
109
+ );
110
+ }
111
+
112
+ function buildDisallowedCliSpellings(def: ToolDefinition): Set<string> {
113
+ const disallowedSpellings = new Set<string>();
114
+
115
+ for (const [key] of Object.entries(def.schema)) {
116
+ const cliOption = def.cli?.options?.[key];
117
+ const acceptedNames = new Set(getAcceptedCliOptionNames(key, cliOption));
118
+ const knownSpellings = new Set<string>([
119
+ key,
120
+ ...getKeyAliases(key),
121
+ ...(cliOption?.preferredName
122
+ ? getKeyAliases(cliOption.preferredName)
123
+ : []),
124
+ ...(cliOption?.aliases ?? []),
125
+ ]);
126
+
127
+ for (const spelling of knownSpellings) {
128
+ if (!acceptedNames.has(spelling)) {
129
+ disallowedSpellings.add(spelling);
130
+ }
131
+ }
132
+ }
133
+
134
+ return disallowedSpellings;
135
+ }
136
+
137
+ export function formatCliValidationError(
138
+ scriptName: string,
139
+ commandName: string,
140
+ def: ToolDefinition,
141
+ rawArgs: Record<string, unknown>,
142
+ ): string | undefined {
143
+ if (Object.keys(def.schema).length === 0) {
144
+ return undefined;
145
+ }
146
+
147
+ const cliSchema = z.object(buildCliArgSchema(def)).strict();
148
+ const parsed = cliSchema.safeParse(rawArgs);
149
+ if (parsed.success) {
150
+ return undefined;
151
+ }
152
+
153
+ const disallowedSpellings = buildDisallowedCliSpellings(def);
154
+ const unknownKeys = parsed.error.issues.flatMap((issue) =>
155
+ issue.code === 'unrecognized_keys' ? issue.keys : [],
156
+ );
157
+
158
+ if (unknownKeys.length > 0) {
159
+ return unknownKeys
160
+ .map((key) => {
161
+ if (disallowedSpellings.has(key)) {
162
+ return `Unsupported option "--${key}" for ${scriptName} ${commandName}.`;
163
+ }
164
+ return `Unknown option "--${key}" for ${scriptName} ${commandName}.`;
165
+ })
166
+ .join('\n');
167
+ }
168
+
169
+ const [issue] = parsed.error.issues;
170
+ const optionName =
171
+ typeof issue?.path[0] === 'string' ? `--${issue.path[0]}` : 'CLI arguments';
172
+ return `Invalid value for "${optionName}" in ${scriptName} ${commandName}: ${issue?.message ?? parsed.error.message}`;
173
+ }
@@ -0,0 +1,24 @@
1
+ export class CLIError extends Error {
2
+ constructor(
3
+ message: string,
4
+ public exitCode = 1,
5
+ ) {
6
+ super(message);
7
+ }
8
+ }
9
+
10
+ export function reportCLIError(
11
+ error: unknown,
12
+ log: (
13
+ message?: unknown,
14
+ ...optionalParams: unknown[]
15
+ ) => void = console.error,
16
+ ): number {
17
+ if (error instanceof CLIError) {
18
+ log(error.message);
19
+ return error.exitCode;
20
+ }
21
+
22
+ log(error);
23
+ return 1;
24
+ }