@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
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import { parseBase64 } from "@aiscene/shared/img";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { attachCliVerboseDumpListener, emitCliVerboseEvent } from "../cli/verbose.mjs";
|
|
4
|
+
import { getZodDescription, getZodTypeName, isMidsceneLocatorField, unwrapZodField } from "../zod-schema-utils.mjs";
|
|
5
|
+
import { getErrorMessage } from "./error-formatter.mjs";
|
|
6
|
+
import { resolveObservationArtifactAdapter } from "./observation-artifact.mjs";
|
|
7
|
+
import { readUIObservationRecord } from "./observation-record.mjs";
|
|
8
|
+
import { composeUserPrompt, promptInputExtraSchema } from "./user-prompt.mjs";
|
|
9
|
+
function describeActionForTool(action) {
|
|
10
|
+
const actionDesc = action.description || `Execute ${action.name} action`;
|
|
11
|
+
if (!action.paramSchema) return `${action.name} action, ${actionDesc}`;
|
|
12
|
+
const shape = getZodObjectShape(action.paramSchema);
|
|
13
|
+
if (!shape) {
|
|
14
|
+
const typeName = getZodTypeName(action.paramSchema);
|
|
15
|
+
const description = getZodDescription(action.paramSchema);
|
|
16
|
+
const paramDesc = description ? `${typeName} - ${description}` : typeName;
|
|
17
|
+
return `${action.name} action, ${actionDesc}. Parameter: ${paramDesc}`;
|
|
18
|
+
}
|
|
19
|
+
const paramDescriptions = [];
|
|
20
|
+
for (const [key, field] of Object.entries(shape))if (field && 'object' == typeof field) {
|
|
21
|
+
const isFieldOptional = 'function' == typeof field.isOptional && field.isOptional();
|
|
22
|
+
const typeName = getZodTypeName(field);
|
|
23
|
+
const description = getZodDescription(field);
|
|
24
|
+
let paramStr = `${key}${isFieldOptional ? '?' : ''} (${typeName})`;
|
|
25
|
+
if (description) paramStr += ` - ${description}`;
|
|
26
|
+
paramDescriptions.push(paramStr);
|
|
27
|
+
}
|
|
28
|
+
if (0 === paramDescriptions.length) return `${action.name} action, ${actionDesc}`;
|
|
29
|
+
return `${action.name} action, ${actionDesc}. Parameters: ${paramDescriptions.join('; ')}`;
|
|
30
|
+
}
|
|
31
|
+
function isZodOptional(value) {
|
|
32
|
+
return '_def' in value && value._def?.typeName === 'ZodOptional';
|
|
33
|
+
}
|
|
34
|
+
function unwrapOptional(value) {
|
|
35
|
+
if (isZodOptional(value)) return {
|
|
36
|
+
innerValue: value._def.innerType,
|
|
37
|
+
isOptional: true
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
innerValue: value,
|
|
41
|
+
isOptional: false
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function getZodObjectShape(value) {
|
|
45
|
+
if (!value) return;
|
|
46
|
+
const actualValue = unwrapZodField(value);
|
|
47
|
+
if (actualValue._def?.typeName !== 'ZodObject') return;
|
|
48
|
+
if ('function' == typeof actualValue._def.shape) return actualValue._def.shape();
|
|
49
|
+
return actualValue.shape;
|
|
50
|
+
}
|
|
51
|
+
function isRecord(value) {
|
|
52
|
+
return 'object' == typeof value && null !== value && !Array.isArray(value);
|
|
53
|
+
}
|
|
54
|
+
function makePromptOptional(shape, wrapInOptional, description) {
|
|
55
|
+
const newShape = {
|
|
56
|
+
...shape
|
|
57
|
+
};
|
|
58
|
+
newShape.prompt = shape.prompt.optional();
|
|
59
|
+
let newSchema = z.object(newShape).passthrough();
|
|
60
|
+
if (wrapInOptional) newSchema = newSchema.optional();
|
|
61
|
+
if (description) newSchema = newSchema.describe(description);
|
|
62
|
+
return newSchema;
|
|
63
|
+
}
|
|
64
|
+
function transformSchemaField(key, value) {
|
|
65
|
+
const { innerValue, isOptional } = unwrapOptional(value);
|
|
66
|
+
const shape = getZodObjectShape(innerValue);
|
|
67
|
+
if (shape && isMidsceneLocatorField(innerValue)) return [
|
|
68
|
+
key,
|
|
69
|
+
makePromptOptional(shape, isOptional, getZodDescription(value))
|
|
70
|
+
];
|
|
71
|
+
return [
|
|
72
|
+
key,
|
|
73
|
+
value
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
function extractActionSchema(paramSchema, actionName) {
|
|
77
|
+
if (!paramSchema) return {};
|
|
78
|
+
const shape = getZodObjectShape(paramSchema);
|
|
79
|
+
if (!shape) {
|
|
80
|
+
const typeName = paramSchema?._def?.typeName ?? 'unknown';
|
|
81
|
+
throw new Error(`Action "${actionName}" declared a non-object paramSchema (${typeName}). CLI tool schemas must be a ZodObject (e.g. z.object({ uri: z.string() })) or undefined. Wrap primitive fields in an object schema.`);
|
|
82
|
+
}
|
|
83
|
+
return Object.fromEntries(Object.entries(shape).map(([key, value])=>transformSchemaField(key, value)));
|
|
84
|
+
}
|
|
85
|
+
function getPromptText(prompt) {
|
|
86
|
+
if ('string' == typeof prompt) return prompt;
|
|
87
|
+
if (isRecord(prompt) && 'string' == typeof prompt.prompt) return prompt.prompt;
|
|
88
|
+
}
|
|
89
|
+
function moveLocateExtrasIntoPrompt(value, locateFieldKeys) {
|
|
90
|
+
const promptText = getPromptText(value.prompt);
|
|
91
|
+
if (!promptText) return value;
|
|
92
|
+
const normalizedPrompt = isRecord(value.prompt) ? {
|
|
93
|
+
...value.prompt
|
|
94
|
+
} : {
|
|
95
|
+
prompt: promptText
|
|
96
|
+
};
|
|
97
|
+
const normalizedLocate = {};
|
|
98
|
+
let movedExtraField = false;
|
|
99
|
+
for (const [key, fieldValue] of Object.entries(value))if ('prompt' !== key) {
|
|
100
|
+
if (locateFieldKeys.has(key)) {
|
|
101
|
+
normalizedLocate[key] = fieldValue;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
movedExtraField = true;
|
|
105
|
+
if (!(key in normalizedPrompt)) normalizedPrompt[key] = fieldValue;
|
|
106
|
+
}
|
|
107
|
+
if (!movedExtraField) return value;
|
|
108
|
+
return {
|
|
109
|
+
...normalizedLocate,
|
|
110
|
+
prompt: normalizedPrompt
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function normalizeLocateLikeArg(value, fieldSchema) {
|
|
114
|
+
if ('string' == typeof value) return {
|
|
115
|
+
prompt: value
|
|
116
|
+
};
|
|
117
|
+
if (!isRecord(value)) return value;
|
|
118
|
+
const shape = getZodObjectShape(fieldSchema);
|
|
119
|
+
if (!shape) return value;
|
|
120
|
+
return moveLocateExtrasIntoPrompt(value, new Set(Object.keys(shape)));
|
|
121
|
+
}
|
|
122
|
+
function normalizeActionArgs(args, paramSchema) {
|
|
123
|
+
if (!paramSchema) return args;
|
|
124
|
+
const shape = getZodObjectShape(paramSchema);
|
|
125
|
+
if (!shape) return args;
|
|
126
|
+
return Object.fromEntries(Object.entries(args).map(([key, value])=>{
|
|
127
|
+
const fieldSchema = shape[key];
|
|
128
|
+
if (!fieldSchema) return [
|
|
129
|
+
key,
|
|
130
|
+
value
|
|
131
|
+
];
|
|
132
|
+
if (isMidsceneLocatorField(fieldSchema)) return [
|
|
133
|
+
key,
|
|
134
|
+
normalizeLocateLikeArg(value, fieldSchema)
|
|
135
|
+
];
|
|
136
|
+
return [
|
|
137
|
+
key,
|
|
138
|
+
value
|
|
139
|
+
];
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
function mergeLocateDefaults(locate, defaults) {
|
|
143
|
+
let merged;
|
|
144
|
+
for (const [key, value] of Object.entries(defaults))if (void 0 === locate[key]) {
|
|
145
|
+
if ('deepLocate' !== key || void 0 === locate.deepThink) {
|
|
146
|
+
merged = merged ?? {
|
|
147
|
+
...locate
|
|
148
|
+
};
|
|
149
|
+
merged[key] = value;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return merged ?? locate;
|
|
153
|
+
}
|
|
154
|
+
function applyLocateDefaults(args, paramSchema, locateDefaults) {
|
|
155
|
+
if (!paramSchema || 0 === Object.keys(locateDefaults).length) return args;
|
|
156
|
+
const shape = getZodObjectShape(paramSchema);
|
|
157
|
+
if (!shape) return args;
|
|
158
|
+
return Object.fromEntries(Object.entries(args).map(([key, value])=>{
|
|
159
|
+
const fieldSchema = shape[key];
|
|
160
|
+
if (fieldSchema && isMidsceneLocatorField(fieldSchema) && isRecord(value)) return [
|
|
161
|
+
key,
|
|
162
|
+
mergeLocateDefaults(value, locateDefaults)
|
|
163
|
+
];
|
|
164
|
+
return [
|
|
165
|
+
key,
|
|
166
|
+
value
|
|
167
|
+
];
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
function serializeArgsToDescription(args) {
|
|
171
|
+
try {
|
|
172
|
+
return Object.entries(args).map(([key, value])=>{
|
|
173
|
+
if ('object' == typeof value && null !== value) try {
|
|
174
|
+
return `${key}: ${JSON.stringify(value)}`;
|
|
175
|
+
} catch {
|
|
176
|
+
return `${key}: [object]`;
|
|
177
|
+
}
|
|
178
|
+
return `${key}: "${value}"`;
|
|
179
|
+
}).join(', ');
|
|
180
|
+
} catch (error) {
|
|
181
|
+
const errorMessage = getErrorMessage(error);
|
|
182
|
+
console.error('Error serializing args:', errorMessage);
|
|
183
|
+
return `[args serialization failed: ${errorMessage}]`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function buildActionInstruction(actionName, args) {
|
|
187
|
+
const locatePrompt = isRecord(args.locate) ? getPromptText(args.locate.prompt) : void 0;
|
|
188
|
+
switch(actionName){
|
|
189
|
+
case 'Tap':
|
|
190
|
+
return locatePrompt ? `Tap on "${locatePrompt}"` : 'Tap';
|
|
191
|
+
case 'Input':
|
|
192
|
+
{
|
|
193
|
+
const value = args.value ?? args.content ?? '';
|
|
194
|
+
return locatePrompt ? `Input "${value}" into "${locatePrompt}"` : `Input "${value}"`;
|
|
195
|
+
}
|
|
196
|
+
case 'Scroll':
|
|
197
|
+
{
|
|
198
|
+
const direction = args.direction ?? 'down';
|
|
199
|
+
return locatePrompt ? `Scroll ${direction} on "${locatePrompt}"` : `Scroll ${direction}`;
|
|
200
|
+
}
|
|
201
|
+
case 'Hover':
|
|
202
|
+
return locatePrompt ? `Hover over "${locatePrompt}"` : 'Hover';
|
|
203
|
+
case 'KeyboardPress':
|
|
204
|
+
{
|
|
205
|
+
const key = args.value ?? args.key ?? '';
|
|
206
|
+
return `Press key "${key}"`;
|
|
207
|
+
}
|
|
208
|
+
default:
|
|
209
|
+
{
|
|
210
|
+
const argsDescription = serializeArgsToDescription(args);
|
|
211
|
+
return argsDescription ? `${actionName}: ${argsDescription}` : actionName;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async function executeAction(agent, actionName, args) {
|
|
216
|
+
if (agent.callActionInActionSpace) return agent.callActionInActionSpace(actionName, args);
|
|
217
|
+
if (agent.aiAction) {
|
|
218
|
+
const instruction = buildActionInstruction(actionName, args);
|
|
219
|
+
return agent.aiAction(instruction);
|
|
220
|
+
}
|
|
221
|
+
throw new Error(`Action "${actionName}" is not supported by this agent`);
|
|
222
|
+
}
|
|
223
|
+
async function captureScreenshotResult(agent, actionName, actionResult) {
|
|
224
|
+
const content = [
|
|
225
|
+
{
|
|
226
|
+
type: 'text',
|
|
227
|
+
text: `Action "${actionName}" completed.`
|
|
228
|
+
}
|
|
229
|
+
];
|
|
230
|
+
if (void 0 !== actionResult) content.push({
|
|
231
|
+
type: 'text',
|
|
232
|
+
text: `Result: ${serializeActionResult(actionResult)}`
|
|
233
|
+
});
|
|
234
|
+
try {
|
|
235
|
+
const screenshot = await agent.page?.screenshotBase64();
|
|
236
|
+
if (!screenshot) return {
|
|
237
|
+
content
|
|
238
|
+
};
|
|
239
|
+
const { mimeType, body } = parseBase64(screenshot);
|
|
240
|
+
content.push({
|
|
241
|
+
type: 'image',
|
|
242
|
+
data: body,
|
|
243
|
+
mimeType
|
|
244
|
+
});
|
|
245
|
+
return {
|
|
246
|
+
content
|
|
247
|
+
};
|
|
248
|
+
} catch (error) {
|
|
249
|
+
const errorMessage = getErrorMessage(error);
|
|
250
|
+
console.error('Error capturing screenshot:', errorMessage);
|
|
251
|
+
content[0] = {
|
|
252
|
+
type: 'text',
|
|
253
|
+
text: `Action "${actionName}" completed (screenshot unavailable: ${errorMessage})`
|
|
254
|
+
};
|
|
255
|
+
return {
|
|
256
|
+
content
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function serializeActionResult(actionResult) {
|
|
261
|
+
if ('string' == typeof actionResult) return actionResult;
|
|
262
|
+
try {
|
|
263
|
+
return JSON.stringify(actionResult);
|
|
264
|
+
} catch {
|
|
265
|
+
return String(actionResult);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function createErrorResult(message) {
|
|
269
|
+
return {
|
|
270
|
+
content: [
|
|
271
|
+
{
|
|
272
|
+
type: 'text',
|
|
273
|
+
text: message
|
|
274
|
+
}
|
|
275
|
+
],
|
|
276
|
+
isError: true
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
async function captureFailureResult(agent, actionName, errorMessage) {
|
|
280
|
+
const warningText = `Warning: Action "${actionName}" failed: ${errorMessage}. Check the screenshot below for the current page state and decide how to proceed.`;
|
|
281
|
+
try {
|
|
282
|
+
const screenshot = await agent.page?.screenshotBase64();
|
|
283
|
+
if (!screenshot) return {
|
|
284
|
+
content: [
|
|
285
|
+
{
|
|
286
|
+
type: 'text',
|
|
287
|
+
text: warningText
|
|
288
|
+
}
|
|
289
|
+
]
|
|
290
|
+
};
|
|
291
|
+
const { mimeType, body } = parseBase64(screenshot);
|
|
292
|
+
return {
|
|
293
|
+
content: [
|
|
294
|
+
{
|
|
295
|
+
type: 'text',
|
|
296
|
+
text: warningText
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
type: 'image',
|
|
300
|
+
data: body,
|
|
301
|
+
mimeType
|
|
302
|
+
}
|
|
303
|
+
]
|
|
304
|
+
};
|
|
305
|
+
} catch {
|
|
306
|
+
return {
|
|
307
|
+
content: [
|
|
308
|
+
{
|
|
309
|
+
type: 'text',
|
|
310
|
+
text: warningText
|
|
311
|
+
}
|
|
312
|
+
]
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function mergeToolCliMetadata(base, extra) {
|
|
317
|
+
const options = {
|
|
318
|
+
...base?.options ?? {},
|
|
319
|
+
...extra?.options ?? {}
|
|
320
|
+
};
|
|
321
|
+
const positionals = base?.positionals ?? extra?.positionals;
|
|
322
|
+
return Object.keys(options).length > 0 || positionals ? {
|
|
323
|
+
options,
|
|
324
|
+
positionals
|
|
325
|
+
} : void 0;
|
|
326
|
+
}
|
|
327
|
+
function generateToolsFromActionSpace(actionSpace, getAgent, sanitizeArgs = (args)=>args, initArgSchema = {}, initArgCliMetadata, toolDefaults = {}) {
|
|
328
|
+
return actionSpace.map((action)=>{
|
|
329
|
+
const schema = {
|
|
330
|
+
...extractActionSchema(action.paramSchema, action.name),
|
|
331
|
+
...initArgSchema
|
|
332
|
+
};
|
|
333
|
+
return {
|
|
334
|
+
name: action.name,
|
|
335
|
+
description: describeActionForTool(action),
|
|
336
|
+
schema,
|
|
337
|
+
cli: initArgCliMetadata,
|
|
338
|
+
handler: async (args)=>{
|
|
339
|
+
try {
|
|
340
|
+
const agent = await getAgent(args);
|
|
341
|
+
emitCliVerboseEvent({
|
|
342
|
+
event: 'agent_ready',
|
|
343
|
+
tool: action.name
|
|
344
|
+
});
|
|
345
|
+
const unsubscribeVerbose = attachCliVerboseDumpListener(agent, {
|
|
346
|
+
toolName: action.name
|
|
347
|
+
});
|
|
348
|
+
try {
|
|
349
|
+
let normalizedArgs = normalizeActionArgs(sanitizeArgs(args), action.paramSchema);
|
|
350
|
+
if (toolDefaults.locate) normalizedArgs = applyLocateDefaults(normalizedArgs, action.paramSchema, toolDefaults.locate);
|
|
351
|
+
let actionResult;
|
|
352
|
+
try {
|
|
353
|
+
actionResult = await executeAction(agent, action.name, normalizedArgs);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
const errorMessage = getErrorMessage(error);
|
|
356
|
+
console.error(`Error executing action "${action.name}":`, errorMessage);
|
|
357
|
+
return await captureFailureResult(agent, action.name, errorMessage);
|
|
358
|
+
}
|
|
359
|
+
return await captureScreenshotResult(agent, action.name, actionResult);
|
|
360
|
+
} finally{
|
|
361
|
+
unsubscribeVerbose();
|
|
362
|
+
}
|
|
363
|
+
} catch (error) {
|
|
364
|
+
const errorMessage = getErrorMessage(error);
|
|
365
|
+
console.error(`Error in handler for "${action.name}":`, errorMessage);
|
|
366
|
+
return createErrorResult(`Failed to get agent or execute action "${action.name}": ${errorMessage}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
function generateCommonTools(getAgent, initArgSchema = {}, initArgCliMetadata, toolDefaults = {}) {
|
|
373
|
+
return [
|
|
374
|
+
{
|
|
375
|
+
name: 'take_screenshot',
|
|
376
|
+
description: 'Capture screenshot of current page/screen',
|
|
377
|
+
schema: {
|
|
378
|
+
...initArgSchema
|
|
379
|
+
},
|
|
380
|
+
cli: initArgCliMetadata,
|
|
381
|
+
handler: async (args = {})=>{
|
|
382
|
+
try {
|
|
383
|
+
const agent = await getAgent(args);
|
|
384
|
+
emitCliVerboseEvent({
|
|
385
|
+
event: 'agent_ready',
|
|
386
|
+
tool: 'take_screenshot'
|
|
387
|
+
});
|
|
388
|
+
const unsubscribeVerbose = attachCliVerboseDumpListener(agent, {
|
|
389
|
+
toolName: 'take_screenshot'
|
|
390
|
+
});
|
|
391
|
+
try {
|
|
392
|
+
const screenshot = await agent.page?.screenshotBase64();
|
|
393
|
+
if (!screenshot) return createErrorResult('Screenshot not available');
|
|
394
|
+
await agent.recordToReport?.('take_screenshot', {
|
|
395
|
+
screenshotBase64: screenshot
|
|
396
|
+
});
|
|
397
|
+
const { mimeType, body } = parseBase64(screenshot);
|
|
398
|
+
return {
|
|
399
|
+
content: [
|
|
400
|
+
{
|
|
401
|
+
type: 'image',
|
|
402
|
+
data: body,
|
|
403
|
+
mimeType
|
|
404
|
+
}
|
|
405
|
+
]
|
|
406
|
+
};
|
|
407
|
+
} finally{
|
|
408
|
+
unsubscribeVerbose();
|
|
409
|
+
}
|
|
410
|
+
} catch (error) {
|
|
411
|
+
const errorMessage = getErrorMessage(error);
|
|
412
|
+
console.error('Error taking screenshot:', errorMessage);
|
|
413
|
+
return createErrorResult(`Failed to capture screenshot: ${errorMessage}`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
name: 'act',
|
|
419
|
+
description: 'Execute a natural language action. The AI will plan and perform multi-step operations in a single invocation, useful for transient UI interactions (e.g., Spotlight, dropdown menus) that disappear between separate commands.',
|
|
420
|
+
schema: {
|
|
421
|
+
prompt: z.string().describe('Natural language description of the action to perform, e.g. "press Command+Space, type Safari, press Enter"'),
|
|
422
|
+
deepLocate: z.boolean().optional().describe('Use deep locate for every element this action targets. Improves precision for small or ambiguous targets at the cost of speed. Defaults to the server --deep-locate setting.'),
|
|
423
|
+
deepThink: z.boolean().optional().describe('Plan this action with deep thinking (richer context and sub-goal decomposition). Helps with complex multi-step instructions at the cost of speed. Defaults to the server --deep-think setting.'),
|
|
424
|
+
fileChooserAllowedDir: z.string().optional().describe('Directory that model-planned file uploads may access. Required when the prompt asks the model to upload files.'),
|
|
425
|
+
...promptInputExtraSchema,
|
|
426
|
+
...initArgSchema
|
|
427
|
+
},
|
|
428
|
+
cli: mergeToolCliMetadata(void 0, initArgCliMetadata),
|
|
429
|
+
handler: async (args = {})=>{
|
|
430
|
+
const prompt = args.prompt;
|
|
431
|
+
try {
|
|
432
|
+
const agent = await getAgent(args);
|
|
433
|
+
emitCliVerboseEvent({
|
|
434
|
+
event: 'agent_ready',
|
|
435
|
+
tool: 'act'
|
|
436
|
+
});
|
|
437
|
+
if (!agent.aiAction) return createErrorResult('act is not supported by this agent');
|
|
438
|
+
const unsubscribeVerbose = attachCliVerboseDumpListener(agent, {
|
|
439
|
+
toolName: 'act'
|
|
440
|
+
});
|
|
441
|
+
try {
|
|
442
|
+
const actOptions = {
|
|
443
|
+
deepThink: false,
|
|
444
|
+
...toolDefaults.act
|
|
445
|
+
};
|
|
446
|
+
if (void 0 !== args.deepLocate) actOptions.deepLocate = args.deepLocate;
|
|
447
|
+
if (void 0 !== args.deepThink) actOptions.deepThink = args.deepThink;
|
|
448
|
+
if (void 0 !== args.fileChooserAllowedDir) actOptions.fileChooserAllowedDir = args.fileChooserAllowedDir;
|
|
449
|
+
const userPrompt = composeUserPrompt({
|
|
450
|
+
prompt,
|
|
451
|
+
image: args.image,
|
|
452
|
+
imageName: args.imageName,
|
|
453
|
+
convertHttpImage2Base64: args.convertHttpImage2Base64
|
|
454
|
+
});
|
|
455
|
+
const result = await agent.aiAction(userPrompt, actOptions);
|
|
456
|
+
return await captureScreenshotResult(agent, 'act', result);
|
|
457
|
+
} finally{
|
|
458
|
+
unsubscribeVerbose();
|
|
459
|
+
}
|
|
460
|
+
} catch (error) {
|
|
461
|
+
const errorMessage = getErrorMessage(error);
|
|
462
|
+
console.error('Error executing act:', errorMessage);
|
|
463
|
+
return createErrorResult(`Failed to execute act: ${errorMessage}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
name: 'assert',
|
|
469
|
+
description: 'Assert a natural language statement against the current page/screen or a saved UI observation record.',
|
|
470
|
+
schema: {
|
|
471
|
+
prompt: z.string().describe('Natural language assertion to verify, e.g. "there is a login button visible"'),
|
|
472
|
+
message: z.string().optional().describe('Custom error message to throw when the assertion fails, e.g. "the login button should be visible".'),
|
|
473
|
+
record: z.string().optional().describe('Path to a JSON observation manifest created by the record command. Keep its adjacent image directory with it. When set, the assertion uses that ordered frame window instead of the current screen.'),
|
|
474
|
+
...promptInputExtraSchema,
|
|
475
|
+
...initArgSchema
|
|
476
|
+
},
|
|
477
|
+
cli: mergeToolCliMetadata(void 0, initArgCliMetadata),
|
|
478
|
+
handler: async (args = {})=>{
|
|
479
|
+
const prompt = args.prompt;
|
|
480
|
+
const message = args.message;
|
|
481
|
+
try {
|
|
482
|
+
const agent = await getAgent(args);
|
|
483
|
+
emitCliVerboseEvent({
|
|
484
|
+
event: 'agent_ready',
|
|
485
|
+
tool: 'assert'
|
|
486
|
+
});
|
|
487
|
+
if (!agent.aiAssert) return createErrorResult('assert is not supported by this agent');
|
|
488
|
+
const unsubscribeVerbose = attachCliVerboseDumpListener(agent, {
|
|
489
|
+
toolName: 'assert'
|
|
490
|
+
});
|
|
491
|
+
try {
|
|
492
|
+
const observationRecord = 'string' == typeof args.record ? readUIObservationRecord(args.record) : void 0;
|
|
493
|
+
const userPrompt = composeUserPrompt({
|
|
494
|
+
prompt,
|
|
495
|
+
image: args.image,
|
|
496
|
+
imageName: args.imageName,
|
|
497
|
+
convertHttpImage2Base64: args.convertHttpImage2Base64
|
|
498
|
+
});
|
|
499
|
+
if (observationRecord) {
|
|
500
|
+
const observationArtifacts = resolveObservationArtifactAdapter(agent);
|
|
501
|
+
if (!observationArtifacts) throw new Error('assert --record is not supported because this agent cannot load UI observations');
|
|
502
|
+
const observation = observationArtifacts.loadRecord(observationRecord);
|
|
503
|
+
try {
|
|
504
|
+
await observation.aiAssert(userPrompt, message);
|
|
505
|
+
} finally{
|
|
506
|
+
await observation.dispose?.();
|
|
507
|
+
}
|
|
508
|
+
} else await agent.aiAssert(userPrompt, message);
|
|
509
|
+
return {
|
|
510
|
+
content: [
|
|
511
|
+
{
|
|
512
|
+
type: 'text',
|
|
513
|
+
text: 'Assertion passed.'
|
|
514
|
+
}
|
|
515
|
+
]
|
|
516
|
+
};
|
|
517
|
+
} finally{
|
|
518
|
+
unsubscribeVerbose();
|
|
519
|
+
}
|
|
520
|
+
} catch (error) {
|
|
521
|
+
const errorMessage = getErrorMessage(error);
|
|
522
|
+
console.error('Error executing assert:', errorMessage);
|
|
523
|
+
return createErrorResult(`Failed to execute assert: ${errorMessage}`);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
];
|
|
528
|
+
}
|
|
529
|
+
export { composeUserPrompt, generateCommonTools, generateToolsFromActionSpace };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
function normalizeStringList(raw, fieldName) {
|
|
3
|
+
if (null == raw) return [];
|
|
4
|
+
if ('string' == typeof raw) {
|
|
5
|
+
const trimmed = raw.trim();
|
|
6
|
+
return trimmed ? [
|
|
7
|
+
trimmed
|
|
8
|
+
] : [];
|
|
9
|
+
}
|
|
10
|
+
if (Array.isArray(raw)) return raw.map((item, index)=>{
|
|
11
|
+
if ('string' != typeof item) throw new Error(`${fieldName}[${index}]: expected a string.`);
|
|
12
|
+
return item.trim();
|
|
13
|
+
});
|
|
14
|
+
throw new Error(`${fieldName}: expected a string or string array, got ${typeof raw}.`);
|
|
15
|
+
}
|
|
16
|
+
function composeImages(input) {
|
|
17
|
+
const urls = normalizeStringList(input.image, 'image');
|
|
18
|
+
const names = normalizeStringList(input.imageName, 'imageName');
|
|
19
|
+
if (urls.length !== names.length) throw new Error(`image/imageName: expected the same number of --image and --image-name values, got ${urls.length} image(s) and ${names.length} image name(s).`);
|
|
20
|
+
return urls.map((url, index)=>({
|
|
21
|
+
name: names[index],
|
|
22
|
+
url
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
function coerceBoolean(value) {
|
|
26
|
+
if (null == value) return;
|
|
27
|
+
if ('boolean' == typeof value) return value;
|
|
28
|
+
if ('string' == typeof value) {
|
|
29
|
+
const trimmed = value.trim();
|
|
30
|
+
if (!trimmed) return;
|
|
31
|
+
const v = trimmed.toLowerCase();
|
|
32
|
+
if ('true' === v || '1' === v) return true;
|
|
33
|
+
if ('false' === v || '0' === v) return false;
|
|
34
|
+
throw new Error(`convertHttpImage2Base64: expected "true", "false", "1", or "0"; got ${JSON.stringify(value)}.`);
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`convertHttpImage2Base64: expected a boolean, got ${typeof value}.`);
|
|
37
|
+
}
|
|
38
|
+
function composeUserPrompt(input) {
|
|
39
|
+
const images = composeImages({
|
|
40
|
+
image: input.image,
|
|
41
|
+
imageName: input.imageName
|
|
42
|
+
});
|
|
43
|
+
const convertFlag = coerceBoolean(input.convertHttpImage2Base64);
|
|
44
|
+
if (0 === images.length && void 0 === convertFlag) return input.prompt;
|
|
45
|
+
const payload = {
|
|
46
|
+
prompt: input.prompt
|
|
47
|
+
};
|
|
48
|
+
if (images.length > 0) payload.images = images;
|
|
49
|
+
if (void 0 !== convertFlag) payload.convertHttpImage2Base64 = convertFlag;
|
|
50
|
+
return payload;
|
|
51
|
+
}
|
|
52
|
+
const promptInputExtraSchema = {
|
|
53
|
+
image: z.union([
|
|
54
|
+
z.string(),
|
|
55
|
+
z.array(z.string())
|
|
56
|
+
]).optional().describe('Reference image URL/path. Repeat --image for multiple images.'),
|
|
57
|
+
imageName: z.union([
|
|
58
|
+
z.string(),
|
|
59
|
+
z.array(z.string())
|
|
60
|
+
]).optional().describe('Reference image name. Repeat --image-name; must align with --image order.'),
|
|
61
|
+
convertHttpImage2Base64: z.union([
|
|
62
|
+
z.boolean(),
|
|
63
|
+
z.string()
|
|
64
|
+
]).optional().describe('If true, convert http(s) image URLs to base64 before sending to the model.')
|
|
65
|
+
};
|
|
66
|
+
export { composeUserPrompt, promptInputExtraSchema };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getKeyAliases } from "../key-alias-utils.mjs";
|
|
3
|
+
function parseValue(raw) {
|
|
4
|
+
if (raw.startsWith('{') || raw.startsWith('[')) try {
|
|
5
|
+
return JSON.parse(raw);
|
|
6
|
+
} catch {}
|
|
7
|
+
if (/^-?\d+(\.\d+)?$/.test(raw)) return Number(raw);
|
|
8
|
+
return raw;
|
|
9
|
+
}
|
|
10
|
+
function walkCliArgs(args, setArgValue) {
|
|
11
|
+
for(let i = 0; i < args.length; i++){
|
|
12
|
+
const arg = args[i];
|
|
13
|
+
if (!arg.startsWith('--')) continue;
|
|
14
|
+
const body = arg.slice(2);
|
|
15
|
+
const eqIdx = body.indexOf('=');
|
|
16
|
+
if (eqIdx >= 0) setArgValue(body.slice(0, eqIdx), parseValue(body.slice(eqIdx + 1)));
|
|
17
|
+
else if (args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
18
|
+
i++;
|
|
19
|
+
setArgValue(body, parseValue(args[i]));
|
|
20
|
+
} else setArgValue(body, true);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function parseCliArgs(args) {
|
|
24
|
+
const result = {};
|
|
25
|
+
walkCliArgs(args, (key, value)=>{
|
|
26
|
+
result[key] = value;
|
|
27
|
+
});
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
function formatCliOptionName(name) {
|
|
31
|
+
return `--${name}`;
|
|
32
|
+
}
|
|
33
|
+
function getCliOptionDisplay(key, cliOption) {
|
|
34
|
+
const label = formatCliOptionName(cliOption?.preferredName ?? key);
|
|
35
|
+
const aliases = [
|
|
36
|
+
...new Set(cliOption?.aliases ?? [])
|
|
37
|
+
].map((alias)=>formatCliOptionName(alias)).filter((alias)=>alias !== label);
|
|
38
|
+
return {
|
|
39
|
+
label,
|
|
40
|
+
aliases
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function getAcceptedCliOptionNames(key, cliOption) {
|
|
44
|
+
return [
|
|
45
|
+
...new Set(cliOption ? [
|
|
46
|
+
cliOption.preferredName ?? key,
|
|
47
|
+
...cliOption.aliases ?? []
|
|
48
|
+
] : [
|
|
49
|
+
key,
|
|
50
|
+
...getKeyAliases(key)
|
|
51
|
+
])
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
function toOptionalCliSchemaField(field) {
|
|
55
|
+
if ('object' == typeof field && null !== field && 'function' == typeof field.optional) return field.optional();
|
|
56
|
+
const description = 'object' == typeof field && null !== field && "description" in field && 'string' == typeof field.description ? field.description : void 0;
|
|
57
|
+
return description ? z.any().describe(description) : z.any();
|
|
58
|
+
}
|
|
59
|
+
function buildCliArgSchema(def) {
|
|
60
|
+
return Object.fromEntries(Object.entries(def.schema).flatMap(([key, zodType])=>getAcceptedCliOptionNames(key, def.cli?.options?.[key]).map((cliKey)=>[
|
|
61
|
+
cliKey,
|
|
62
|
+
toOptionalCliSchemaField(zodType)
|
|
63
|
+
])));
|
|
64
|
+
}
|
|
65
|
+
function buildDisallowedCliSpellings(def) {
|
|
66
|
+
const disallowedSpellings = new Set();
|
|
67
|
+
for (const [key] of Object.entries(def.schema)){
|
|
68
|
+
const cliOption = def.cli?.options?.[key];
|
|
69
|
+
const acceptedNames = new Set(getAcceptedCliOptionNames(key, cliOption));
|
|
70
|
+
const knownSpellings = new Set([
|
|
71
|
+
key,
|
|
72
|
+
...getKeyAliases(key),
|
|
73
|
+
...cliOption?.preferredName ? getKeyAliases(cliOption.preferredName) : [],
|
|
74
|
+
...cliOption?.aliases ?? []
|
|
75
|
+
]);
|
|
76
|
+
for (const spelling of knownSpellings)if (!acceptedNames.has(spelling)) disallowedSpellings.add(spelling);
|
|
77
|
+
}
|
|
78
|
+
return disallowedSpellings;
|
|
79
|
+
}
|
|
80
|
+
function formatCliValidationError(scriptName, commandName, def, rawArgs) {
|
|
81
|
+
if (0 === Object.keys(def.schema).length) return;
|
|
82
|
+
const cliSchema = z.object(buildCliArgSchema(def)).strict();
|
|
83
|
+
const parsed = cliSchema.safeParse(rawArgs);
|
|
84
|
+
if (parsed.success) return;
|
|
85
|
+
const disallowedSpellings = buildDisallowedCliSpellings(def);
|
|
86
|
+
const unknownKeys = parsed.error.issues.flatMap((issue)=>'unrecognized_keys' === issue.code ? issue.keys : []);
|
|
87
|
+
if (unknownKeys.length > 0) return unknownKeys.map((key)=>{
|
|
88
|
+
if (disallowedSpellings.has(key)) return `Unsupported option "--${key}" for ${scriptName} ${commandName}.`;
|
|
89
|
+
return `Unknown option "--${key}" for ${scriptName} ${commandName}.`;
|
|
90
|
+
}).join('\n');
|
|
91
|
+
const [issue] = parsed.error.issues;
|
|
92
|
+
const optionName = 'string' == typeof issue?.path[0] ? `--${issue.path[0]}` : 'CLI arguments';
|
|
93
|
+
return `Invalid value for "${optionName}" in ${scriptName} ${commandName}: ${issue?.message ?? parsed.error.message}`;
|
|
94
|
+
}
|
|
95
|
+
export { formatCliValidationError, getCliOptionDisplay, parseCliArgs, parseValue };
|