@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,250 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { ToolDefaults } from './tool-defaults';
|
|
3
|
+
|
|
4
|
+
// Avoid circular dependency: don't import from @aiscene/core
|
|
5
|
+
// Instead, use generic types that will be provided by implementation
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Default timeout constants for app loading verification
|
|
9
|
+
*/
|
|
10
|
+
export const defaultAppLoadingTimeoutMs = 10000;
|
|
11
|
+
export const defaultAppLoadingCheckIntervalMs = 2000;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Content item types for tool results.
|
|
15
|
+
*/
|
|
16
|
+
export type ToolResultContent =
|
|
17
|
+
| { type: 'text'; text: string }
|
|
18
|
+
| { type: 'image'; data: string; mimeType: string }
|
|
19
|
+
| { type: 'audio'; data: string; mimeType: string }
|
|
20
|
+
| {
|
|
21
|
+
type: 'resource';
|
|
22
|
+
resource:
|
|
23
|
+
| { text: string; uri: string; mimeType?: string }
|
|
24
|
+
| { uri: string; blob: string; mimeType?: string };
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Result type for tool execution.
|
|
29
|
+
*/
|
|
30
|
+
export interface ToolResult {
|
|
31
|
+
[x: string]: unknown;
|
|
32
|
+
content: ToolResultContent[];
|
|
33
|
+
isError?: boolean;
|
|
34
|
+
_meta?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tool handler function type
|
|
39
|
+
* Takes parsed arguments and returns a tool result
|
|
40
|
+
*/
|
|
41
|
+
export type ToolHandler<T = Record<string, unknown>> = (
|
|
42
|
+
args: T,
|
|
43
|
+
) => Promise<ToolResult>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Tool schema type using Zod
|
|
47
|
+
*/
|
|
48
|
+
export type ToolSchema = Record<string, z.ZodTypeAny>;
|
|
49
|
+
|
|
50
|
+
export interface ToolCliOption {
|
|
51
|
+
preferredName?: string;
|
|
52
|
+
aliases?: string[];
|
|
53
|
+
hidden?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ToolCliMetadata {
|
|
57
|
+
options?: Record<string, ToolCliOption>;
|
|
58
|
+
/** Schema keys populated from leading positional CLI arguments. */
|
|
59
|
+
positionals?: string[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Tool definition for Midscene CLI and Skill surfaces.
|
|
64
|
+
*/
|
|
65
|
+
export interface ToolDefinition<T = Record<string, unknown>> {
|
|
66
|
+
name: string;
|
|
67
|
+
description: string;
|
|
68
|
+
schema: ToolSchema;
|
|
69
|
+
handler: ToolHandler<T>;
|
|
70
|
+
cli?: ToolCliMetadata;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type Tool = ToolDefinition;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Action space item definition
|
|
77
|
+
* Note: Intentionally no index signature to maintain compatibility with DeviceAction
|
|
78
|
+
*/
|
|
79
|
+
export interface ActionSpaceItem {
|
|
80
|
+
name: string;
|
|
81
|
+
description?: string;
|
|
82
|
+
args?: Record<string, unknown>;
|
|
83
|
+
paramSchema?: z.ZodTypeAny;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Structural shape compatible with @aiscene/core `TUserPrompt`.
|
|
88
|
+
* Declared locally to avoid a circular dep on `@aiscene/core` from `@aiscene/shared`.
|
|
89
|
+
*
|
|
90
|
+
* Consumed by the `assert` and `act` tools in `generateCommonTools`, both of
|
|
91
|
+
* which forward reference images to core (`aiAssert` / `aiAct`). `aiWaitFor`
|
|
92
|
+
* stays string-only at the CLI surface because the tools generator does not
|
|
93
|
+
* yet expose a multimodal entry point for it.
|
|
94
|
+
*/
|
|
95
|
+
export type UserPromptLike =
|
|
96
|
+
| string
|
|
97
|
+
| {
|
|
98
|
+
prompt: string;
|
|
99
|
+
images?: Array<{ name: string; url: string }>;
|
|
100
|
+
convertHttpImage2Base64?: boolean;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export interface RecordToReportScreenshot {
|
|
104
|
+
/**
|
|
105
|
+
* PNG/JPEG data URI, or raw PNG base64 body.
|
|
106
|
+
*/
|
|
107
|
+
base64: string;
|
|
108
|
+
description?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface RecordToReportOptions {
|
|
112
|
+
content?: string;
|
|
113
|
+
/**
|
|
114
|
+
* @deprecated Use `screenshots: [{ base64 }]` instead.
|
|
115
|
+
*/
|
|
116
|
+
screenshotBase64?: string;
|
|
117
|
+
screenshots?: RecordToReportScreenshot[];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Generic progress-bus envelope as seen by tool consumers (all untrusted). */
|
|
121
|
+
export interface BaseAgentProgressEvent {
|
|
122
|
+
scope?: unknown;
|
|
123
|
+
phase?: unknown;
|
|
124
|
+
sequence?: unknown;
|
|
125
|
+
data?: unknown;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** A single frame in a portable UI observation record. */
|
|
129
|
+
export interface UIObservationFrame {
|
|
130
|
+
/** Path to the captured image. Serialized manifests use relative paths. */
|
|
131
|
+
path: string;
|
|
132
|
+
/** MIME type of the captured image file. */
|
|
133
|
+
mimeType: 'image/png' | 'image/jpeg';
|
|
134
|
+
/** Capture timestamp in milliseconds. */
|
|
135
|
+
capturedAt: number;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Serializable observation window that can be persisted and loaded later.
|
|
140
|
+
* Frames are ordered from earliest to latest; the final frame represents the
|
|
141
|
+
* UI state at the end of the observation window.
|
|
142
|
+
*/
|
|
143
|
+
export interface UIObservationRecord {
|
|
144
|
+
type: 'midscene_ui_observation';
|
|
145
|
+
version: 1;
|
|
146
|
+
/** Time when observation sampling started. */
|
|
147
|
+
startedAt: number;
|
|
148
|
+
/** Time when the final representative frame was captured. */
|
|
149
|
+
endedAt: number;
|
|
150
|
+
frames: UIObservationFrame[];
|
|
151
|
+
shotSize: {
|
|
152
|
+
width: number;
|
|
153
|
+
height: number;
|
|
154
|
+
};
|
|
155
|
+
shrunkShotToLogicalRatio: number;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Fixed observation window consumed by shared CLI surfaces. */
|
|
159
|
+
export interface BaseUIObservation {
|
|
160
|
+
readonly frameCount: number;
|
|
161
|
+
readonly startedAt: number;
|
|
162
|
+
readonly endedAt: number;
|
|
163
|
+
aiAssert(
|
|
164
|
+
assertion: UserPromptLike,
|
|
165
|
+
message?: string,
|
|
166
|
+
options?: Record<string, unknown>,
|
|
167
|
+
): Promise<unknown>;
|
|
168
|
+
/** Release any resources owned by this observation. */
|
|
169
|
+
dispose?(): Promise<void>;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Minimal UI observation lifecycle required by shared tool surfaces. */
|
|
173
|
+
export interface BaseUIObserver {
|
|
174
|
+
readonly bufferedFrameCount: number;
|
|
175
|
+
/** Stop sampling and return the fixed observed window. */
|
|
176
|
+
stop(): Promise<BaseUIObservation>;
|
|
177
|
+
/** Release temporary backing files if observation does not complete. */
|
|
178
|
+
dispose?(): Promise<void>;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Options for {@link BaseAgent.startObserving}. */
|
|
182
|
+
export interface BaseUIObserverOptions {
|
|
183
|
+
/** Sampling interval in milliseconds. Defaults to 1000; minimum 200. */
|
|
184
|
+
intervalMs?: number;
|
|
185
|
+
/** Maximum number of buffered frames. Defaults to 30; minimum 2. */
|
|
186
|
+
maxFrames?: number;
|
|
187
|
+
/** Auto-stop timeout in milliseconds. Defaults to 300000; 0 disables it. */
|
|
188
|
+
watchdogMs?: number;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Base agent interface
|
|
193
|
+
* Represents a platform-specific agent (Android, iOS, Web)
|
|
194
|
+
* Note: Return types use `unknown` for compatibility with platform-specific implementations
|
|
195
|
+
*/
|
|
196
|
+
export interface BaseAgent {
|
|
197
|
+
getActionSpace(): Promise<ActionSpaceItem[]>;
|
|
198
|
+
destroy?(): Promise<void>;
|
|
199
|
+
reportFile?: string | null;
|
|
200
|
+
page?: {
|
|
201
|
+
screenshotBase64(): Promise<string>;
|
|
202
|
+
};
|
|
203
|
+
addDumpUpdateListener?(
|
|
204
|
+
listener: (dump: string, executionDump?: unknown) => void,
|
|
205
|
+
): () => void;
|
|
206
|
+
addProgressListener?(
|
|
207
|
+
listener: (event: BaseAgentProgressEvent) => void,
|
|
208
|
+
): () => void;
|
|
209
|
+
recordToReport?(
|
|
210
|
+
title?: string,
|
|
211
|
+
opt?: RecordToReportOptions,
|
|
212
|
+
): Promise<void>;
|
|
213
|
+
callActionInActionSpace?(
|
|
214
|
+
actionName: string,
|
|
215
|
+
params?: unknown,
|
|
216
|
+
): Promise<unknown>;
|
|
217
|
+
aiAction?(
|
|
218
|
+
description: UserPromptLike,
|
|
219
|
+
params?: Record<string, unknown>,
|
|
220
|
+
): Promise<unknown>;
|
|
221
|
+
aiWaitFor?(
|
|
222
|
+
assertion: string,
|
|
223
|
+
options: Record<string, unknown>,
|
|
224
|
+
): Promise<unknown>;
|
|
225
|
+
aiAssert?(
|
|
226
|
+
assertion: UserPromptLike,
|
|
227
|
+
msg?: string,
|
|
228
|
+
options?: Record<string, unknown>,
|
|
229
|
+
): Promise<unknown>;
|
|
230
|
+
/** Start a UI observation window and capture its baseline frame. */
|
|
231
|
+
startObserving?(options?: BaseUIObserverOptions): Promise<BaseUIObserver>;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Base device interface for temporary device instances
|
|
236
|
+
*/
|
|
237
|
+
export interface BaseDevice {
|
|
238
|
+
actionSpace(): ActionSpaceItem[];
|
|
239
|
+
destroy?(): Promise<void>;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Interface for platform-specific tools manager.
|
|
244
|
+
*/
|
|
245
|
+
export interface IMidsceneTools {
|
|
246
|
+
initTools(): Promise<void>;
|
|
247
|
+
destroy?(): Promise<void>;
|
|
248
|
+
getCliToolDefinitions?(): ToolDefinition[];
|
|
249
|
+
setToolDefaults?(toolDefaults: ToolDefaults): void;
|
|
250
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { UserPromptLike } from './types';
|
|
3
|
+
|
|
4
|
+
type PromptReferenceImage = { name: string; url: string };
|
|
5
|
+
|
|
6
|
+
function normalizeStringList(raw: unknown, fieldName: string): string[] {
|
|
7
|
+
if (raw === undefined || raw === null) return [];
|
|
8
|
+
if (typeof raw === 'string') {
|
|
9
|
+
const trimmed = raw.trim();
|
|
10
|
+
return trimmed ? [trimmed] : [];
|
|
11
|
+
}
|
|
12
|
+
if (Array.isArray(raw)) {
|
|
13
|
+
return raw.map((item, index) => {
|
|
14
|
+
if (typeof item !== 'string') {
|
|
15
|
+
throw new Error(`${fieldName}[${index}]: expected a string.`);
|
|
16
|
+
}
|
|
17
|
+
return item.trim();
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
throw new Error(
|
|
21
|
+
`${fieldName}: expected a string or string array, got ${typeof raw}.`,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function composeImages(input: {
|
|
26
|
+
image?: unknown;
|
|
27
|
+
imageName?: unknown;
|
|
28
|
+
}): PromptReferenceImage[] {
|
|
29
|
+
const urls = normalizeStringList(input.image, 'image');
|
|
30
|
+
const names = normalizeStringList(input.imageName, 'imageName');
|
|
31
|
+
|
|
32
|
+
if (urls.length !== names.length) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`image/imageName: expected the same number of --image and --image-name values, got ${urls.length} image(s) and ${names.length} image name(s).`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return urls.map((url, index) => ({ name: names[index], url }));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function coerceBoolean(value: unknown): boolean | undefined {
|
|
42
|
+
if (value === undefined || value === null) return undefined;
|
|
43
|
+
if (typeof value === 'boolean') return value;
|
|
44
|
+
if (typeof value === 'string') {
|
|
45
|
+
const trimmed = value.trim();
|
|
46
|
+
if (!trimmed) return undefined;
|
|
47
|
+
const v = trimmed.toLowerCase();
|
|
48
|
+
if (v === 'true' || v === '1') return true;
|
|
49
|
+
if (v === 'false' || v === '0') return false;
|
|
50
|
+
throw new Error(
|
|
51
|
+
`convertHttpImage2Base64: expected "true", "false", "1", or "0"; got ${JSON.stringify(value)}.`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
throw new Error(
|
|
55
|
+
`convertHttpImage2Base64: expected a boolean, got ${typeof value}.`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function composeUserPrompt(input: {
|
|
60
|
+
prompt: string;
|
|
61
|
+
image?: unknown;
|
|
62
|
+
imageName?: unknown;
|
|
63
|
+
convertHttpImage2Base64?: unknown;
|
|
64
|
+
}): UserPromptLike {
|
|
65
|
+
const images = composeImages({
|
|
66
|
+
image: input.image,
|
|
67
|
+
imageName: input.imageName,
|
|
68
|
+
});
|
|
69
|
+
const convertFlag = coerceBoolean(input.convertHttpImage2Base64);
|
|
70
|
+
|
|
71
|
+
if (images.length === 0 && convertFlag === undefined) {
|
|
72
|
+
return input.prompt;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const payload: Exclude<UserPromptLike, string> = { prompt: input.prompt };
|
|
76
|
+
if (images.length > 0) {
|
|
77
|
+
payload.images = images;
|
|
78
|
+
}
|
|
79
|
+
if (convertFlag !== undefined) {
|
|
80
|
+
payload.convertHttpImage2Base64 = convertFlag;
|
|
81
|
+
}
|
|
82
|
+
return payload;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const promptInputExtraSchema = {
|
|
86
|
+
image: z
|
|
87
|
+
.union([z.string(), z.array(z.string())])
|
|
88
|
+
.optional()
|
|
89
|
+
.describe('Reference image URL/path. Repeat --image for multiple images.'),
|
|
90
|
+
imageName: z
|
|
91
|
+
.union([z.string(), z.array(z.string())])
|
|
92
|
+
.optional()
|
|
93
|
+
.describe(
|
|
94
|
+
'Reference image name. Repeat --image-name; must align with --image order.',
|
|
95
|
+
),
|
|
96
|
+
convertHttpImage2Base64: z
|
|
97
|
+
.union([z.boolean(), z.string()])
|
|
98
|
+
.optional()
|
|
99
|
+
.describe(
|
|
100
|
+
'If true, convert http(s) image URLs to base64 before sending to the model.',
|
|
101
|
+
),
|
|
102
|
+
};
|
|
@@ -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
|
+
}
|
package/src/cli/cli-runner.ts
CHANGED
|
@@ -9,6 +9,12 @@ import type {
|
|
|
9
9
|
ToolResult,
|
|
10
10
|
ToolResultContent,
|
|
11
11
|
} from '../mcp/types';
|
|
12
|
+
import {
|
|
13
|
+
formatCliValidationError,
|
|
14
|
+
getCliOptionDisplay,
|
|
15
|
+
parseCliArgs,
|
|
16
|
+
} from './cli-args';
|
|
17
|
+
import { CLIError } from './cli-error';
|
|
12
18
|
|
|
13
19
|
const debug = getDebug('cli-runner');
|
|
14
20
|
|
|
@@ -32,58 +38,8 @@ export interface CLIRunnerOptions {
|
|
|
32
38
|
extraCommands?: CLIExtraCommand[];
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
message: string,
|
|
38
|
-
public exitCode = 1,
|
|
39
|
-
) {
|
|
40
|
-
super(message);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function parseValue(raw: string): unknown {
|
|
45
|
-
// JSON objects/arrays
|
|
46
|
-
if (raw.startsWith('{') || raw.startsWith('[')) {
|
|
47
|
-
try {
|
|
48
|
-
return JSON.parse(raw);
|
|
49
|
-
} catch {
|
|
50
|
-
// Not valid JSON, treat as string below
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Numbers
|
|
55
|
-
if (/^-?\d+(\.\d+)?$/.test(raw)) {
|
|
56
|
-
return Number(raw);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return raw;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function parseCliArgs(args: string[]): Record<string, unknown> {
|
|
63
|
-
const result: Record<string, unknown> = {};
|
|
64
|
-
|
|
65
|
-
for (let i = 0; i < args.length; i++) {
|
|
66
|
-
const arg = args[i];
|
|
67
|
-
if (!arg.startsWith('--')) continue;
|
|
68
|
-
|
|
69
|
-
const body = arg.slice(2);
|
|
70
|
-
const eqIdx = body.indexOf('=');
|
|
71
|
-
|
|
72
|
-
if (eqIdx >= 0) {
|
|
73
|
-
// --key=value
|
|
74
|
-
result[body.slice(0, eqIdx)] = parseValue(body.slice(eqIdx + 1));
|
|
75
|
-
} else if (args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
76
|
-
// --key value
|
|
77
|
-
i++;
|
|
78
|
-
result[body] = parseValue(args[i]);
|
|
79
|
-
} else {
|
|
80
|
-
// --flag (boolean)
|
|
81
|
-
result[body] = true;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return result;
|
|
86
|
-
}
|
|
41
|
+
export { parseCliArgs, parseValue } from './cli-args';
|
|
42
|
+
export { CLIError, reportCLIError } from './cli-error';
|
|
87
43
|
|
|
88
44
|
function outputContentItem(item: ToolResultContent, isError: boolean): void {
|
|
89
45
|
switch (item.type) {
|
|
@@ -128,10 +84,23 @@ function printCommandHelp(scriptName: string, cmd: CLICommand): void {
|
|
|
128
84
|
|
|
129
85
|
const schemaEntries = Object.entries(def.schema);
|
|
130
86
|
if (schemaEntries.length > 0) {
|
|
87
|
+
const optionWidth = Math.max(
|
|
88
|
+
22,
|
|
89
|
+
...schemaEntries.map(
|
|
90
|
+
([key]) =>
|
|
91
|
+
getCliOptionDisplay(key, def.cli?.options?.[key]).label.length,
|
|
92
|
+
),
|
|
93
|
+
);
|
|
131
94
|
console.log('\nOptions:');
|
|
132
95
|
for (const [key, zodType] of schemaEntries) {
|
|
96
|
+
const { label, aliases } = getCliOptionDisplay(
|
|
97
|
+
key,
|
|
98
|
+
def.cli?.options?.[key],
|
|
99
|
+
);
|
|
133
100
|
const desc = zodType.description ?? '';
|
|
134
|
-
|
|
101
|
+
const aliasText =
|
|
102
|
+
aliases.length > 0 ? ` (aliases: ${aliases.join(', ')})` : '';
|
|
103
|
+
console.log(` ${label.padEnd(optionWidth)} ${desc}${aliasText}`);
|
|
135
104
|
}
|
|
136
105
|
}
|
|
137
106
|
}
|
|
@@ -158,8 +127,10 @@ function printHelp(
|
|
|
158
127
|
console.log(`\nRun "${scriptName} <command> --help" for more info.`);
|
|
159
128
|
}
|
|
160
129
|
|
|
130
|
+
type AnyMidsceneTools = BaseMidsceneTools<any, any>;
|
|
131
|
+
|
|
161
132
|
export async function runToolsCLI(
|
|
162
|
-
tools:
|
|
133
|
+
tools: AnyMidsceneTools,
|
|
163
134
|
scriptName: string,
|
|
164
135
|
options?: CLIRunnerOptions,
|
|
165
136
|
): Promise<void> {
|
|
@@ -227,14 +198,24 @@ export async function runToolsCLI(
|
|
|
227
198
|
}
|
|
228
199
|
|
|
229
200
|
const parsedArgs = parseCliArgs(restArgs);
|
|
230
|
-
debug('command: %s, args: %s', match.name, JSON.stringify(parsedArgs));
|
|
231
|
-
|
|
232
201
|
if (parsedArgs.help === true) {
|
|
233
202
|
debug('showing command help for: %s', match.name);
|
|
234
203
|
printCommandHelp(scriptName, match);
|
|
235
204
|
return;
|
|
236
205
|
}
|
|
237
206
|
|
|
207
|
+
const cliValidationError = formatCliValidationError(
|
|
208
|
+
scriptName,
|
|
209
|
+
match.name,
|
|
210
|
+
match.def,
|
|
211
|
+
parsedArgs,
|
|
212
|
+
);
|
|
213
|
+
if (cliValidationError) {
|
|
214
|
+
throw new CLIError(cliValidationError);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
debug('command: %s, args: %s', match.name, JSON.stringify(parsedArgs));
|
|
218
|
+
|
|
238
219
|
const result = await match.def.handler(parsedArgs);
|
|
239
220
|
debug(
|
|
240
221
|
'command %s completed, isError: %s',
|
package/src/cli/index.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
export {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
parseValue,
|
|
5
|
-
parseCliArgs,
|
|
6
|
-
removePrefix,
|
|
7
|
-
} from './cli-runner';
|
|
1
|
+
export { CLIError, reportCLIError } from './cli-error';
|
|
2
|
+
export { parseCliArgs, parseValue } from './cli-args';
|
|
3
|
+
export { runToolsCLI, removePrefix } from './cli-runner';
|
|
8
4
|
export type { CLIRunnerOptions, CLIExtraCommand } from './cli-runner';
|