@modelprofile.com/flexharness 10.3.0 → 10.4.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.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts_browser/classes.flexprovider.d.ts +9 -0
- package/dist_ts_browser/classes.flexprovider.js +135 -0
- package/dist_ts_browser/index.d.ts +3 -0
- package/dist_ts_browser/index.js +3 -0
- package/dist_ts_browser/interfaces.d.ts +8 -0
- package/dist_ts_browser/interfaces.js +2 -0
- package/dist_ts_browser/plugins.d.ts +7 -0
- package/dist_ts_browser/plugins.js +5 -0
- package/dist_ts_browser/tool.context.d.ts +2 -0
- package/dist_ts_browser/tool.context.js +61 -0
- package/package.json +13 -3
- package/readme.md +7 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/readme.md +7 -0
- package/ts_browser/classes.flexprovider.ts +140 -0
- package/ts_browser/index.ts +6 -0
- package/ts_browser/interfaces.ts +12 -0
- package/ts_browser/plugins.ts +23 -0
- package/ts_browser/readme.md +32 -0
- package/ts_browser/tool.context.ts +67 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@modelprofile.com/flexharness',
|
|
6
|
-
version: '10.
|
|
6
|
+
version: '10.4.0',
|
|
7
7
|
description: 'The FlexHarness toolbox source repository and shared dependency catalog.'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSwrQkFBK0I7SUFDckMsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLDBFQUEwRTtDQUN4RixDQUFBIn0=
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import type { IBrowserRuntimeFlexToolProviderOptions } from './interfaces.js';
|
|
3
|
+
export declare class BrowserRuntimeFlexToolProvider<TScope> implements plugins.IFlexToolProvider<TScope> {
|
|
4
|
+
private readonly client;
|
|
5
|
+
private readonly resolveCapability;
|
|
6
|
+
private state;
|
|
7
|
+
constructor(options: IBrowserRuntimeFlexToolProviderOptions<TScope>);
|
|
8
|
+
provideTools(context: plugins.IFlexToolProviderContext<TScope>): Promise<plugins.IFlexToolHandle>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { createFramedToolBrowserContext } from './tool.context.js';
|
|
3
|
+
const browserActions = [
|
|
4
|
+
'navigate',
|
|
5
|
+
'snapshot',
|
|
6
|
+
'screenshot',
|
|
7
|
+
'click',
|
|
8
|
+
'fill',
|
|
9
|
+
'press',
|
|
10
|
+
];
|
|
11
|
+
const boundedIdentifier = (minimum, maximum) => plugins.z.string().trim().min(minimum).max(maximum);
|
|
12
|
+
const runBindingSchema = plugins.z.object({
|
|
13
|
+
scopeId: boundedIdentifier(1, 128),
|
|
14
|
+
sessionId: boundedIdentifier(1, 128),
|
|
15
|
+
runId: boundedIdentifier(1, 256),
|
|
16
|
+
});
|
|
17
|
+
const capabilitySchema = plugins.z.strictObject({
|
|
18
|
+
capabilityToken: boundedIdentifier(16, 512),
|
|
19
|
+
});
|
|
20
|
+
export class BrowserRuntimeFlexToolProvider {
|
|
21
|
+
client;
|
|
22
|
+
resolveCapability;
|
|
23
|
+
state = 'unused';
|
|
24
|
+
constructor(options) {
|
|
25
|
+
if (!options || !options.client || typeof options.resolveCapability !== 'function') {
|
|
26
|
+
throw new plugins.BrowserRuntimeError('INVALID_INPUT');
|
|
27
|
+
}
|
|
28
|
+
this.client = options.client;
|
|
29
|
+
this.resolveCapability = options.resolveCapability;
|
|
30
|
+
}
|
|
31
|
+
async provideTools(context) {
|
|
32
|
+
if (this.state !== 'unused')
|
|
33
|
+
throw new plugins.BrowserRuntimeError('BUSY');
|
|
34
|
+
this.state = 'provisioning';
|
|
35
|
+
try {
|
|
36
|
+
const binding = runBindingSchema.safeParse(context);
|
|
37
|
+
if (!binding.success)
|
|
38
|
+
throw new plugins.BrowserRuntimeError('INVALID_INPUT');
|
|
39
|
+
if (this.client.scopeId !== binding.data.scopeId
|
|
40
|
+
|| this.client.sessionId.harnessId !== 'flex'
|
|
41
|
+
|| this.client.sessionId.nativeId !== binding.data.sessionId
|
|
42
|
+
|| this.client.runId !== binding.data.runId)
|
|
43
|
+
throw new plugins.BrowserRuntimeError('CAPABILITY_INVALID');
|
|
44
|
+
context.signal.throwIfAborted();
|
|
45
|
+
const capability = capabilitySchema.safeParse(await this.resolveCapability(context));
|
|
46
|
+
if (!capability.success)
|
|
47
|
+
throw new plugins.BrowserRuntimeError('INVALID_INPUT');
|
|
48
|
+
context.signal.throwIfAborted();
|
|
49
|
+
await this.client.acquire(capability.data.capabilityToken, context.signal);
|
|
50
|
+
context.signal.throwIfAborted();
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
this.state = 'closed';
|
|
54
|
+
const cleanupErrors = [];
|
|
55
|
+
await this.client.release().catch((cleanupError) => cleanupErrors.push(cleanupError));
|
|
56
|
+
await this.client.close().catch((cleanupError) => cleanupErrors.push(cleanupError));
|
|
57
|
+
if (cleanupErrors.length > 0) {
|
|
58
|
+
throw new AggregateError([error, ...cleanupErrors], 'Browser tool provisioning cleanup failed.');
|
|
59
|
+
}
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
this.state = 'active';
|
|
63
|
+
const tools = plugins.createBrowserTools({
|
|
64
|
+
abortSignal: context.signal,
|
|
65
|
+
browser: createFramedToolBrowserContext(this.client, context.signal),
|
|
66
|
+
requestPermission: async (request) => {
|
|
67
|
+
const action = plugins.z.enum(browserActions).safeParse(request.metadata?.action);
|
|
68
|
+
if (!action.success)
|
|
69
|
+
throw new plugins.BrowserRuntimeError('INVALID_INPUT');
|
|
70
|
+
const toolCallId = request.toolCallId === undefined
|
|
71
|
+
? undefined
|
|
72
|
+
: boundedIdentifier(1, 128).safeParse(request.toolCallId);
|
|
73
|
+
if (toolCallId !== undefined && !toolCallId.success) {
|
|
74
|
+
throw new plugins.BrowserRuntimeError('INVALID_INPUT');
|
|
75
|
+
}
|
|
76
|
+
const signal = request.abortSignal
|
|
77
|
+
? AbortSignal.any([context.signal, request.abortSignal])
|
|
78
|
+
: context.signal;
|
|
79
|
+
signal.throwIfAborted();
|
|
80
|
+
const permission = context.requestPermission({
|
|
81
|
+
kind: `browser.${action.data}`,
|
|
82
|
+
description: `Allow the browser to perform the ${action.data} action.`,
|
|
83
|
+
...(toolCallId === undefined ? {} : { toolCallId: toolCallId.data }),
|
|
84
|
+
metadata: { action: action.data },
|
|
85
|
+
});
|
|
86
|
+
let onAbort;
|
|
87
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
88
|
+
onAbort = () => reject(new plugins.BrowserRuntimeError('ABORTED'));
|
|
89
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
90
|
+
if (signal.aborted)
|
|
91
|
+
onAbort();
|
|
92
|
+
});
|
|
93
|
+
try {
|
|
94
|
+
await Promise.race([permission, aborted]);
|
|
95
|
+
signal.throwIfAborted();
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
signal.removeEventListener('abort', onAbort);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
}, {
|
|
102
|
+
allowedActions: browserActions,
|
|
103
|
+
maxLines: 2000,
|
|
104
|
+
maxBytes: 128 * 1024,
|
|
105
|
+
});
|
|
106
|
+
let closePromise;
|
|
107
|
+
const close = () => {
|
|
108
|
+
if (closePromise)
|
|
109
|
+
return closePromise;
|
|
110
|
+
closePromise = (async () => {
|
|
111
|
+
context.signal.removeEventListener('abort', onRunAbort);
|
|
112
|
+
const cleanupErrors = [];
|
|
113
|
+
await this.client.release().catch((error) => cleanupErrors.push(error));
|
|
114
|
+
await this.client.close().catch((error) => cleanupErrors.push(error));
|
|
115
|
+
this.state = 'closed';
|
|
116
|
+
if (cleanupErrors.length === 1)
|
|
117
|
+
throw cleanupErrors[0];
|
|
118
|
+
if (cleanupErrors.length > 1) {
|
|
119
|
+
throw new AggregateError(cleanupErrors, 'Browser tool cleanup failed.');
|
|
120
|
+
}
|
|
121
|
+
})();
|
|
122
|
+
return closePromise;
|
|
123
|
+
};
|
|
124
|
+
const onRunAbort = () => {
|
|
125
|
+
void close().catch(() => undefined);
|
|
126
|
+
};
|
|
127
|
+
context.signal.addEventListener('abort', onRunAbort, { once: true });
|
|
128
|
+
if (context.signal.aborted) {
|
|
129
|
+
await close();
|
|
130
|
+
throw new plugins.BrowserRuntimeError('ABORTED');
|
|
131
|
+
}
|
|
132
|
+
return { tools, close };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5mbGV4cHJvdmlkZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c19icm93c2VyL2NsYXNzZXMuZmxleHByb3ZpZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBRXhDLE9BQU8sRUFBRSw4QkFBOEIsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBRW5FLE1BQU0sY0FBYyxHQUFHO0lBQ3JCLFVBQVU7SUFDVixVQUFVO0lBQ1YsWUFBWTtJQUNaLE9BQU87SUFDUCxNQUFNO0lBQ04sT0FBTztDQUNpRCxDQUFDO0FBRTNELE1BQU0saUJBQWlCLEdBQUcsQ0FBQyxPQUFlLEVBQUUsT0FBZSxFQUFFLEVBQUUsQ0FDN0QsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0FBRXRELE1BQU0sZ0JBQWdCLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDeEMsT0FBTyxFQUFFLGlCQUFpQixDQUFDLENBQUMsRUFBRSxHQUFHLENBQUM7SUFDbEMsU0FBUyxFQUFFLGlCQUFpQixDQUFDLENBQUMsRUFBRSxHQUFHLENBQUM7SUFDcEMsS0FBSyxFQUFFLGlCQUFpQixDQUFDLENBQUMsRUFBRSxHQUFHLENBQUM7Q0FDakMsQ0FBQyxDQUFDO0FBRUgsTUFBTSxnQkFBZ0IsR0FBRyxPQUFPLENBQUMsQ0FBQyxDQUFDLFlBQVksQ0FBQztJQUM5QyxlQUFlLEVBQUUsaUJBQWlCLENBQUMsRUFBRSxFQUFFLEdBQUcsQ0FBQztDQUM1QyxDQUFDLENBQUM7QUFFSCxNQUFNLE9BQU8sOEJBQThCO0lBRXhCLE1BQU0sQ0FBcUM7SUFDM0MsaUJBQWlCLENBQXNFO0lBQ2hHLEtBQUssR0FBb0QsUUFBUSxDQUFDO0lBRTFFLFlBQVksT0FBdUQ7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
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { BrowserRuntimeFlexToolProvider } from './classes.flexprovider.js';
|
|
2
|
+
export { createFramedToolBrowserContext } from './tool.context.js';
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c19icm93c2VyL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSw4QkFBOEIsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQzNFLE9BQU8sRUFBRSw4QkFBOEIsRUFBRSxNQUFNLG1CQUFtQixDQUFDIn0=
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type * as plugins from './plugins.js';
|
|
2
|
+
export interface IResolvedBrowserFlexCapability {
|
|
3
|
+
capabilityToken: string;
|
|
4
|
+
}
|
|
5
|
+
export interface IBrowserRuntimeFlexToolProviderOptions<TScope> {
|
|
6
|
+
client: plugins.BrowserRuntimeFramedClient;
|
|
7
|
+
resolveCapability(context: plugins.IFlexToolProviderContext<TScope>): Promise<IResolvedBrowserFlexCapability> | IResolvedBrowserFlexCapability;
|
|
8
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BrowserRuntimeError, validateAgentAction } from '@push.rocks/smartbrowser/runtime';
|
|
3
|
+
import { createBrowserTools } from '@modelprofile.com/flexharness/tools';
|
|
4
|
+
export { z, BrowserRuntimeError, validateAgentAction, createBrowserTools };
|
|
5
|
+
export type { BrowserRuntimeFramedClient, TBrowserAgentAction, } from '@push.rocks/smartbrowser/runtime';
|
|
6
|
+
export type { IFlexToolHandle, IFlexToolProvider, IFlexToolProviderContext, } from '@modelprofile.com/flexharness';
|
|
7
|
+
export type { IBrowserToolInput, IToolBrowserContext, IToolBrowserRunOptions, TBrowserToolAction, } from '@modelprofile.com/flexharness/tools';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BrowserRuntimeError, validateAgentAction, } from '@push.rocks/smartbrowser/runtime';
|
|
3
|
+
import { createBrowserTools } from '@modelprofile.com/flexharness/tools';
|
|
4
|
+
export { z, BrowserRuntimeError, validateAgentAction, createBrowserTools };
|
|
5
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX2Jyb3dzZXIvcGx1Z2lucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sS0FBSyxDQUFDO0FBQ3hCLE9BQU8sRUFDTCxtQkFBbUIsRUFDbkIsbUJBQW1CLEdBQ3BCLE1BQU0sa0NBQWtDLENBQUM7QUFDMUMsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0scUNBQXFDLENBQUM7QUFFekUsT0FBTyxFQUFFLENBQUMsRUFBRSxtQkFBbUIsRUFBRSxtQkFBbUIsRUFBRSxrQkFBa0IsRUFBRSxDQUFDIn0=
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
const browserToolInputSchema = plugins.z.strictObject({
|
|
3
|
+
action: plugins.z.preprocess((value) => typeof value === 'string' ? value.trim() : value ?? 'snapshot', plugins.z.enum(['navigate', 'snapshot', 'screenshot', 'click', 'fill', 'press'])),
|
|
4
|
+
url: plugins.z.string().optional(),
|
|
5
|
+
selector: plugins.z.string().optional(),
|
|
6
|
+
text: plugins.z.string().optional(),
|
|
7
|
+
timeoutMs: plugins.z.number().int().min(100).max(120_000).optional(),
|
|
8
|
+
});
|
|
9
|
+
export function createFramedToolBrowserContext(client, runSignal) {
|
|
10
|
+
return {
|
|
11
|
+
execute: async (input, options) => {
|
|
12
|
+
const parsed = browserToolInputSchema.safeParse(input);
|
|
13
|
+
if (!parsed.success)
|
|
14
|
+
throw new plugins.BrowserRuntimeError('INVALID_INPUT');
|
|
15
|
+
const { action, url, selector, text, timeoutMs } = parsed.data;
|
|
16
|
+
let runtimeAction;
|
|
17
|
+
switch (action) {
|
|
18
|
+
case 'navigate':
|
|
19
|
+
runtimeAction = plugins.validateAgentAction({
|
|
20
|
+
action,
|
|
21
|
+
url: url ?? '',
|
|
22
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
23
|
+
});
|
|
24
|
+
break;
|
|
25
|
+
case 'snapshot':
|
|
26
|
+
case 'screenshot':
|
|
27
|
+
runtimeAction = { action };
|
|
28
|
+
break;
|
|
29
|
+
case 'click':
|
|
30
|
+
runtimeAction = plugins.validateAgentAction({
|
|
31
|
+
action,
|
|
32
|
+
selector: selector ?? '',
|
|
33
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
34
|
+
});
|
|
35
|
+
break;
|
|
36
|
+
case 'fill':
|
|
37
|
+
runtimeAction = plugins.validateAgentAction({
|
|
38
|
+
action,
|
|
39
|
+
selector: selector ?? '',
|
|
40
|
+
text: text ?? '',
|
|
41
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
42
|
+
});
|
|
43
|
+
break;
|
|
44
|
+
case 'press':
|
|
45
|
+
runtimeAction = plugins.validateAgentAction({
|
|
46
|
+
action,
|
|
47
|
+
selector: selector ?? '',
|
|
48
|
+
key: text ?? '',
|
|
49
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
50
|
+
});
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
const signal = options?.abortSignal
|
|
54
|
+
? AbortSignal.any([runSignal, options.abortSignal])
|
|
55
|
+
: runSignal;
|
|
56
|
+
signal.throwIfAborted();
|
|
57
|
+
return client.executeAgentAction(runtimeAction, { signal });
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9vbC5jb250ZXh0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHNfYnJvd3Nlci90b29sLmNvbnRleHQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFFeEMsTUFBTSxzQkFBc0IsR0FBRyxPQUFPLENBQUMsQ0FBQyxDQUFDLFlBQVksQ0FBQztJQUNwRCxNQUFNLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQzFCLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxPQUFPLEtBQUssS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsS0FBSyxJQUFJLFVBQVUsRUFDekUsT0FBTyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxVQUFVLEVBQUUsVUFBVSxFQUFFLFlBQVksRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQ2pGO0lBQ0QsR0FBRyxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFO0lBQ2xDLFFBQVEsRUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUN2QyxJQUFJLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLEVBQUU7SUFDbkMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsR0FBRyxFQUFFLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxRQUFRLEVBQUU7Q0FDckUsQ0FBQyxDQUFDO0FBRUgsTUFBTSxVQUFVLDhCQUE4QixDQUM1QyxNQUEwQyxFQUMxQyxTQUFzQjtJQUV0QixPQUFPO1FBQ0wsT0FBTyxFQUFFLEtBQUssRUFBRSxLQUFnQyxFQUFFLE9BQXdD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
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"author": "Task Venture Capital GmbH",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"name": "@modelprofile.com/flexharness",
|
|
5
|
-
"version": "10.
|
|
5
|
+
"version": "10.4.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"description": "Managed agent sessions with tools, MCP, chat, OCR, documents and browser control through focused subpath imports.",
|
|
8
8
|
"main": "./dist_ts/index.js",
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"types": "./dist_ts_tools_node/index.d.ts",
|
|
25
25
|
"import": "./dist_ts_tools_node/index.js"
|
|
26
26
|
},
|
|
27
|
+
"./browser": {
|
|
28
|
+
"types": "./dist_ts_browser/index.d.ts",
|
|
29
|
+
"import": "./dist_ts_browser/index.js"
|
|
30
|
+
},
|
|
27
31
|
"./compaction": {
|
|
28
32
|
"types": "./dist_ts_compaction/index.d.ts",
|
|
29
33
|
"import": "./dist_ts_compaction/index.js"
|
|
@@ -70,14 +74,15 @@
|
|
|
70
74
|
}
|
|
71
75
|
},
|
|
72
76
|
"dependencies": {
|
|
73
|
-
"@modelprofile.com/flexharness-agent": "10.
|
|
74
|
-
"@modelprofile.com/flexharness-models": "10.
|
|
77
|
+
"@modelprofile.com/flexharness-agent": "10.4.0",
|
|
78
|
+
"@modelprofile.com/flexharness-models": "10.4.0",
|
|
75
79
|
"@push.rocks/smartrequest": "^5.5.1",
|
|
76
80
|
"ai": "^7.0.111",
|
|
77
81
|
"zod": "^4.6.5"
|
|
78
82
|
},
|
|
79
83
|
"peerDependencies": {
|
|
80
84
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
85
|
+
"@push.rocks/smartbrowser": "^5.0.0",
|
|
81
86
|
"@push.rocks/smartpdf": "^6.0.0",
|
|
82
87
|
"sharp": "^0.35.3",
|
|
83
88
|
"puppeteer": "^24.42.0",
|
|
@@ -93,6 +98,9 @@
|
|
|
93
98
|
"@modelcontextprotocol/sdk": {
|
|
94
99
|
"optional": true
|
|
95
100
|
},
|
|
101
|
+
"@push.rocks/smartbrowser": {
|
|
102
|
+
"optional": true
|
|
103
|
+
},
|
|
96
104
|
"@push.rocks/smartpdf": {
|
|
97
105
|
"optional": true
|
|
98
106
|
},
|
|
@@ -139,6 +147,8 @@
|
|
|
139
147
|
"dist_ts_tools",
|
|
140
148
|
"ts_tools_node",
|
|
141
149
|
"dist_ts_tools_node",
|
|
150
|
+
"ts_browser",
|
|
151
|
+
"dist_ts_browser",
|
|
142
152
|
"ts_compaction",
|
|
143
153
|
"dist_ts_compaction",
|
|
144
154
|
"ts_mcp",
|
package/readme.md
CHANGED
|
@@ -55,6 +55,7 @@ or provider SDKs.
|
|
|
55
55
|
| --- | --- | --- |
|
|
56
56
|
| `/tools` | Host-supplied execution contexts, HTTP and JSON tools | None |
|
|
57
57
|
| `/tools/node` | Local filesystem and process tools, file-backed job adapters | None |
|
|
58
|
+
| `/browser` | Run-scoped SmartBrowser framed-client adapter for Flex tool providers | `@push.rocks/smartbrowser` |
|
|
58
59
|
| `/compaction` | Model-driven conversation compaction | None |
|
|
59
60
|
| `/media` | Vision recipes using an injected model | None |
|
|
60
61
|
| `/ocr` | `ImageOcr`, OCR contracts and Mistral transport | None |
|
|
@@ -75,6 +76,12 @@ only for the entrypoints your project imports, for example:
|
|
|
75
76
|
pnpm add @modelprofile.com/flexharness lit
|
|
76
77
|
```
|
|
77
78
|
|
|
79
|
+
For a managed SmartBrowser resource, install its optional browser peer:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
pnpm add @modelprofile.com/flexharness @push.rocks/smartbrowser
|
|
83
|
+
```
|
|
84
|
+
|
|
78
85
|
```typescript
|
|
79
86
|
import { ChatSession } from '@modelprofile.com/flexharness/chat';
|
|
80
87
|
import '@modelprofile.com/flexharness/chat/web';
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/readme.md
CHANGED
|
@@ -55,6 +55,7 @@ or provider SDKs.
|
|
|
55
55
|
| --- | --- | --- |
|
|
56
56
|
| `/tools` | Host-supplied execution contexts, HTTP and JSON tools | None |
|
|
57
57
|
| `/tools/node` | Local filesystem and process tools, file-backed job adapters | None |
|
|
58
|
+
| `/browser` | Run-scoped SmartBrowser framed-client adapter for Flex tool providers | `@push.rocks/smartbrowser` |
|
|
58
59
|
| `/compaction` | Model-driven conversation compaction | None |
|
|
59
60
|
| `/media` | Vision recipes using an injected model | None |
|
|
60
61
|
| `/ocr` | `ImageOcr`, OCR contracts and Mistral transport | None |
|
|
@@ -75,6 +76,12 @@ only for the entrypoints your project imports, for example:
|
|
|
75
76
|
pnpm add @modelprofile.com/flexharness lit
|
|
76
77
|
```
|
|
77
78
|
|
|
79
|
+
For a managed SmartBrowser resource, install its optional browser peer:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
pnpm add @modelprofile.com/flexharness @push.rocks/smartbrowser
|
|
83
|
+
```
|
|
84
|
+
|
|
78
85
|
```typescript
|
|
79
86
|
import { ChatSession } from '@modelprofile.com/flexharness/chat';
|
|
80
87
|
import '@modelprofile.com/flexharness/chat/web';
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import type { IBrowserRuntimeFlexToolProviderOptions } from './interfaces.js';
|
|
3
|
+
import { createFramedToolBrowserContext } from './tool.context.js';
|
|
4
|
+
|
|
5
|
+
const browserActions = [
|
|
6
|
+
'navigate',
|
|
7
|
+
'snapshot',
|
|
8
|
+
'screenshot',
|
|
9
|
+
'click',
|
|
10
|
+
'fill',
|
|
11
|
+
'press',
|
|
12
|
+
] as const satisfies readonly plugins.TBrowserToolAction[];
|
|
13
|
+
|
|
14
|
+
const boundedIdentifier = (minimum: number, maximum: number) =>
|
|
15
|
+
plugins.z.string().trim().min(minimum).max(maximum);
|
|
16
|
+
|
|
17
|
+
const runBindingSchema = plugins.z.object({
|
|
18
|
+
scopeId: boundedIdentifier(1, 128),
|
|
19
|
+
sessionId: boundedIdentifier(1, 128),
|
|
20
|
+
runId: boundedIdentifier(1, 256),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const capabilitySchema = plugins.z.strictObject({
|
|
24
|
+
capabilityToken: boundedIdentifier(16, 512),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export class BrowserRuntimeFlexToolProvider<TScope>
|
|
28
|
+
implements plugins.IFlexToolProvider<TScope> {
|
|
29
|
+
private readonly client: plugins.BrowserRuntimeFramedClient;
|
|
30
|
+
private readonly resolveCapability: IBrowserRuntimeFlexToolProviderOptions<TScope>['resolveCapability'];
|
|
31
|
+
private state: 'unused' | 'provisioning' | 'active' | 'closed' = 'unused';
|
|
32
|
+
|
|
33
|
+
constructor(options: IBrowserRuntimeFlexToolProviderOptions<TScope>) {
|
|
34
|
+
if (!options || !options.client || typeof options.resolveCapability !== 'function') {
|
|
35
|
+
throw new plugins.BrowserRuntimeError('INVALID_INPUT');
|
|
36
|
+
}
|
|
37
|
+
this.client = options.client;
|
|
38
|
+
this.resolveCapability = options.resolveCapability;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public async provideTools(
|
|
42
|
+
context: plugins.IFlexToolProviderContext<TScope>,
|
|
43
|
+
): Promise<plugins.IFlexToolHandle> {
|
|
44
|
+
if (this.state !== 'unused') throw new plugins.BrowserRuntimeError('BUSY');
|
|
45
|
+
this.state = 'provisioning';
|
|
46
|
+
try {
|
|
47
|
+
const binding = runBindingSchema.safeParse(context);
|
|
48
|
+
if (!binding.success) throw new plugins.BrowserRuntimeError('INVALID_INPUT');
|
|
49
|
+
if (
|
|
50
|
+
this.client.scopeId !== binding.data.scopeId
|
|
51
|
+
|| this.client.sessionId.harnessId !== 'flex'
|
|
52
|
+
|| this.client.sessionId.nativeId !== binding.data.sessionId
|
|
53
|
+
|| this.client.runId !== binding.data.runId
|
|
54
|
+
) throw new plugins.BrowserRuntimeError('CAPABILITY_INVALID');
|
|
55
|
+
context.signal.throwIfAborted();
|
|
56
|
+
const capability = capabilitySchema.safeParse(await this.resolveCapability(context));
|
|
57
|
+
if (!capability.success) throw new plugins.BrowserRuntimeError('INVALID_INPUT');
|
|
58
|
+
context.signal.throwIfAborted();
|
|
59
|
+
await this.client.acquire(capability.data.capabilityToken, context.signal);
|
|
60
|
+
context.signal.throwIfAborted();
|
|
61
|
+
} catch (error) {
|
|
62
|
+
this.state = 'closed';
|
|
63
|
+
const cleanupErrors: unknown[] = [];
|
|
64
|
+
await this.client.release().catch((cleanupError: unknown) => cleanupErrors.push(cleanupError));
|
|
65
|
+
await this.client.close().catch((cleanupError: unknown) => cleanupErrors.push(cleanupError));
|
|
66
|
+
if (cleanupErrors.length > 0) {
|
|
67
|
+
throw new AggregateError([error, ...cleanupErrors], 'Browser tool provisioning cleanup failed.');
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
this.state = 'active';
|
|
72
|
+
|
|
73
|
+
const tools = plugins.createBrowserTools({
|
|
74
|
+
abortSignal: context.signal,
|
|
75
|
+
browser: createFramedToolBrowserContext(this.client, context.signal),
|
|
76
|
+
requestPermission: async (request) => {
|
|
77
|
+
const action = plugins.z.enum(browserActions).safeParse(request.metadata?.action);
|
|
78
|
+
if (!action.success) throw new plugins.BrowserRuntimeError('INVALID_INPUT');
|
|
79
|
+
const toolCallId = request.toolCallId === undefined
|
|
80
|
+
? undefined
|
|
81
|
+
: boundedIdentifier(1, 128).safeParse(request.toolCallId);
|
|
82
|
+
if (toolCallId !== undefined && !toolCallId.success) {
|
|
83
|
+
throw new plugins.BrowserRuntimeError('INVALID_INPUT');
|
|
84
|
+
}
|
|
85
|
+
const signal = request.abortSignal
|
|
86
|
+
? AbortSignal.any([context.signal, request.abortSignal])
|
|
87
|
+
: context.signal;
|
|
88
|
+
signal.throwIfAborted();
|
|
89
|
+
const permission = context.requestPermission({
|
|
90
|
+
kind: `browser.${action.data}`,
|
|
91
|
+
description: `Allow the browser to perform the ${action.data} action.`,
|
|
92
|
+
...(toolCallId === undefined ? {} : { toolCallId: toolCallId.data }),
|
|
93
|
+
metadata: { action: action.data },
|
|
94
|
+
});
|
|
95
|
+
let onAbort!: () => void;
|
|
96
|
+
const aborted = new Promise<never>((_resolve, reject) => {
|
|
97
|
+
onAbort = () => reject(new plugins.BrowserRuntimeError('ABORTED'));
|
|
98
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
99
|
+
if (signal.aborted) onAbort();
|
|
100
|
+
});
|
|
101
|
+
try {
|
|
102
|
+
await Promise.race([permission, aborted]);
|
|
103
|
+
signal.throwIfAborted();
|
|
104
|
+
} finally {
|
|
105
|
+
signal.removeEventListener('abort', onAbort);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
}, {
|
|
109
|
+
allowedActions: browserActions,
|
|
110
|
+
maxLines: 2000,
|
|
111
|
+
maxBytes: 128 * 1024,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
let closePromise: Promise<void> | undefined;
|
|
115
|
+
const close = (): Promise<void> => {
|
|
116
|
+
if (closePromise) return closePromise;
|
|
117
|
+
closePromise = (async () => {
|
|
118
|
+
context.signal.removeEventListener('abort', onRunAbort);
|
|
119
|
+
const cleanupErrors: unknown[] = [];
|
|
120
|
+
await this.client.release().catch((error: unknown) => cleanupErrors.push(error));
|
|
121
|
+
await this.client.close().catch((error: unknown) => cleanupErrors.push(error));
|
|
122
|
+
this.state = 'closed';
|
|
123
|
+
if (cleanupErrors.length === 1) throw cleanupErrors[0];
|
|
124
|
+
if (cleanupErrors.length > 1) {
|
|
125
|
+
throw new AggregateError(cleanupErrors, 'Browser tool cleanup failed.');
|
|
126
|
+
}
|
|
127
|
+
})();
|
|
128
|
+
return closePromise;
|
|
129
|
+
};
|
|
130
|
+
const onRunAbort = (): void => {
|
|
131
|
+
void close().catch(() => undefined);
|
|
132
|
+
};
|
|
133
|
+
context.signal.addEventListener('abort', onRunAbort, { once: true });
|
|
134
|
+
if (context.signal.aborted) {
|
|
135
|
+
await close();
|
|
136
|
+
throw new plugins.BrowserRuntimeError('ABORTED');
|
|
137
|
+
}
|
|
138
|
+
return { tools, close };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type * as plugins from './plugins.js';
|
|
2
|
+
|
|
3
|
+
export interface IResolvedBrowserFlexCapability {
|
|
4
|
+
capabilityToken: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface IBrowserRuntimeFlexToolProviderOptions<TScope> {
|
|
8
|
+
client: plugins.BrowserRuntimeFramedClient;
|
|
9
|
+
resolveCapability(
|
|
10
|
+
context: plugins.IFlexToolProviderContext<TScope>,
|
|
11
|
+
): Promise<IResolvedBrowserFlexCapability> | IResolvedBrowserFlexCapability;
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import {
|
|
3
|
+
BrowserRuntimeError,
|
|
4
|
+
validateAgentAction,
|
|
5
|
+
} from '@push.rocks/smartbrowser/runtime';
|
|
6
|
+
import { createBrowserTools } from '@modelprofile.com/flexharness/tools';
|
|
7
|
+
|
|
8
|
+
export { z, BrowserRuntimeError, validateAgentAction, createBrowserTools };
|
|
9
|
+
export type {
|
|
10
|
+
BrowserRuntimeFramedClient,
|
|
11
|
+
TBrowserAgentAction,
|
|
12
|
+
} from '@push.rocks/smartbrowser/runtime';
|
|
13
|
+
export type {
|
|
14
|
+
IFlexToolHandle,
|
|
15
|
+
IFlexToolProvider,
|
|
16
|
+
IFlexToolProviderContext,
|
|
17
|
+
} from '@modelprofile.com/flexharness';
|
|
18
|
+
export type {
|
|
19
|
+
IBrowserToolInput,
|
|
20
|
+
IToolBrowserContext,
|
|
21
|
+
IToolBrowserRunOptions,
|
|
22
|
+
TBrowserToolAction,
|
|
23
|
+
} from '@modelprofile.com/flexharness/tools';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# FlexHarness browser adapter
|
|
2
|
+
|
|
3
|
+
Install both packages before importing this optional entrypoint:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pnpm add @modelprofile.com/flexharness @push.rocks/smartbrowser
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Import `BrowserRuntimeFlexToolProvider` from `@modelprofile.com/flexharness/browser` and `BrowserRuntimeFramedClient` from `@push.rocks/smartbrowser/runtime`. Create a fresh client and provider for each Flex run. The host supplies the trusted framed streams and the complete SmartBrowser binding when constructing the client; the provider resolves only the capability token.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { BrowserRuntimeFlexToolProvider } from '@modelprofile.com/flexharness/browser';
|
|
13
|
+
import { BrowserRuntimeFramedClient } from '@push.rocks/smartbrowser/runtime';
|
|
14
|
+
|
|
15
|
+
const client = new BrowserRuntimeFramedClient({
|
|
16
|
+
...trustedBinding,
|
|
17
|
+
readable,
|
|
18
|
+
writable,
|
|
19
|
+
});
|
|
20
|
+
const provider = new BrowserRuntimeFlexToolProvider({
|
|
21
|
+
client,
|
|
22
|
+
resolveCapability: () => ({ capabilityToken }),
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`trustedBinding`, `readable`, `writable`, and `capabilityToken` are host-provided values. The binding contains the exact project, browser resource, attachment authority and revision, actor, peer, Flex scope, session, run, and channel identities. The provider checks the Flex run's scope, session, and run against the client before resolving or acquiring a capability. SmartBrowser's framed server validates the token against the complete trusted binding.
|
|
27
|
+
|
|
28
|
+
The provider exposes `navigate`, `snapshot`, `screenshot`, `click`, `fill`, and `press` through the existing `/tools` browser tool. Navigation and input actions request Flex permission as `browser.<action>` with the tool call ID. Snapshot and screenshot are observations and do not request permission. Tool and run cancellation reach the framed client. The returned Flex tool handle releases the capability and closes the client; FlexHarness closes this handle after the run. A screenshot returns bounded artifact metadata, which the host can use for a separate authorized artifact read.
|
|
29
|
+
|
|
30
|
+
This entrypoint replaces `BrowserRuntimeFlexToolProvider` from `@modelprofile.com/browser-runtime`. Browser resource ownership, capabilities, framed transport, and artifact storage now come from `@push.rocks/smartbrowser/runtime`.
|
|
31
|
+
|
|
32
|
+
For hosts that used the old client's `client.asToolBrowserContext()`, import `createFramedToolBrowserContext` from this entrypoint and call `createFramedToolBrowserContext(client, runSignal)`. It returns the same `IToolBrowserContext` shape, maps the six supported actions to `executeAgentAction()`, and combines the run signal with each tool invocation's abort signal.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
|
|
3
|
+
const browserToolInputSchema = plugins.z.strictObject({
|
|
4
|
+
action: plugins.z.preprocess(
|
|
5
|
+
(value) => typeof value === 'string' ? value.trim() : value ?? 'snapshot',
|
|
6
|
+
plugins.z.enum(['navigate', 'snapshot', 'screenshot', 'click', 'fill', 'press']),
|
|
7
|
+
),
|
|
8
|
+
url: plugins.z.string().optional(),
|
|
9
|
+
selector: plugins.z.string().optional(),
|
|
10
|
+
text: plugins.z.string().optional(),
|
|
11
|
+
timeoutMs: plugins.z.number().int().min(100).max(120_000).optional(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export function createFramedToolBrowserContext(
|
|
15
|
+
client: plugins.BrowserRuntimeFramedClient,
|
|
16
|
+
runSignal: AbortSignal,
|
|
17
|
+
): plugins.IToolBrowserContext {
|
|
18
|
+
return {
|
|
19
|
+
execute: async (input: plugins.IBrowserToolInput, options?: plugins.IToolBrowserRunOptions) => {
|
|
20
|
+
const parsed = browserToolInputSchema.safeParse(input);
|
|
21
|
+
if (!parsed.success) throw new plugins.BrowserRuntimeError('INVALID_INPUT');
|
|
22
|
+
const { action, url, selector, text, timeoutMs } = parsed.data;
|
|
23
|
+
let runtimeAction: plugins.TBrowserAgentAction;
|
|
24
|
+
switch (action) {
|
|
25
|
+
case 'navigate':
|
|
26
|
+
runtimeAction = plugins.validateAgentAction({
|
|
27
|
+
action,
|
|
28
|
+
url: url ?? '',
|
|
29
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
30
|
+
});
|
|
31
|
+
break;
|
|
32
|
+
case 'snapshot':
|
|
33
|
+
case 'screenshot':
|
|
34
|
+
runtimeAction = { action };
|
|
35
|
+
break;
|
|
36
|
+
case 'click':
|
|
37
|
+
runtimeAction = plugins.validateAgentAction({
|
|
38
|
+
action,
|
|
39
|
+
selector: selector ?? '',
|
|
40
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
41
|
+
});
|
|
42
|
+
break;
|
|
43
|
+
case 'fill':
|
|
44
|
+
runtimeAction = plugins.validateAgentAction({
|
|
45
|
+
action,
|
|
46
|
+
selector: selector ?? '',
|
|
47
|
+
text: text ?? '',
|
|
48
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
49
|
+
});
|
|
50
|
+
break;
|
|
51
|
+
case 'press':
|
|
52
|
+
runtimeAction = plugins.validateAgentAction({
|
|
53
|
+
action,
|
|
54
|
+
selector: selector ?? '',
|
|
55
|
+
key: text ?? '',
|
|
56
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
57
|
+
});
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
const signal = options?.abortSignal
|
|
61
|
+
? AbortSignal.any([runSignal, options.abortSignal])
|
|
62
|
+
: runSignal;
|
|
63
|
+
signal.throwIfAborted();
|
|
64
|
+
return client.executeAgentAction(runtimeAction, { signal });
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|