@mearl/provider 2.20.0 → 2.20.1
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/README.md +25 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/provider-runtime.d.ts +2 -1
- package/dist/provider-runtime.js +78 -1
- package/dist/registry.js +12 -1
- package/dist/server.js +8 -0
- package/dist/types.d.ts +35 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -136,6 +136,31 @@ Provider 可用 `component: "switch"` 渲染紧凑布尔开关;`formHidden`
|
|
|
136
136
|
`browserType.documentationUrl` 声明 HTTP(S) 文档地址;`configuration.inputSchema` 声明配置参数,
|
|
137
137
|
配置只传给所选 Provider 的 `configure`,不存入页面本地存储。
|
|
138
138
|
|
|
139
|
+
Provider 需要查询设备池、已有会话或动态候选项时,声明 `browserType.dynamicLaunchForm: true`
|
|
140
|
+
并实现只读的 `getLaunchForm({ variant?, values }, context)`。
|
|
141
|
+
`browserType.launchForm` 可提供无网络依赖的初始表单,弹窗立即渲染其字段和模式;
|
|
142
|
+
各 `variants[]` 可用 `inputSchema/values/refreshOn/dynamicFields` 覆盖该模式的初值。
|
|
143
|
+
`dynamicFields` 指定异步查询期间需要禁用的字段,其余字段与模式选择保持可操作;加载状态在表单状态行显示。
|
|
144
|
+
初次查询携带静态表单默认值;查询只传入可解析的字段,最终启动仍严格校验全部输入。
|
|
145
|
+
用户在等待期间输入的内容会保留,切换模式或环境会使旧请求失效。
|
|
146
|
+
管理器通过内部
|
|
147
|
+
`browser_provider_launch_form` 请求获取 `ProviderLaunchForm`,不识别 Provider ID 或业务字段:
|
|
148
|
+
|
|
149
|
+
- `inputSchema` 描述当前模式的用户输入,`required` 决定必填标识与提交校验。
|
|
150
|
+
- `variants` 的 `id/title/description` 描述连接方式,`variant` 指定当前方式。
|
|
151
|
+
- `values` 是 Provider 给出的固定启动参数,提交时与用户输入合并;只包含非敏感参数。
|
|
152
|
+
- 字符串字段的 `options` 使用 `{ value, label, description?, disabled? }` 描述候选项。
|
|
153
|
+
禁用项只供展示;运行时的最终资源状态由 Provider 的 `launch` 再次核验。
|
|
154
|
+
- `refreshOn` 指定需要重新查询表单的字段;字段变化会清空其他选项字段的选择。
|
|
155
|
+
刷新时保留已有输入,选项字段仅保留仍有效的选择;切换模式重新使用该模式默认值。
|
|
156
|
+
- `message` 展示查询结果,`blocked` 阻止无资源、无有效会话等情况下的提交。
|
|
157
|
+
|
|
158
|
+
查询过程不得创建或租用资源,也不得返回凭据。Runtime 会校验返回的 schema、候选项、
|
|
159
|
+
模式与固定参数。最终 `launch` 仍使用 `browserType.inputSchema` 校验,动态表单不改变 CLI/API
|
|
160
|
+
参数契约。未声明动态表单的 Provider 继续使用静态 schema。
|
|
161
|
+
开发联调时,Provider 应链接包含该公开契约的本地 `@mearl/provider` 构建;分发时需配套发布
|
|
162
|
+
包含动态表单能力的 SDK、Mearl 宿主与 Provider。
|
|
163
|
+
|
|
139
164
|
`details` 中的通用键具有固定类型:`headless` 为布尔值,`createdAt` 为可解析时间字符串,
|
|
140
165
|
`cdpPort` 为有效端口,`account` 包含 `nickname` 和可选的字符串 `accountId/loginId`,
|
|
141
166
|
`copiedCookies` 包含字符串数组 `domains` 和非负整数 `count`。其余键允许非敏感 JSON,界面同样展示。
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
2
|
export { PROVIDER_ACTIONS, isProviderAction } from './generated-provider-actions.js';
|
|
3
3
|
export { providerDataDir } from './registry.js';
|
|
4
|
-
export { createProviderRuntime, validateProviderBrowser, validateProviderInput, } from './provider-runtime.js';
|
|
4
|
+
export { createProviderRuntime, validateProviderBrowser, validateProviderInput, validateProviderLaunchForm, } from './provider-runtime.js';
|
|
5
5
|
export { createProviderServer } from './server.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
2
|
export { PROVIDER_ACTIONS, isProviderAction } from './generated-provider-actions.js';
|
|
3
3
|
export { providerDataDir } from './registry.js';
|
|
4
|
-
export { createProviderRuntime, validateProviderBrowser, validateProviderInput, } from './provider-runtime.js';
|
|
4
|
+
export { createProviderRuntime, validateProviderBrowser, validateProviderInput, validateProviderLaunchForm, } from './provider-runtime.js';
|
|
5
5
|
export { createProviderServer } from './server.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { BrowserProviderDefinition, JsonObject, ProviderBrowser, ProviderInputSchema, ProviderObjectSchema, ProviderRuntime } from './types.js';
|
|
1
|
+
import type { BrowserProviderDefinition, JsonObject, ProviderBrowser, ProviderInputSchema, ProviderObjectSchema, ProviderRuntime, ProviderLaunchForm } from './types.js';
|
|
2
2
|
export declare function isProviderInputSchema(value: unknown, depth?: number): value is ProviderInputSchema;
|
|
3
3
|
export declare function validateProviderInput(schema: ProviderObjectSchema, value: JsonObject): void;
|
|
4
4
|
export declare function validateProviderBrowser(browser: ProviderBrowser): void;
|
|
5
|
+
export declare function validateProviderLaunchForm(form: ProviderLaunchForm): void;
|
|
5
6
|
export declare function createProviderRuntime(definition: BrowserProviderDefinition): ProviderRuntime;
|
package/dist/provider-runtime.js
CHANGED
|
@@ -71,7 +71,18 @@ export function isProviderInputSchema(value, depth = 0) {
|
|
|
71
71
|
Number(schema.minLength) <= Number(schema.maxLength)) &&
|
|
72
72
|
isOptionalString(schema.pattern) &&
|
|
73
73
|
validPattern &&
|
|
74
|
-
isOptionalString(schema.placeholder)
|
|
74
|
+
isOptionalString(schema.placeholder) &&
|
|
75
|
+
(schema.options === undefined ||
|
|
76
|
+
(Array.isArray(schema.options) &&
|
|
77
|
+
schema.options.every(option => option &&
|
|
78
|
+
typeof option === 'object' &&
|
|
79
|
+
!Array.isArray(option) &&
|
|
80
|
+
typeof option.value === 'string' &&
|
|
81
|
+
typeof option.label === 'string' &&
|
|
82
|
+
isOptionalString(option.description) &&
|
|
83
|
+
(option.disabled === undefined || typeof option.disabled === 'boolean') &&
|
|
84
|
+
(schema.enum === undefined || schema.enum.includes(option.value))) &&
|
|
85
|
+
new Set(schema.options.map(option => option.value)).size === schema.options.length));
|
|
75
86
|
if (!valid)
|
|
76
87
|
return false;
|
|
77
88
|
}
|
|
@@ -387,6 +398,13 @@ function validateDefinition(definition) {
|
|
|
387
398
|
(typeof definition.browserType.documentationUrl !== 'string' ||
|
|
388
399
|
!/^https?:\/\//.test(definition.browserType.documentationUrl)))
|
|
389
400
|
throw new Error('Provider documentation URL must use HTTP(S)');
|
|
401
|
+
if (definition.browserType.dynamicLaunchForm !== undefined &&
|
|
402
|
+
typeof definition.browserType.dynamicLaunchForm !== 'boolean')
|
|
403
|
+
throw new Error('Provider dynamic launch form declaration is invalid');
|
|
404
|
+
if (definition.browserType.dynamicLaunchForm && !definition.getLaunchForm)
|
|
405
|
+
throw new Error('Provider does not implement getLaunchForm');
|
|
406
|
+
if (definition.browserType.launchForm !== undefined)
|
|
407
|
+
validateProviderLaunchForm(definition.browserType.launchForm);
|
|
390
408
|
const configuration = definition.browserType.configuration;
|
|
391
409
|
if (configuration &&
|
|
392
410
|
(!isProviderInputSchema(configuration.inputSchema) ||
|
|
@@ -394,6 +412,45 @@ function validateDefinition(definition) {
|
|
|
394
412
|
throw new Error('Provider configuration schema is invalid');
|
|
395
413
|
}
|
|
396
414
|
}
|
|
415
|
+
export function validateProviderLaunchForm(form) {
|
|
416
|
+
if (!form ||
|
|
417
|
+
!isProviderInputSchema(form.inputSchema) ||
|
|
418
|
+
form.inputSchema.type !== 'object' ||
|
|
419
|
+
!isOptionalString(form.variant) ||
|
|
420
|
+
!isOptionalString(form.message) ||
|
|
421
|
+
(form.blocked !== undefined && typeof form.blocked !== 'boolean') ||
|
|
422
|
+
(form.values !== undefined &&
|
|
423
|
+
(!form.values ||
|
|
424
|
+
typeof form.values !== 'object' ||
|
|
425
|
+
Array.isArray(form.values) ||
|
|
426
|
+
!isJsonValue(form.values))) ||
|
|
427
|
+
(form.dynamicFields !== undefined &&
|
|
428
|
+
(!Array.isArray(form.dynamicFields) ||
|
|
429
|
+
form.dynamicFields.some(field => typeof field !== 'string' || !(field in form.inputSchema.properties)))) ||
|
|
430
|
+
(form.refreshOn !== undefined &&
|
|
431
|
+
(!Array.isArray(form.refreshOn) ||
|
|
432
|
+
form.refreshOn.some(field => typeof field !== 'string' || !(field in form.inputSchema.properties)))) ||
|
|
433
|
+
(form.variants !== undefined &&
|
|
434
|
+
(!Array.isArray(form.variants) ||
|
|
435
|
+
!form.variants.length ||
|
|
436
|
+
form.variants.some(variant => !variant ||
|
|
437
|
+
typeof variant.id !== 'string' ||
|
|
438
|
+
!variant.id ||
|
|
439
|
+
typeof variant.title !== 'string' ||
|
|
440
|
+
!isOptionalString(variant.description)) ||
|
|
441
|
+
new Set(form.variants.map(variant => variant.id)).size !== form.variants.length ||
|
|
442
|
+
!form.variants.some(variant => variant.id === form.variant)))) {
|
|
443
|
+
throw new Error('Provider returned an invalid launch form');
|
|
444
|
+
}
|
|
445
|
+
for (const variant of form.variants ?? []) {
|
|
446
|
+
validateProviderLaunchForm({
|
|
447
|
+
inputSchema: variant.inputSchema === undefined ? form.inputSchema : variant.inputSchema,
|
|
448
|
+
values: variant.values === undefined ? form.values : variant.values,
|
|
449
|
+
refreshOn: variant.refreshOn === undefined ? form.refreshOn : variant.refreshOn,
|
|
450
|
+
dynamicFields: variant.dynamicFields === undefined ? form.dynamicFields : variant.dynamicFields,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
397
454
|
export function createProviderRuntime(definition) {
|
|
398
455
|
validateDefinition(definition);
|
|
399
456
|
let publishedBrowsers = [];
|
|
@@ -445,6 +502,26 @@ export function createProviderRuntime(definition) {
|
|
|
445
502
|
}
|
|
446
503
|
publishedBrowsers = [...browsers];
|
|
447
504
|
},
|
|
505
|
+
async getLaunchForm(request, context = {}) {
|
|
506
|
+
if (!definition.browserType.dynamicLaunchForm || !definition.getLaunchForm)
|
|
507
|
+
throw new Error('Provider does not support dynamic launch forms');
|
|
508
|
+
if (!request ||
|
|
509
|
+
!isOptionalString(request.variant) ||
|
|
510
|
+
!request.values ||
|
|
511
|
+
typeof request.values !== 'object' ||
|
|
512
|
+
Array.isArray(request.values) ||
|
|
513
|
+
!isJsonValue(request.values))
|
|
514
|
+
throw new Error('Invalid launch form request');
|
|
515
|
+
const form = await definition.getLaunchForm(request, context);
|
|
516
|
+
validateProviderLaunchForm(form);
|
|
517
|
+
for (const [key, value] of Object.entries(form.values ?? {})) {
|
|
518
|
+
const schema = definition.browserType.inputSchema.properties[key];
|
|
519
|
+
if (!schema)
|
|
520
|
+
throw new Error(`Launch form returned an unknown parameter: ${key}`);
|
|
521
|
+
validateSchemaValue(schema, value, key);
|
|
522
|
+
}
|
|
523
|
+
return form;
|
|
524
|
+
},
|
|
448
525
|
async launch(options, context = {}) {
|
|
449
526
|
validateProviderInput(definition.browserType.inputSchema, options);
|
|
450
527
|
const browser = await definition.launch(options, context);
|
package/dist/registry.js
CHANGED
|
@@ -3,7 +3,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import { claimRecord, ensureDir, hashKey, isAlive, keyedConfigFile, keyedDir, listRecordFiles, readLiveRecords, readRecord, removeInvalidRecord, removeRecordIfPid, writeRecord, writeRecordIfPid, } from '@mearl/daemon-core';
|
|
5
5
|
import { MEARL_PROVIDER_PROTOCOL_VERSION, } from './types.js';
|
|
6
|
-
import { isProviderInputSchema } from './provider-runtime.js';
|
|
6
|
+
import { validateProviderLaunchForm, isProviderInputSchema } from './provider-runtime.js';
|
|
7
7
|
export const INSTALLED_PROVIDERS_SUBDIR = 'providers/installed';
|
|
8
8
|
export const LIVE_PROVIDERS_SUBDIR = 'providers/live';
|
|
9
9
|
export const STARTING_PROVIDERS_SUBDIR = 'providers/starting';
|
|
@@ -155,6 +155,17 @@ function isProviderBrowserType(value) {
|
|
|
155
155
|
return false;
|
|
156
156
|
if (!isProviderObjectSchema(browserType.inputSchema))
|
|
157
157
|
return false;
|
|
158
|
+
if (browserType.dynamicLaunchForm !== undefined &&
|
|
159
|
+
typeof browserType.dynamicLaunchForm !== 'boolean')
|
|
160
|
+
return false;
|
|
161
|
+
if (browserType.launchForm !== undefined) {
|
|
162
|
+
try {
|
|
163
|
+
validateProviderLaunchForm(browserType.launchForm);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
158
169
|
return (browserType.configuration === undefined ||
|
|
159
170
|
(Boolean(browserType.configuration) &&
|
|
160
171
|
typeof browserType.configuration === 'object' &&
|
package/dist/server.js
CHANGED
|
@@ -84,6 +84,14 @@ export function createProviderServer(options) {
|
|
|
84
84
|
if (request.controlSessionId)
|
|
85
85
|
throw new Error('CONTROL_SESSION_UNSUPPORTED: this Provider does not implement control sessions');
|
|
86
86
|
const context = providerContext(request);
|
|
87
|
+
if (request.action === 'browser_provider_launch_form') {
|
|
88
|
+
if (request.browserId)
|
|
89
|
+
throw new Error('Provider launch form does not accept browserId');
|
|
90
|
+
return runtime.getLaunchForm({
|
|
91
|
+
variant: request.data?.variant,
|
|
92
|
+
values: request.data?.values,
|
|
93
|
+
}, context);
|
|
94
|
+
}
|
|
87
95
|
if (request.action === 'browser_launch') {
|
|
88
96
|
if (request.browserId)
|
|
89
97
|
throw new Error('browser_launch does not accept browserId');
|
package/dist/types.d.ts
CHANGED
|
@@ -62,6 +62,37 @@ export interface ProviderStringSchema extends ProviderSchemaBase {
|
|
|
62
62
|
maxLength?: number;
|
|
63
63
|
pattern?: string;
|
|
64
64
|
placeholder?: string;
|
|
65
|
+
options?: readonly ProviderInputOption[];
|
|
66
|
+
}
|
|
67
|
+
export interface ProviderInputOption {
|
|
68
|
+
value: string;
|
|
69
|
+
label: string;
|
|
70
|
+
description?: string;
|
|
71
|
+
disabled?: boolean;
|
|
72
|
+
}
|
|
73
|
+
export interface ProviderLaunchFormRequest {
|
|
74
|
+
variant?: string;
|
|
75
|
+
values: JsonObject;
|
|
76
|
+
}
|
|
77
|
+
export interface ProviderLaunchFormVariant {
|
|
78
|
+
id: string;
|
|
79
|
+
title: string;
|
|
80
|
+
description?: string;
|
|
81
|
+
inputSchema?: ProviderObjectSchema;
|
|
82
|
+
values?: JsonObject;
|
|
83
|
+
refreshOn?: readonly string[];
|
|
84
|
+
dynamicFields?: readonly string[];
|
|
85
|
+
}
|
|
86
|
+
/** Read-only presentation data; values are fixed launch parameters, never credentials. */
|
|
87
|
+
export interface ProviderLaunchForm {
|
|
88
|
+
inputSchema: ProviderObjectSchema;
|
|
89
|
+
variants?: readonly ProviderLaunchFormVariant[];
|
|
90
|
+
variant?: string;
|
|
91
|
+
values?: JsonObject;
|
|
92
|
+
refreshOn?: readonly string[];
|
|
93
|
+
dynamicFields?: readonly string[];
|
|
94
|
+
message?: string;
|
|
95
|
+
blocked?: boolean;
|
|
65
96
|
}
|
|
66
97
|
export interface ProviderNumberSchema extends ProviderSchemaBase {
|
|
67
98
|
type: 'number';
|
|
@@ -97,6 +128,8 @@ export interface ProviderBrowserType {
|
|
|
97
128
|
help?: readonly string[];
|
|
98
129
|
inputSchema: ProviderObjectSchema;
|
|
99
130
|
configuration?: ProviderConfigurationDefinition;
|
|
131
|
+
dynamicLaunchForm?: boolean;
|
|
132
|
+
launchForm?: ProviderLaunchForm;
|
|
100
133
|
}
|
|
101
134
|
export type ProviderBrowserStatus = 'connected' | 'pairing' | 'running_disconnected' | 'stopped';
|
|
102
135
|
export type ProviderOwnership = 'owned' | 'attached';
|
|
@@ -210,6 +243,7 @@ export interface BrowserProviderDefinition {
|
|
|
210
243
|
syncCookies?: (request: ProviderSyncCookiesRequest, context: ProviderContext) => Promise<unknown>;
|
|
211
244
|
getAccess?: (request: ProviderInstanceRequest, context: ProviderContext) => Promise<ProviderAccess>;
|
|
212
245
|
configure?: (options: JsonObject, context: ProviderContext) => Promise<unknown>;
|
|
246
|
+
getLaunchForm?: (request: ProviderLaunchFormRequest, context: ProviderContext) => Promise<ProviderLaunchForm>;
|
|
213
247
|
handleAction?: (context: ProviderActionContext) => Promise<unknown>;
|
|
214
248
|
}
|
|
215
249
|
export interface ProviderRuntime {
|
|
@@ -223,6 +257,7 @@ export interface ProviderRuntime {
|
|
|
223
257
|
syncCookies(request: ProviderSyncCookiesRequest, context?: ProviderContext): Promise<unknown>;
|
|
224
258
|
getAccess(request: ProviderInstanceRequest, context?: ProviderContext): Promise<ProviderAccess>;
|
|
225
259
|
configure(options: JsonObject, context?: ProviderContext): Promise<unknown>;
|
|
260
|
+
getLaunchForm(request: ProviderLaunchFormRequest, context?: ProviderContext): Promise<ProviderLaunchForm>;
|
|
226
261
|
invokeAction(context: ProviderActionContext): Promise<unknown>;
|
|
227
262
|
}
|
|
228
263
|
export interface ProviderServerOptions {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearl/provider",
|
|
3
|
-
"version": "2.20.
|
|
3
|
+
"version": "2.20.1",
|
|
4
4
|
"description": "Unified contracts and runtime utilities for built-in and external Mearl browser providers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"registry": "https://registry.npmjs.org"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@mearl/daemon-core": "2.20.
|
|
38
|
+
"@mearl/daemon-core": "2.20.1"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^24.9.1",
|