@midscene/test 1.14.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,367 @@
1
+ "use strict";
2
+ var __webpack_modules__ = {
3
+ "@midscene/core" (module) {
4
+ module.exports = require("@midscene/core");
5
+ },
6
+ "@midscene/shared/env" (module) {
7
+ module.exports = require("@midscene/shared/env");
8
+ },
9
+ chalk (module) {
10
+ module.exports = require("chalk");
11
+ },
12
+ dotenv (module) {
13
+ module.exports = require("dotenv");
14
+ },
15
+ "node:fs" (module) {
16
+ module.exports = require("node:fs");
17
+ },
18
+ "node:path" (module) {
19
+ module.exports = require("node:path");
20
+ }
21
+ };
22
+ var __webpack_module_cache__ = {};
23
+ function __webpack_require__(moduleId) {
24
+ var cachedModule = __webpack_module_cache__[moduleId];
25
+ if (void 0 !== cachedModule) return cachedModule.exports;
26
+ var module = __webpack_module_cache__[moduleId] = {
27
+ exports: {}
28
+ };
29
+ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
30
+ return module.exports;
31
+ }
32
+ (()=>{
33
+ __webpack_require__.n = (module)=>{
34
+ var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
35
+ __webpack_require__.d(getter, {
36
+ a: getter
37
+ });
38
+ return getter;
39
+ };
40
+ })();
41
+ (()=>{
42
+ __webpack_require__.d = (exports1, definition)=>{
43
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
44
+ enumerable: true,
45
+ get: definition[key]
46
+ });
47
+ };
48
+ })();
49
+ (()=>{
50
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
51
+ })();
52
+ (()=>{
53
+ __webpack_require__.r = (exports1)=>{
54
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
55
+ value: 'Module'
56
+ });
57
+ Object.defineProperty(exports1, '__esModule', {
58
+ value: true
59
+ });
60
+ };
61
+ })();
62
+ var __webpack_exports__ = {};
63
+ (()=>{
64
+ __webpack_require__.r(__webpack_exports__);
65
+ __webpack_require__.d(__webpack_exports__, {
66
+ buildModelVerifyCurlCommands: ()=>buildModelVerifyCurlCommands,
67
+ loadDotenvConfig: ()=>loadDotenvConfig,
68
+ runModelCommand: ()=>runModelCommand
69
+ });
70
+ var core_ = __webpack_require__("@midscene/core");
71
+ var env_ = __webpack_require__("@midscene/shared/env");
72
+ var external_chalk_ = __webpack_require__("chalk");
73
+ var external_chalk_default = /*#__PURE__*/ __webpack_require__.n(external_chalk_);
74
+ var external_node_fs_ = __webpack_require__("node:fs");
75
+ var external_node_path_ = __webpack_require__("node:path");
76
+ var external_dotenv_ = __webpack_require__("dotenv");
77
+ var external_dotenv_default = /*#__PURE__*/ __webpack_require__.n(external_dotenv_);
78
+ function loadDotenvConfig(options = {}) {
79
+ const dotEnvConfigFile = (0, external_node_path_.join)(options.cwd ?? process.cwd(), '.env');
80
+ if (!(0, external_node_fs_.existsSync)(dotEnvConfigFile)) return;
81
+ options.log?.(` Env file: ${dotEnvConfigFile}`);
82
+ external_dotenv_default().config({
83
+ path: dotEnvConfigFile,
84
+ debug: options.dotenvDebug,
85
+ override: options.dotenvOverride
86
+ });
87
+ }
88
+ const DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1';
89
+ const MODEL_VERIFY_SEPARATOR = '────────────────────────────────────────';
90
+ const modelCommandUsage = (commandName)=>`Usage:
91
+ ${commandName} model verify
92
+
93
+ Verify model connectivity and Midscene compatibility for default, planning, and insight models.
94
+ Reads .env from the current working directory, overriding shell environment variables.
95
+ Shows endpoints, model names, request counts, and per-check results with duration.
96
+ Returns exit code 0 on success and 1 on failure.
97
+
98
+ Options:
99
+ --help, -h Show this help
100
+ `;
101
+ function assertNoModelVerifyOptions(args, commandName) {
102
+ for (const arg of args)throw new Error(`Unknown option for ${commandName} model verify: ${arg}`);
103
+ }
104
+ function formatTable(headers, rows) {
105
+ const widths = headers.map((header, index)=>Math.max(header.length, ...rows.map((row)=>row[index].length)));
106
+ const formatRow = (row)=>row.map((cell, index)=>cell.padEnd(widths[index])).join(' ').trimEnd();
107
+ return [
108
+ formatRow(headers),
109
+ ...rows.map(formatRow)
110
+ ].join('\n');
111
+ }
112
+ function displayEndpoint(endpoint) {
113
+ try {
114
+ const url = new URL(endpoint);
115
+ url.username = '';
116
+ url.password = '';
117
+ url.search = '';
118
+ url.hash = '';
119
+ return url.toString();
120
+ } catch {
121
+ return '(invalid endpoint URL)';
122
+ }
123
+ }
124
+ function shellSingleQuote(value) {
125
+ return `'${value.replace(/'/g, "'\\''")}'`;
126
+ }
127
+ function buildChatCompletionsUrl(baseURL) {
128
+ const normalizedBaseURL = (baseURL || DEFAULT_OPENAI_BASE_URL).replace(/\/+$/, '');
129
+ if (normalizedBaseURL.endsWith('/chat/completions')) return normalizedBaseURL;
130
+ return `${normalizedBaseURL}/chat/completions`;
131
+ }
132
+ function buildCurlCommand(modelConfig) {
133
+ const payload = {
134
+ model: modelConfig.modelName,
135
+ messages: [
136
+ {
137
+ role: 'user',
138
+ content: 'What is 1+1?'
139
+ }
140
+ ]
141
+ };
142
+ return [
143
+ `curl -X POST ${shellSingleQuote(buildChatCompletionsUrl(modelConfig.openaiBaseURL))} \\`,
144
+ ` -H ${shellSingleQuote(`Authorization: Bearer ${modelConfig.openaiApiKey || ''}`)} \\`,
145
+ ` -H ${shellSingleQuote('Content-Type: application/json')} \\`,
146
+ ` -d ${shellSingleQuote(JSON.stringify(payload, null, 2))}`
147
+ ].join('\n');
148
+ }
149
+ function buildCurlDedupKey(modelConfig) {
150
+ return JSON.stringify({
151
+ baseURL: modelConfig.openaiBaseURL || DEFAULT_OPENAI_BASE_URL,
152
+ apiKey: modelConfig.openaiApiKey || '',
153
+ modelName: modelConfig.modelName
154
+ });
155
+ }
156
+ function buildModelVerifyCurlCommands(configs) {
157
+ const commandMap = new Map();
158
+ for (const item of configs){
159
+ const key = buildCurlDedupKey(item.modelConfig);
160
+ const existing = commandMap.get(key);
161
+ if (existing) {
162
+ existing.intents.push(item.intent);
163
+ continue;
164
+ }
165
+ commandMap.set(key, {
166
+ intents: [
167
+ item.intent
168
+ ],
169
+ curl: buildCurlCommand(item.modelConfig),
170
+ usesDefaultBaseURL: !item.modelConfig.openaiBaseURL
171
+ });
172
+ }
173
+ return [
174
+ ...commandMap.values()
175
+ ];
176
+ }
177
+ function formatModelVerifyFailureOutput(message, curlCommands) {
178
+ const details = message?.trim() || 'No failure details were generated.';
179
+ const curlSection = curlCommands.map((item)=>{
180
+ const baseUrlNote = item.usesDefaultBaseURL ? ' (base URL not configured; using OpenAI SDK default)' : '';
181
+ return external_chalk_default().gray(`# ${item.intents.join(', ')}${baseUrlNote}\n${item.curl}`);
182
+ }).join('\n\n');
183
+ return [
184
+ external_chalk_default().red.bold('❌ Model verify failed with messages:'),
185
+ MODEL_VERIFY_SEPARATOR,
186
+ '',
187
+ details,
188
+ '',
189
+ MODEL_VERIFY_SEPARATOR,
190
+ 'Generated curl requests for basic API connectivity:',
191
+ 'If the error is a basic connectivity issue, use these requests to test the base URL, API key, and model name directly.',
192
+ 'These commands contain your API key. Do not share them publicly.',
193
+ '',
194
+ curlSection
195
+ ].join('\n');
196
+ }
197
+ function getDefaultDeps(io) {
198
+ return {
199
+ loadDotenv: ()=>{
200
+ loadDotenvConfig({
201
+ dotenvDebug: true,
202
+ dotenvOverride: true,
203
+ log: io.stdout
204
+ });
205
+ },
206
+ getModelConfig: (intent)=>env_.globalModelConfigManager.getModelConfig(intent),
207
+ verifyModel: core_.runConnectivityTest
208
+ };
209
+ }
210
+ async function runModelVerifyCommand(args, deps, io, commandName) {
211
+ try {
212
+ if (args.includes('--help') || args.includes('-h')) {
213
+ io.stdout(modelCommandUsage(commandName));
214
+ return 0;
215
+ }
216
+ assertNoModelVerifyOptions(args, commandName);
217
+ io.stdout('Model verify started. This usually completes within 30 seconds.');
218
+ deps.loadDotenv();
219
+ io.stdout('');
220
+ const defaultModelConfig = deps.getModelConfig('default');
221
+ const planningModelConfig = deps.getModelConfig('planning');
222
+ const insightModelConfig = deps.getModelConfig('insight');
223
+ const curlCommands = buildModelVerifyCurlCommands([
224
+ {
225
+ intent: 'default',
226
+ modelConfig: defaultModelConfig
227
+ },
228
+ {
229
+ intent: 'planning',
230
+ modelConfig: planningModelConfig
231
+ },
232
+ {
233
+ intent: 'insight',
234
+ modelConfig: insightModelConfig
235
+ }
236
+ ]);
237
+ const baseURLs = new Map();
238
+ const modelRows = [
239
+ [
240
+ 'planning',
241
+ planningModelConfig,
242
+ 'Text response'
243
+ ],
244
+ [
245
+ 'insight',
246
+ insightModelConfig,
247
+ 'Image understanding'
248
+ ],
249
+ [
250
+ 'default',
251
+ defaultModelConfig,
252
+ 'AI locate'
253
+ ]
254
+ ].map(([intent, config, check])=>{
255
+ const baseURL = config.openaiExtraConfig?.baseURL ?? config.openaiBaseURL;
256
+ const endpoint = baseURL ? displayEndpoint(String(baseURL)) : 'provider default';
257
+ if (!baseURLs.has(endpoint)) baseURLs.set(endpoint, baseURLs.size + 1);
258
+ return [
259
+ intent,
260
+ config.modelName,
261
+ check,
262
+ `[${baseURLs.get(endpoint)}]`
263
+ ];
264
+ });
265
+ io.stdout(formatTable([
266
+ 'Model role',
267
+ 'Model',
268
+ 'Check',
269
+ 'Endpoint'
270
+ ], modelRows));
271
+ io.stdout('');
272
+ for (const [endpoint, index] of baseURLs)io.stdout(`[${index}] Base URL: ${endpoint}`);
273
+ io.stdout('\nRunning 3 checks...\n');
274
+ const result = await deps.verifyModel({
275
+ defaultModelConfig,
276
+ planningModelConfig,
277
+ insightModelConfig
278
+ });
279
+ const checkLabels = {
280
+ text: 'Text response',
281
+ vision: 'Image understanding',
282
+ aiLocate: 'AI locate'
283
+ };
284
+ io.stdout(formatTable([
285
+ 'Model role',
286
+ 'Check',
287
+ 'Result',
288
+ 'Requests',
289
+ 'Time'
290
+ ], result.checks.map((check)=>[
291
+ check.intent,
292
+ checkLabels[check.name],
293
+ check.passed ? 'PASS' : 'FAIL',
294
+ String(check.requestCount),
295
+ `${check.durationMs} ms`
296
+ ])));
297
+ const requestEndpoints = new Map();
298
+ for (const check of result.checks){
299
+ for (const endpoint of check.endpoints){
300
+ const display = displayEndpoint(endpoint);
301
+ if (!requestEndpoints.has(display)) requestEndpoints.set(display, new Set());
302
+ requestEndpoints.get(display).add(check.intent);
303
+ }
304
+ if (!check.passed && check.message) io.stdout(`\n${check.intent}: ${check.message}`);
305
+ }
306
+ if (requestEndpoints.size > 0) {
307
+ io.stdout('\nRequest endpoints:');
308
+ for (const [endpoint, intents] of requestEndpoints)io.stdout(` ${[
309
+ ...intents
310
+ ].join(', ')}: ${endpoint}`);
311
+ }
312
+ io.stdout('');
313
+ const passedChecks = result.checks.filter((check)=>check.passed).length;
314
+ io.stdout(`Requests sent: ${result.requestCount}. Checks: ${passedChecks} passed, ${result.checks.length - passedChecks} failed.`);
315
+ if (result.passed) {
316
+ io.stdout('✅ Model verify passed.');
317
+ return 0;
318
+ }
319
+ io.stderr(formatModelVerifyFailureOutput(result.message, curlCommands));
320
+ return 1;
321
+ } catch (error) {
322
+ const message = error instanceof Error ? error.message : String(error);
323
+ io.stderr([
324
+ MODEL_VERIFY_SEPARATOR,
325
+ '❌ Model verify failed with messages:',
326
+ '',
327
+ message,
328
+ MODEL_VERIFY_SEPARATOR
329
+ ].join('\n'));
330
+ return 1;
331
+ }
332
+ }
333
+ async function runModelCommand(rawArgs, deps, io = {
334
+ stdout: console.log,
335
+ stderr: console.error
336
+ }, commandName = 'midscene-test') {
337
+ const [, action, ...restArgs] = rawArgs;
338
+ const mergedDeps = {
339
+ ...getDefaultDeps(io),
340
+ ...deps
341
+ };
342
+ if (!action || '--help' === action || '-h' === action) {
343
+ io.stdout(modelCommandUsage(commandName));
344
+ return 0;
345
+ }
346
+ if ('verify' === action) return runModelVerifyCommand(restArgs, mergedDeps, io, commandName);
347
+ if ('eval' === action) {
348
+ io.stderr(`${commandName} model eval is not implemented yet. It is reserved for future model evaluation suites.`);
349
+ return 1;
350
+ }
351
+ io.stderr(`Unknown ${commandName} model command: ${action}\n\n${modelCommandUsage(commandName)}`);
352
+ return 1;
353
+ }
354
+ })();
355
+ exports.buildModelVerifyCurlCommands = __webpack_exports__.buildModelVerifyCurlCommands;
356
+ exports.loadDotenvConfig = __webpack_exports__.loadDotenvConfig;
357
+ exports.runModelCommand = __webpack_exports__.runModelCommand;
358
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
359
+ "buildModelVerifyCurlCommands",
360
+ "loadDotenvConfig",
361
+ "runModelCommand"
362
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
363
+ Object.defineProperty(exports, '__esModule', {
364
+ value: true
365
+ });
366
+
367
+ //# sourceMappingURL=model-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli/model-command.js","sources":["webpack/runtime/compat_get_default_export","webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../../src/cli/dotenv-loader.ts","../../../src/cli/model-command.ts"],"sourcesContent":["// getDefaultExport function for compatibility with non-ESM modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};\n","__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport dotenv from 'dotenv';\n\nexport interface DotenvLoadOptions {\n dotenvOverride?: boolean;\n dotenvDebug?: boolean;\n cwd?: string;\n log?: (message: string) => void;\n}\n\nexport function loadDotenvConfig(options: DotenvLoadOptions = {}) {\n const dotEnvConfigFile = join(options.cwd ?? process.cwd(), '.env');\n if (!existsSync(dotEnvConfigFile)) {\n return;\n }\n\n options.log?.(` Env file: ${dotEnvConfigFile}`);\n dotenv.config({\n path: dotEnvConfigFile,\n debug: options.dotenvDebug,\n override: options.dotenvOverride,\n });\n}\n","import { runConnectivityTest } from '@midscene/core';\nimport {\n type IModelConfig,\n type TIntent,\n globalModelConfigManager,\n} from '@midscene/shared/env';\nimport chalk from 'chalk';\nimport { loadDotenvConfig } from './dotenv-loader';\n\nconst DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1';\nconst MODEL_VERIFY_SEPARATOR = '────────────────────────────────────────';\nconst modelCommandUsage = (commandName: string) => `Usage:\n ${commandName} model verify\n\nVerify model connectivity and Midscene compatibility for default, planning, and insight models.\nReads .env from the current working directory, overriding shell environment variables.\nShows endpoints, model names, request counts, and per-check results with duration.\nReturns exit code 0 on success and 1 on failure.\n\nOptions:\n --help, -h Show this help\n`;\n\ninterface ModelCommandIO {\n stdout: (message: string) => void;\n stderr: (message: string) => void;\n}\n\ninterface ModelCommandDeps {\n loadDotenv: () => void;\n getModelConfig: (intent: TIntent) => IModelConfig;\n verifyModel: typeof runConnectivityTest;\n}\n\nexport interface CurlCommandItem {\n intents: TIntent[];\n curl: string;\n usesDefaultBaseURL: boolean;\n}\n\nfunction assertNoModelVerifyOptions(args: string[], commandName: string) {\n for (const arg of args) {\n throw new Error(`Unknown option for ${commandName} model verify: ${arg}`);\n }\n}\n\nfunction formatTable(headers: string[], rows: string[][]): string {\n const widths = headers.map((header, index) =>\n Math.max(header.length, ...rows.map((row) => row[index].length)),\n );\n const formatRow = (row: string[]) =>\n row\n .map((cell, index) => cell.padEnd(widths[index]))\n .join(' ')\n .trimEnd();\n return [formatRow(headers), ...rows.map(formatRow)].join('\\n');\n}\n\nfunction displayEndpoint(endpoint: string): string {\n try {\n const url = new URL(endpoint);\n url.username = '';\n url.password = '';\n url.search = '';\n url.hash = '';\n return url.toString();\n } catch {\n return '(invalid endpoint URL)';\n }\n}\n\nfunction shellSingleQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction buildChatCompletionsUrl(baseURL?: string): string {\n const normalizedBaseURL = (baseURL || DEFAULT_OPENAI_BASE_URL).replace(\n /\\/+$/,\n '',\n );\n if (normalizedBaseURL.endsWith('/chat/completions')) {\n return normalizedBaseURL;\n }\n return `${normalizedBaseURL}/chat/completions`;\n}\n\nfunction buildCurlCommand(modelConfig: IModelConfig): string {\n const payload = {\n model: modelConfig.modelName,\n messages: [{ role: 'user', content: 'What is 1+1?' }],\n };\n\n return [\n `curl -X POST ${shellSingleQuote(buildChatCompletionsUrl(modelConfig.openaiBaseURL))} \\\\`,\n ` -H ${shellSingleQuote(`Authorization: Bearer ${modelConfig.openaiApiKey || ''}`)} \\\\`,\n ` -H ${shellSingleQuote('Content-Type: application/json')} \\\\`,\n ` -d ${shellSingleQuote(JSON.stringify(payload, null, 2))}`,\n ].join('\\n');\n}\n\nfunction buildCurlDedupKey(modelConfig: IModelConfig): string {\n return JSON.stringify({\n baseURL: modelConfig.openaiBaseURL || DEFAULT_OPENAI_BASE_URL,\n apiKey: modelConfig.openaiApiKey || '',\n modelName: modelConfig.modelName,\n });\n}\n\nexport function buildModelVerifyCurlCommands(\n configs: Array<{ intent: TIntent; modelConfig: IModelConfig }>,\n): CurlCommandItem[] {\n const commandMap = new Map<string, CurlCommandItem>();\n\n for (const item of configs) {\n const key = buildCurlDedupKey(item.modelConfig);\n const existing = commandMap.get(key);\n if (existing) {\n existing.intents.push(item.intent);\n continue;\n }\n commandMap.set(key, {\n intents: [item.intent],\n curl: buildCurlCommand(item.modelConfig),\n usesDefaultBaseURL: !item.modelConfig.openaiBaseURL,\n });\n }\n\n return [...commandMap.values()];\n}\n\nfunction formatModelVerifyFailureOutput(\n message: string | undefined,\n curlCommands: CurlCommandItem[],\n): string {\n const details = message?.trim() || 'No failure details were generated.';\n const curlSection = curlCommands\n .map((item) => {\n const baseUrlNote = item.usesDefaultBaseURL\n ? ' (base URL not configured; using OpenAI SDK default)'\n : '';\n return chalk.gray(\n `# ${item.intents.join(', ')}${baseUrlNote}\\n${item.curl}`,\n );\n })\n .join('\\n\\n');\n\n return [\n chalk.red.bold('❌ Model verify failed with messages:'),\n MODEL_VERIFY_SEPARATOR,\n '',\n details,\n '',\n MODEL_VERIFY_SEPARATOR,\n 'Generated curl requests for basic API connectivity:',\n 'If the error is a basic connectivity issue, use these requests to test the base URL, API key, and model name directly.',\n 'These commands contain your API key. Do not share them publicly.',\n '',\n curlSection,\n ].join('\\n');\n}\n\nfunction getDefaultDeps(io: ModelCommandIO): ModelCommandDeps {\n return {\n loadDotenv: () => {\n loadDotenvConfig({\n dotenvDebug: true,\n dotenvOverride: true,\n log: io.stdout,\n });\n },\n getModelConfig: (intent) => globalModelConfigManager.getModelConfig(intent),\n verifyModel: runConnectivityTest,\n };\n}\n\nasync function runModelVerifyCommand(\n args: string[],\n deps: ModelCommandDeps,\n io: ModelCommandIO,\n commandName: string,\n): Promise<number> {\n try {\n if (args.includes('--help') || args.includes('-h')) {\n io.stdout(modelCommandUsage(commandName));\n return 0;\n }\n\n assertNoModelVerifyOptions(args, commandName);\n io.stdout(\n 'Model verify started. This usually completes within 30 seconds.',\n );\n deps.loadDotenv();\n io.stdout('');\n\n const defaultModelConfig = deps.getModelConfig('default');\n const planningModelConfig = deps.getModelConfig('planning');\n const insightModelConfig = deps.getModelConfig('insight');\n const curlCommands = buildModelVerifyCurlCommands([\n { intent: 'default', modelConfig: defaultModelConfig },\n { intent: 'planning', modelConfig: planningModelConfig },\n { intent: 'insight', modelConfig: insightModelConfig },\n ]);\n\n const baseURLs = new Map<string, number>();\n const modelRows = (\n [\n ['planning', planningModelConfig, 'Text response'],\n ['insight', insightModelConfig, 'Image understanding'],\n ['default', defaultModelConfig, 'AI locate'],\n ] as const\n ).map(([intent, config, check]) => {\n const baseURL = config.openaiExtraConfig?.baseURL ?? config.openaiBaseURL;\n const endpoint = baseURL\n ? displayEndpoint(String(baseURL))\n : 'provider default';\n if (!baseURLs.has(endpoint)) baseURLs.set(endpoint, baseURLs.size + 1);\n return [intent, config.modelName, check, `[${baseURLs.get(endpoint)}]`];\n });\n io.stdout(\n formatTable(['Model role', 'Model', 'Check', 'Endpoint'], modelRows),\n );\n io.stdout('');\n for (const [endpoint, index] of baseURLs) {\n io.stdout(`[${index}] Base URL: ${endpoint}`);\n }\n io.stdout('\\nRunning 3 checks...\\n');\n\n const result = await deps.verifyModel({\n defaultModelConfig,\n planningModelConfig,\n insightModelConfig,\n });\n\n const checkLabels = {\n text: 'Text response',\n vision: 'Image understanding',\n aiLocate: 'AI locate',\n };\n io.stdout(\n formatTable(\n ['Model role', 'Check', 'Result', 'Requests', 'Time'],\n result.checks.map((check) => [\n check.intent,\n checkLabels[check.name],\n check.passed ? 'PASS' : 'FAIL',\n String(check.requestCount),\n `${check.durationMs} ms`,\n ]),\n ),\n );\n const requestEndpoints = new Map<string, Set<string>>();\n for (const check of result.checks) {\n for (const endpoint of check.endpoints) {\n const display = displayEndpoint(endpoint);\n if (!requestEndpoints.has(display))\n requestEndpoints.set(display, new Set());\n requestEndpoints.get(display)!.add(check.intent);\n }\n if (!check.passed && check.message)\n io.stdout(`\\n${check.intent}: ${check.message}`);\n }\n if (requestEndpoints.size > 0) {\n io.stdout('\\nRequest endpoints:');\n for (const [endpoint, intents] of requestEndpoints) {\n io.stdout(` ${[...intents].join(', ')}: ${endpoint}`);\n }\n }\n io.stdout('');\n const passedChecks = result.checks.filter((check) => check.passed).length;\n io.stdout(\n `Requests sent: ${result.requestCount}. Checks: ${passedChecks} passed, ${result.checks.length - passedChecks} failed.`,\n );\n\n if (result.passed) {\n io.stdout('✅ Model verify passed.');\n return 0;\n }\n\n io.stderr(formatModelVerifyFailureOutput(result.message, curlCommands));\n return 1;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n io.stderr(\n [\n MODEL_VERIFY_SEPARATOR,\n '❌ Model verify failed with messages:',\n '',\n message,\n MODEL_VERIFY_SEPARATOR,\n ].join('\\n'),\n );\n return 1;\n }\n}\n\nexport async function runModelCommand(\n rawArgs: string[],\n deps?: Partial<ModelCommandDeps>,\n io: ModelCommandIO = {\n stdout: console.log,\n stderr: console.error,\n },\n commandName = 'midscene-test',\n): Promise<number> {\n const [, action, ...restArgs] = rawArgs;\n const mergedDeps = {\n ...getDefaultDeps(io),\n ...deps,\n };\n\n if (!action || action === '--help' || action === '-h') {\n io.stdout(modelCommandUsage(commandName));\n return 0;\n }\n\n if (action === 'verify') {\n return runModelVerifyCommand(restArgs, mergedDeps, io, commandName);\n }\n\n if (action === 'eval') {\n io.stderr(\n `${commandName} model eval is not implemented yet. It is reserved for future model evaluation suites.`,\n );\n return 1;\n }\n\n io.stderr(\n `Unknown ${commandName} model command: ${action}\\n\\n${modelCommandUsage(commandName)}`,\n );\n return 1;\n}\n\nexport { loadDotenvConfig, type DotenvLoadOptions } from './dotenv-loader';\n"],"names":["__webpack_require__","module","getter","definition","key","Object","obj","prop","Symbol","loadDotenvConfig","options","dotEnvConfigFile","join","process","existsSync","dotenv","DEFAULT_OPENAI_BASE_URL","MODEL_VERIFY_SEPARATOR","modelCommandUsage","commandName","assertNoModelVerifyOptions","args","arg","Error","formatTable","headers","rows","widths","header","index","Math","row","formatRow","cell","displayEndpoint","endpoint","url","URL","shellSingleQuote","value","buildChatCompletionsUrl","baseURL","normalizedBaseURL","buildCurlCommand","modelConfig","payload","JSON","buildCurlDedupKey","buildModelVerifyCurlCommands","configs","commandMap","Map","item","existing","formatModelVerifyFailureOutput","message","curlCommands","details","curlSection","baseUrlNote","chalk","getDefaultDeps","io","intent","globalModelConfigManager","runConnectivityTest","runModelVerifyCommand","deps","defaultModelConfig","planningModelConfig","insightModelConfig","baseURLs","modelRows","config","check","String","result","checkLabels","requestEndpoints","display","Set","intents","passedChecks","error","runModelCommand","rawArgs","console","action","restArgs","mergedDeps"],"mappings":"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"}
@@ -3,6 +3,18 @@ var __webpack_modules__ = {
3
3
  "@inquirer/prompts" (module) {
4
4
  module.exports = require("@inquirer/prompts");
5
5
  },
6
+ "@midscene/core" (module) {
7
+ module.exports = require("@midscene/core");
8
+ },
9
+ "@midscene/shared/env" (module) {
10
+ module.exports = require("@midscene/shared/env");
11
+ },
12
+ chalk (module) {
13
+ module.exports = require("chalk");
14
+ },
15
+ dotenv (module) {
16
+ module.exports = require("dotenv");
17
+ },
6
18
  execa (module) {
7
19
  module.exports = require("execa");
8
20
  },
@@ -17,7 +29,7 @@ var __webpack_modules__ = {
17
29
  },
18
30
  "./package.json" (module) {
19
31
  module.exports = {
20
- rE: "1.14.0"
32
+ rE: "1.15.0"
21
33
  };
22
34
  }
23
35
  };
@@ -80,7 +92,14 @@ var __webpack_exports__ = {};
80
92
  (()=>{
81
93
  var external_node_fs_ = __webpack_require__("node:fs");
82
94
  var external_node_path_ = __webpack_require__("node:path");
95
+ const test_runner_namespaceObject = require("@midscene/core/internal/test-runner");
83
96
  var package_0 = __webpack_require__("./package.json");
97
+ const external_node_util_namespaceObject = require("node:util");
98
+ const formatCliError = (error)=>'string' == typeof error ? error : (0, external_node_util_namespaceObject.inspect)(error, {
99
+ colors: false,
100
+ depth: null,
101
+ customInspect: false
102
+ });
84
103
  const v4_namespaceObject = require("zod/v4");
85
104
  const GENERATED_COMMENT = '<!-- Generated by `midscene-test nodes`. Do not edit directly. -->';
86
105
  const sortNodesForSpec = (nodes)=>{
@@ -217,7 +236,6 @@ Each Node's returned \`data\` is saved on that Step's result. Results are not na
217
236
  const external_node_url_namespaceObject = require("node:url");
218
237
  const api_namespaceObject = require("tsx/cjs/api");
219
238
  const esm_api_namespaceObject = require("tsx/esm/api");
220
- const test_runner_namespaceObject = require("@midscene/core/internal/test-runner");
221
239
  const NodeRegistry = test_runner_namespaceObject.NodeRegistry;
222
240
  const projectIdFromIndex = (index)=>`project-${index}`;
223
241
  const getDefaultExport = (loaded, absolutePath)=>{
@@ -1576,6 +1594,7 @@ Usage:
1576
1594
  midscene-test --config <midscene.config.ts> [options]
1577
1595
  midscene-test nodes [directory] [--config midscene.config.ts]
1578
1596
  midscene-test create --help
1597
+ midscene-test model verify Check model connectivity and Midscene compatibility
1579
1598
 
1580
1599
  Options:
1581
1600
  --config <path> Native TypeScript or JavaScript Test config
@@ -1641,8 +1660,78 @@ Legacy tasks/flow YAML and its CLI options belong to the midscene command.
1641
1660
  io.log(`\nNode Spec generated: ${specPath}`);
1642
1661
  }
1643
1662
  };
1663
+ const reportTestProjectResult = (result, io, infrastructureErrors = result.errors ?? [])=>{
1664
+ const reportedErrors = new Set();
1665
+ const reportError = (source, error)=>{
1666
+ if (error instanceof Error && reportedErrors.has(error)) return;
1667
+ io.error(`midscene-test: ${source}: ${formatCliError(error)}`);
1668
+ const pending = [
1669
+ error
1670
+ ];
1671
+ while(pending.length > 0){
1672
+ const current = pending.pop();
1673
+ if (!(current instanceof Error) || reportedErrors.has(current)) continue;
1674
+ reportedErrors.add(current);
1675
+ pending.push(current.cause);
1676
+ if (current instanceof AggregateError) pending.push(...current.errors);
1677
+ }
1678
+ };
1679
+ for (const failure of result.collectionErrors)reportError(`${failure.projectName}/${failure.sourcePath}`, failure.error);
1680
+ const finalCases = new Map(result.cases.map((outcome)=>[
1681
+ outcome.caseId,
1682
+ outcome
1683
+ ]));
1684
+ for (const outcome of finalCases.values()){
1685
+ if ('failed' !== outcome.status || !outcome.run) continue;
1686
+ const run = outcome.run;
1687
+ const source = `${run.projectName}/${run.sourcePath} / ${run.name}`;
1688
+ for (const step of [
1689
+ ...run.beforeEach,
1690
+ ...run.steps,
1691
+ ...run.afterEach
1692
+ ])if (step.error) reportError(`${source} / ${step.phase}[${step.stepIndex + 1}] ${step.node}`, step.error);
1693
+ for (const error of [
1694
+ ...run.executionErrors ?? [],
1695
+ ...run.teardownErrors ?? []
1696
+ ])reportError(source, error);
1697
+ }
1698
+ const finalDocuments = new Map(result.documents.map((document)=>[
1699
+ document.documentId,
1700
+ document
1701
+ ]));
1702
+ for (const document of finalDocuments.values()){
1703
+ if ('failed' !== document.status) continue;
1704
+ const source = `${document.projectName}/${document.sourcePath}`;
1705
+ for (const step of [
1706
+ ...document.beforeAll,
1707
+ ...document.afterAll
1708
+ ])if (step.error) reportError(`${source} / ${step.phase}[${step.stepIndex + 1}] ${step.node}`, step.error);
1709
+ for (const error of [
1710
+ ...document.executionErrors ?? [],
1711
+ ...document.teardownErrors ?? []
1712
+ ])reportError(source, error);
1713
+ for (const failure of document.hostErrors ?? [])reportError(`${source} / host ${failure.phase}`, failure.error);
1714
+ }
1715
+ for (const project of result.projects){
1716
+ if (project.lifecycle?.setupError) reportError(`${project.name} / setup`, project.lifecycle.setupError);
1717
+ for (const error of project.lifecycle?.teardownErrors ?? [])reportError(`${project.name} / teardown`, error);
1718
+ }
1719
+ for (const error of infrastructureErrors)reportError('infrastructure', error);
1720
+ io.log(`midscene-test: ${result.summary.passed}/${result.summary.total} cases passed, ${result.summary.failed} failed, ${result.summary.notRun} not run`);
1721
+ io.log(`Results: ${result.resultDir}`);
1722
+ io.log(`Summary: ${result.summaryPath}`);
1723
+ if (result.reportPath) io.log(`Report: ${result.reportPath}`);
1724
+ return result.exitCode;
1725
+ };
1644
1726
  async function runTestCli(args, io = defaultCliIO) {
1645
1727
  try {
1728
+ if ('model' === args[0]) {
1729
+ const { runModelCommand } = await __webpack_require__.e("703").then(__webpack_require__.bind(__webpack_require__, "./src/cli/model-command.ts"));
1730
+ return runModelCommand(args, void 0, {
1731
+ stdout: io.log,
1732
+ stderr: io.error
1733
+ });
1734
+ }
1646
1735
  if ('create' === args[0]) {
1647
1736
  const { runCreateCommand } = await __webpack_require__.e("160").then(__webpack_require__.bind(__webpack_require__, "./src/cli/create-command.ts"));
1648
1737
  await runCreateCommand(args.slice(1), io);
@@ -1661,36 +1750,20 @@ Legacy tasks/flow YAML and its CLI options belong to the midscene command.
1661
1750
  await runNodesCommand(options, io);
1662
1751
  return 0;
1663
1752
  }
1664
- const result = await runTestProject({
1665
- ...options,
1666
- onProgress: (message)=>io.log(message)
1667
- });
1668
- for (const failure of result.collectionErrors)io.error(`midscene-test: ${failure.projectName}/${failure.sourcePath}: ${failure.error.message}`);
1669
- const finalCases = new Map(result.cases.map((outcome)=>[
1670
- outcome.caseId,
1671
- outcome
1672
- ]));
1673
- for (const outcome of finalCases.values()){
1674
- if ('failed' !== outcome.status || !outcome.run) continue;
1675
- const run = outcome.run;
1676
- const source = `${run.projectName}/${run.sourcePath} / ${run.name}`;
1677
- for (const step of [
1678
- ...run.beforeEach,
1679
- ...run.steps,
1680
- ...run.afterEach
1681
- ])if (step.error) io.error(`midscene-test: ${source} / ${step.phase}[${step.stepIndex + 1}] ${step.node}: ${step.error.message}`);
1682
- for (const error of [
1683
- ...run.executionErrors ?? [],
1684
- ...run.teardownErrors ?? []
1685
- ])io.error(`midscene-test: ${source}: ${error.message}`);
1753
+ let result;
1754
+ try {
1755
+ result = await runTestProject({
1756
+ ...options,
1757
+ onProgress: (message)=>io.log(message)
1758
+ });
1759
+ } catch (error) {
1760
+ if (!(error instanceof test_runner_namespaceObject.WorkflowExecutionFailure)) throw error;
1761
+ reportTestProjectResult(error.result, io, error.errors);
1762
+ return 1;
1686
1763
  }
1687
- io.log(`midscene-test: ${result.summary.passed}/${result.summary.total} cases passed, ${result.summary.failed} failed, ${result.summary.notRun} not run`);
1688
- io.log(`Results: ${result.resultDir}`);
1689
- io.log(`Summary: ${result.summaryPath}`);
1690
- if (result.reportPath) io.log(`Report: ${result.reportPath}`);
1691
- return result.exitCode;
1764
+ return reportTestProjectResult(result, io);
1692
1765
  } catch (error) {
1693
- io.error(error instanceof Error ? error.message : String(error));
1766
+ io.error(formatCliError(error));
1694
1767
  return 1;
1695
1768
  }
1696
1769
  }