alumnium 0.17.0 → 0.18.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/Alumni.d.ts +4 -0
- package/dist/Alumni.js +27 -9
- package/dist/Area.js +13 -7
- package/dist/Model.d.ts +1 -0
- package/dist/Model.js +3 -1
- package/dist/accessibility/AccessibilityElement.d.ts +4 -1
- package/dist/accessibility/AccessibilityElement.js +4 -1
- package/dist/accessibility/ChromiumAccessibilityTree.d.ts +3 -0
- package/dist/accessibility/ChromiumAccessibilityTree.js +86 -9
- package/dist/clients/ApiModels.d.ts +19 -0
- package/dist/clients/HttpClient.d.ts +12 -6
- package/dist/clients/HttpClient.js +35 -7
- package/dist/drivers/AppiumDriver.d.ts +7 -0
- package/dist/drivers/AppiumDriver.js +32 -8
- package/dist/drivers/BaseDriver.d.ts +15 -8
- package/dist/drivers/PlaywrightDriver.d.ts +28 -3
- package/dist/drivers/PlaywrightDriver.js +434 -34
- package/dist/drivers/SeleniumDriver.d.ts +14 -1
- package/dist/drivers/SeleniumDriver.js +198 -24
- package/dist/drivers/scripts/waiter.js +117 -14
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/result.d.ts +1 -0
- package/dist/tools/BaseTool.js +1 -1
- package/dist/tools/ClickTool.js +1 -2
- package/dist/tools/DragSliderTool.d.ts +14 -0
- package/dist/tools/DragSliderTool.js +25 -0
- package/dist/tools/PressKeyTool.js +1 -1
- package/dist/tools/PrintToPdfTool.d.ts +12 -0
- package/dist/tools/PrintToPdfTool.js +19 -0
- package/dist/tools/SwitchToNextTabTool.d.ts +6 -0
- package/dist/tools/SwitchToNextTabTool.js +7 -0
- package/dist/tools/SwitchToPreviousTabTool.d.ts +6 -0
- package/dist/tools/SwitchToPreviousTabTool.js +7 -0
- package/dist/tools/TypeTool.js +1 -1
- package/dist/tools/UploadTool.js +4 -1
- package/dist/tools/WaitForElementTool.d.ts +14 -0
- package/dist/tools/WaitForElementTool.js +33 -0
- package/dist/tools/WaitTool.d.ts +12 -0
- package/dist/tools/WaitTool.js +26 -0
- package/package.json +1 -1
- package/dist/tools/SelectTool.d.ts +0 -15
- package/dist/tools/SelectTool.js +0 -21
package/dist/Alumni.d.ts
CHANGED
|
@@ -13,6 +13,9 @@ export interface AlumniOptions {
|
|
|
13
13
|
url?: string;
|
|
14
14
|
model?: Model;
|
|
15
15
|
extraTools?: ToolClass[];
|
|
16
|
+
planner?: boolean;
|
|
17
|
+
changeAnalysis?: boolean;
|
|
18
|
+
excludedAttributes?: Set<string>;
|
|
16
19
|
}
|
|
17
20
|
export interface VisionOptions {
|
|
18
21
|
vision?: boolean;
|
|
@@ -24,6 +27,7 @@ export declare class Alumni {
|
|
|
24
27
|
cache: Cache;
|
|
25
28
|
private url;
|
|
26
29
|
private model;
|
|
30
|
+
private changeAnalysis;
|
|
27
31
|
constructor(driver: WebDriver | Page | Browser, options?: AlumniOptions);
|
|
28
32
|
quit(): Promise<void>;
|
|
29
33
|
do(goal: string): Promise<DoResult>;
|
package/dist/Alumni.js
CHANGED
|
@@ -20,11 +20,15 @@ import { BaseTool } from "./tools/BaseTool.js";
|
|
|
20
20
|
import { getLogger } from "./utils/logger.js";
|
|
21
21
|
import { retry } from "./utils/retry.js";
|
|
22
22
|
const logger = getLogger(["Alumni"]);
|
|
23
|
+
const changeAnalysis = (process.env.ALUMNIUM_CHANGE_ANALYSIS || "false").toLowerCase() === "true";
|
|
24
|
+
const planner = (process.env.ALUMNIUM_PLANNER || "true").toLowerCase() === "true";
|
|
25
|
+
const excludedAttributes = new Set((process.env.ALUMNIUM_EXCLUDE_ATTRIBUTES || "").split(",").filter(Boolean));
|
|
23
26
|
export class Alumni {
|
|
24
27
|
constructor(driver, options = {}) {
|
|
25
28
|
this.tools = {};
|
|
26
29
|
this.url = options.url || "http://localhost:8013";
|
|
27
30
|
this.model = options.model || Model.current;
|
|
31
|
+
this.changeAnalysis = options.changeAnalysis ?? changeAnalysis;
|
|
28
32
|
// Wrap driver or use directly if already wrapped
|
|
29
33
|
if (driver instanceof WebDriver) {
|
|
30
34
|
this.driver = new SeleniumDriver(driver);
|
|
@@ -47,7 +51,7 @@ export class Alumni {
|
|
|
47
51
|
this.tools[tool.name] = tool;
|
|
48
52
|
}
|
|
49
53
|
// Initialize HTTP client
|
|
50
|
-
this.client = new HttpClient(this.url, this.model, this.driver.platform, this.tools);
|
|
54
|
+
this.client = new HttpClient(this.url, this.model, this.driver.platform, this.tools, options.planner ?? planner, options.excludedAttributes ?? excludedAttributes);
|
|
51
55
|
this.cache = new Cache(this.client);
|
|
52
56
|
logger.info(`Using model: ${this.model.provider}/${this.model.name}`);
|
|
53
57
|
logger.info(`Using HTTP client with server: ${this.url}`);
|
|
@@ -57,8 +61,14 @@ export class Alumni {
|
|
|
57
61
|
await this.driver.quit();
|
|
58
62
|
}
|
|
59
63
|
async do(goal) {
|
|
64
|
+
const app = await this.driver.app();
|
|
60
65
|
const initialAccessibilityTree = await this.driver.getAccessibilityTree();
|
|
61
|
-
const
|
|
66
|
+
const beforeTree = this.changeAnalysis
|
|
67
|
+
? initialAccessibilityTree.toStr()
|
|
68
|
+
: null;
|
|
69
|
+
const beforeUrl = this.changeAnalysis ? await this.driver.url() : null;
|
|
70
|
+
const { explanation, steps } = await this.client.planActions(goal, initialAccessibilityTree.toStr(), app);
|
|
71
|
+
let finalExplanation = explanation;
|
|
62
72
|
const executedSteps = [];
|
|
63
73
|
for (let idx = 0; idx < steps.length; idx++) {
|
|
64
74
|
const step = steps[idx];
|
|
@@ -66,22 +76,30 @@ export class Alumni {
|
|
|
66
76
|
const accessibilityTree = idx === 0
|
|
67
77
|
? initialAccessibilityTree
|
|
68
78
|
: await this.driver.getAccessibilityTree();
|
|
69
|
-
const
|
|
79
|
+
const { explanation: actorExplanation, actions } = await this.client.executeAction(goal, step, accessibilityTree.toStr(), app);
|
|
80
|
+
// When planner is off, explanation is just the goal — replace with actor's reasoning.
|
|
81
|
+
if (finalExplanation === goal) {
|
|
82
|
+
finalExplanation = actorExplanation;
|
|
83
|
+
}
|
|
70
84
|
const calledTools = [];
|
|
71
|
-
for (const toolCall of
|
|
85
|
+
for (const toolCall of actions) {
|
|
72
86
|
const calledTool = await BaseTool.executeToolCall(toolCall, this.tools, this.driver);
|
|
73
87
|
calledTools.push(calledTool);
|
|
74
88
|
}
|
|
75
89
|
executedSteps.push({ name: step, tools: calledTools });
|
|
76
90
|
}
|
|
77
|
-
|
|
91
|
+
let changes = "";
|
|
92
|
+
if (this.changeAnalysis && executedSteps.length > 0) {
|
|
93
|
+
changes = await this.client.analyzeChanges(beforeTree, beforeUrl, (await this.driver.getAccessibilityTree()).toStr(), await this.driver.url());
|
|
94
|
+
}
|
|
95
|
+
return { explanation: finalExplanation, steps: executedSteps, changes };
|
|
78
96
|
}
|
|
79
97
|
async check(statement, options = {}) {
|
|
80
98
|
const screenshot = options.vision
|
|
81
99
|
? await this.driver.screenshot()
|
|
82
100
|
: undefined;
|
|
83
101
|
const accessibilityTree = await this.driver.getAccessibilityTree();
|
|
84
|
-
const [explanation, value] = await this.client.retrieve(`Is the following true or false - ${statement}`, accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot);
|
|
102
|
+
const [explanation, value] = await this.client.retrieve(`Is the following true or false - ${statement}`, accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot, await this.driver.app());
|
|
85
103
|
if (!value) {
|
|
86
104
|
throw new AssertionError(explanation);
|
|
87
105
|
}
|
|
@@ -92,17 +110,17 @@ export class Alumni {
|
|
|
92
110
|
? await this.driver.screenshot()
|
|
93
111
|
: undefined;
|
|
94
112
|
const accessibilityTree = await this.driver.getAccessibilityTree();
|
|
95
|
-
const [explanation, value] = await this.client.retrieve(data, accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot);
|
|
113
|
+
const [explanation, value] = await this.client.retrieve(data, accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot, await this.driver.app());
|
|
96
114
|
return value === null ? explanation : value;
|
|
97
115
|
}
|
|
98
116
|
async find(description) {
|
|
99
117
|
const accessibilityTree = await this.driver.getAccessibilityTree();
|
|
100
|
-
const response = await this.client.findElement(description, accessibilityTree.toStr());
|
|
118
|
+
const response = await this.client.findElement(description, accessibilityTree.toStr(), await this.driver.app());
|
|
101
119
|
return this.driver.findElement(response.id);
|
|
102
120
|
}
|
|
103
121
|
async area(description) {
|
|
104
122
|
const accessibilityTree = await this.driver.getAccessibilityTree();
|
|
105
|
-
const response = await this.client.findArea(description, accessibilityTree.toStr());
|
|
123
|
+
const response = await this.client.findArea(description, accessibilityTree.toStr(), await this.driver.app());
|
|
106
124
|
const scopedTree = accessibilityTree.scopeToArea(response.id);
|
|
107
125
|
return new Area(response.id, response.explanation, scopedTree, this.driver, this.tools, this.client);
|
|
108
126
|
}
|
package/dist/Area.js
CHANGED
|
@@ -20,24 +20,30 @@ export class Area {
|
|
|
20
20
|
this.client = client;
|
|
21
21
|
}
|
|
22
22
|
async do(goal) {
|
|
23
|
-
const
|
|
23
|
+
const app = await this.driver.app();
|
|
24
|
+
const { explanation, steps } = await this.client.planActions(goal, this.accessibilityTree.toStr(), app);
|
|
25
|
+
let finalExplanation = explanation;
|
|
24
26
|
const executedSteps = [];
|
|
25
27
|
for (const step of steps) {
|
|
26
|
-
const
|
|
28
|
+
const { explanation: actorExplanation, actions } = await this.client.executeAction(goal, step, this.accessibilityTree.toStr(), app);
|
|
29
|
+
// When planner is off, explanation is just the goal — replace with actor's reasoning.
|
|
30
|
+
if (finalExplanation === goal) {
|
|
31
|
+
finalExplanation = actorExplanation;
|
|
32
|
+
}
|
|
27
33
|
const calledTools = [];
|
|
28
|
-
for (const toolCall of
|
|
34
|
+
for (const toolCall of actions) {
|
|
29
35
|
const calledTool = await BaseTool.executeToolCall(toolCall, this.tools, this.driver);
|
|
30
36
|
calledTools.push(calledTool);
|
|
31
37
|
}
|
|
32
38
|
executedSteps.push({ name: step, tools: calledTools });
|
|
33
39
|
}
|
|
34
|
-
return { explanation, steps: executedSteps };
|
|
40
|
+
return { explanation: finalExplanation, steps: executedSteps, changes: "" };
|
|
35
41
|
}
|
|
36
42
|
async check(statement, options = {}) {
|
|
37
43
|
const screenshot = options.vision
|
|
38
44
|
? await this.driver.screenshot()
|
|
39
45
|
: undefined;
|
|
40
|
-
const [explanation, value] = await this.client.retrieve(`Is the following true or false - ${statement}`, this.accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot);
|
|
46
|
+
const [explanation, value] = await this.client.retrieve(`Is the following true or false - ${statement}`, this.accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot, await this.driver.app());
|
|
41
47
|
if (!value) {
|
|
42
48
|
throw new AssertionError(explanation);
|
|
43
49
|
}
|
|
@@ -47,11 +53,11 @@ export class Area {
|
|
|
47
53
|
const screenshot = options.vision
|
|
48
54
|
? await this.driver.screenshot()
|
|
49
55
|
: undefined;
|
|
50
|
-
const [explanation, value] = await this.client.retrieve(data, this.accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot);
|
|
56
|
+
const [explanation, value] = await this.client.retrieve(data, this.accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot, await this.driver.app());
|
|
51
57
|
return value === null ? explanation : value;
|
|
52
58
|
}
|
|
53
59
|
async find(description) {
|
|
54
|
-
const response = await this.client.findElement(description, this.accessibilityTree.toStr());
|
|
60
|
+
const response = await this.client.findElement(description, this.accessibilityTree.toStr(), await this.driver.app());
|
|
55
61
|
return this.driver.findElement(response.id);
|
|
56
62
|
}
|
|
57
63
|
}
|
package/dist/Model.d.ts
CHANGED
package/dist/Model.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export var Provider;
|
|
2
2
|
(function (Provider) {
|
|
3
|
+
Provider["AZURE_FOUNDRY"] = "azure_foundry";
|
|
3
4
|
Provider["AZURE_OPENAI"] = "azure_openai";
|
|
4
5
|
Provider["ANTHROPIC"] = "anthropic";
|
|
5
6
|
Provider["AWS_ANTHROPIC"] = "aws_anthropic";
|
|
@@ -15,13 +16,14 @@ export var Provider;
|
|
|
15
16
|
export class ModelName {
|
|
16
17
|
}
|
|
17
18
|
ModelName.DEFAULT = {
|
|
19
|
+
[Provider.AZURE_FOUNDRY]: "gpt-5-nano",
|
|
18
20
|
[Provider.AZURE_OPENAI]: "gpt-5-nano",
|
|
19
21
|
[Provider.ANTHROPIC]: "claude-haiku-4-5-20251001",
|
|
20
22
|
[Provider.AWS_ANTHROPIC]: "us.anthropic.claude-haiku-4-5-20251001-v1:0",
|
|
21
23
|
[Provider.AWS_META]: "us.meta.llama4-maverick-17b-instruct-v1:0",
|
|
22
24
|
[Provider.DEEPSEEK]: "deepseek-reasoner",
|
|
23
25
|
[Provider.GITHUB]: "gpt-4o-mini",
|
|
24
|
-
[Provider.GOOGLE]: "gemini-3-flash-preview",
|
|
26
|
+
[Provider.GOOGLE]: "gemini-3.1-flash-lite-preview",
|
|
25
27
|
[Provider.MISTRALAI]: "mistral-medium-2505",
|
|
26
28
|
[Provider.OLLAMA]: "mistral-small3.1",
|
|
27
29
|
[Provider.OPENAI]: "gpt-5-nano-2025-08-07",
|
|
@@ -10,5 +10,8 @@ export declare class AccessibilityElement {
|
|
|
10
10
|
androidText?: string;
|
|
11
11
|
androidContentDesc?: string;
|
|
12
12
|
androidBounds?: string;
|
|
13
|
-
|
|
13
|
+
frame?: object;
|
|
14
|
+
locatorInfo?: Record<string, unknown>;
|
|
15
|
+
frameChain?: number[];
|
|
16
|
+
constructor(id?: number, name?: string, label?: string, type?: string, value?: string, backendNodeId?: number, androidResourceId?: string, androidClass?: string, androidText?: string, androidContentDesc?: string, androidBounds?: string, frame?: object, locatorInfo?: Record<string, unknown>, frameChain?: number[]);
|
|
14
17
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export class AccessibilityElement {
|
|
2
|
-
constructor(id, name, label, type, value, backendNodeId, androidResourceId, androidClass, androidText, androidContentDesc, androidBounds) {
|
|
2
|
+
constructor(id, name, label, type, value, backendNodeId, androidResourceId, androidClass, androidText, androidContentDesc, androidBounds, frame, locatorInfo, frameChain) {
|
|
3
3
|
this.id = id;
|
|
4
4
|
this.backendNodeId = backendNodeId;
|
|
5
5
|
this.name = name;
|
|
@@ -11,5 +11,8 @@ export class AccessibilityElement {
|
|
|
11
11
|
this.androidText = androidText;
|
|
12
12
|
this.androidContentDesc = androidContentDesc;
|
|
13
13
|
this.androidBounds = androidBounds;
|
|
14
|
+
this.frame = frame;
|
|
15
|
+
this.locatorInfo = locatorInfo;
|
|
16
|
+
this.frameChain = frameChain;
|
|
14
17
|
}
|
|
15
18
|
}
|
|
@@ -4,11 +4,14 @@ export declare class ChromiumAccessibilityTree extends BaseAccessibilityTree {
|
|
|
4
4
|
private cdpResponse;
|
|
5
5
|
private nextRawId;
|
|
6
6
|
private raw;
|
|
7
|
+
private frameMap;
|
|
8
|
+
private frameChainMap;
|
|
7
9
|
constructor(cdpResponse: unknown);
|
|
8
10
|
private static fromXml;
|
|
9
11
|
toStr(): string;
|
|
10
12
|
private nodeToXml;
|
|
11
13
|
private elementToString;
|
|
14
|
+
private unescapeXml;
|
|
12
15
|
elementById(rawId: number): AccessibilityElement;
|
|
13
16
|
scopeToArea(rawId: number): ChromiumAccessibilityTree;
|
|
14
17
|
private findElementByRawId;
|
|
@@ -5,11 +5,16 @@ export class ChromiumAccessibilityTree extends BaseAccessibilityTree {
|
|
|
5
5
|
super();
|
|
6
6
|
this.nextRawId = 0;
|
|
7
7
|
this.raw = null;
|
|
8
|
+
this.frameMap = new Map();
|
|
9
|
+
this.frameChainMap = new Map(); // raw_id -> frame chain
|
|
8
10
|
this.cdpResponse = cdpResponse;
|
|
9
11
|
}
|
|
10
|
-
static fromXml(xmlString) {
|
|
12
|
+
static fromXml(xmlString, frameMap) {
|
|
11
13
|
const instance = new ChromiumAccessibilityTree({ nodes: [] });
|
|
12
14
|
instance.raw = xmlString;
|
|
15
|
+
if (frameMap) {
|
|
16
|
+
instance.frameMap = frameMap;
|
|
17
|
+
}
|
|
13
18
|
return instance;
|
|
14
19
|
}
|
|
15
20
|
toStr() {
|
|
@@ -17,18 +22,38 @@ export class ChromiumAccessibilityTree extends BaseAccessibilityTree {
|
|
|
17
22
|
return this.raw;
|
|
18
23
|
}
|
|
19
24
|
const nodes = this.cdpResponse.nodes;
|
|
25
|
+
if (!nodes || nodes.length === 0) {
|
|
26
|
+
this.raw = "";
|
|
27
|
+
return this.raw;
|
|
28
|
+
}
|
|
20
29
|
const nodeLookup = {};
|
|
21
30
|
for (const node of nodes) {
|
|
22
31
|
nodeLookup[node.nodeId] = node;
|
|
23
32
|
}
|
|
24
|
-
// Build
|
|
25
|
-
|
|
33
|
+
// Build mapping: backendDOMNodeId -> list of iframe child root nodes
|
|
34
|
+
// This allows us to inline iframe content inside their parent <Iframe> elements
|
|
35
|
+
const iframeChildren = new Map();
|
|
36
|
+
const trueRoots = [];
|
|
26
37
|
for (const node of nodes) {
|
|
27
38
|
if (!node.parentId) {
|
|
28
|
-
const
|
|
29
|
-
|
|
39
|
+
const parentIframeId = node._parent_iframe_backend_node_id;
|
|
40
|
+
if (parentIframeId !== undefined) {
|
|
41
|
+
if (!iframeChildren.has(parentIframeId)) {
|
|
42
|
+
iframeChildren.set(parentIframeId, []);
|
|
43
|
+
}
|
|
44
|
+
iframeChildren.get(parentIframeId).push(node);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
trueRoots.push(node);
|
|
48
|
+
}
|
|
30
49
|
}
|
|
31
50
|
}
|
|
51
|
+
// Build tree structure and convert to XML (only from true roots)
|
|
52
|
+
const rootNodes = [];
|
|
53
|
+
for (const node of trueRoots) {
|
|
54
|
+
const xmlNode = this.nodeToXml(node, nodeLookup, iframeChildren);
|
|
55
|
+
rootNodes.push(xmlNode);
|
|
56
|
+
}
|
|
32
57
|
// Combine all root nodes into a single XML string
|
|
33
58
|
let xmlString = "";
|
|
34
59
|
for (const root of rootNodes) {
|
|
@@ -37,7 +62,7 @@ export class ChromiumAccessibilityTree extends BaseAccessibilityTree {
|
|
|
37
62
|
this.raw = xmlString;
|
|
38
63
|
return this.raw;
|
|
39
64
|
}
|
|
40
|
-
nodeToXml(node, nodeLookup) {
|
|
65
|
+
nodeToXml(node, nodeLookup, iframeChildren) {
|
|
41
66
|
const role = node.role?.value || "unknown";
|
|
42
67
|
const elem = {
|
|
43
68
|
tag: role,
|
|
@@ -47,6 +72,24 @@ export class ChromiumAccessibilityTree extends BaseAccessibilityTree {
|
|
|
47
72
|
// Add our own sequential raw_id attribute
|
|
48
73
|
this.nextRawId++;
|
|
49
74
|
elem.attributes.raw_id = String(this.nextRawId);
|
|
75
|
+
// Store frame reference if present
|
|
76
|
+
if (node._frame) {
|
|
77
|
+
this.frameMap.set(this.nextRawId, node._frame);
|
|
78
|
+
}
|
|
79
|
+
// Store frame chain if present (for Selenium nested frame switching)
|
|
80
|
+
if (node._frame_chain) {
|
|
81
|
+
this.frameChainMap.set(this.nextRawId, node._frame_chain);
|
|
82
|
+
}
|
|
83
|
+
// Store Playwright node metadata
|
|
84
|
+
if (node._playwright_node) {
|
|
85
|
+
elem.attributes._playwright_node = "true";
|
|
86
|
+
}
|
|
87
|
+
if (node._locator_info) {
|
|
88
|
+
elem.attributes._locator_info = JSON.stringify(node._locator_info);
|
|
89
|
+
}
|
|
90
|
+
if (node._frame_url) {
|
|
91
|
+
elem.attributes._frame_url = node._frame_url;
|
|
92
|
+
}
|
|
50
93
|
// Add all node attributes as XML attributes
|
|
51
94
|
if (node.backendDOMNodeId !== undefined) {
|
|
52
95
|
elem.attributes.backendDOMNodeId = String(node.backendDOMNodeId);
|
|
@@ -84,11 +127,19 @@ export class ChromiumAccessibilityTree extends BaseAccessibilityTree {
|
|
|
84
127
|
if (node.childIds) {
|
|
85
128
|
for (const childId of node.childIds) {
|
|
86
129
|
if (childId in nodeLookup) {
|
|
87
|
-
const childElem = this.nodeToXml(nodeLookup[childId], nodeLookup);
|
|
130
|
+
const childElem = this.nodeToXml(nodeLookup[childId], nodeLookup, iframeChildren);
|
|
88
131
|
elem.children.push(childElem);
|
|
89
132
|
}
|
|
90
133
|
}
|
|
91
134
|
}
|
|
135
|
+
// Inline iframe content: if this element is an iframe, add its child trees
|
|
136
|
+
const backendNodeId = node.backendDOMNodeId;
|
|
137
|
+
if (backendNodeId !== undefined && iframeChildren.has(backendNodeId)) {
|
|
138
|
+
for (const childRoot of iframeChildren.get(backendNodeId)) {
|
|
139
|
+
const childElem = this.nodeToXml(childRoot, nodeLookup, iframeChildren);
|
|
140
|
+
elem.children.push(childElem);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
92
143
|
return elem;
|
|
93
144
|
}
|
|
94
145
|
elementToString(elem, indent) {
|
|
@@ -124,17 +175,39 @@ export class ChromiumAccessibilityTree extends BaseAccessibilityTree {
|
|
|
124
175
|
}
|
|
125
176
|
return result;
|
|
126
177
|
}
|
|
178
|
+
unescapeXml(value) {
|
|
179
|
+
return value
|
|
180
|
+
.replace(/'/g, "'")
|
|
181
|
+
.replace(/"/g, '"')
|
|
182
|
+
.replace(/>/g, ">")
|
|
183
|
+
.replace(/</g, "<")
|
|
184
|
+
.replace(/&/g, "&");
|
|
185
|
+
}
|
|
127
186
|
elementById(rawId) {
|
|
128
187
|
const rawXml = this.toStr();
|
|
129
188
|
const element = this.findElementByRawId(rawXml, rawId);
|
|
130
189
|
if (!element) {
|
|
131
190
|
throw new Error(`No element with raw_id=${rawId} found`);
|
|
132
191
|
}
|
|
192
|
+
// Handle Playwright nodes (cross-origin iframes)
|
|
193
|
+
if (element.attributes._playwright_node === "true") {
|
|
194
|
+
// Synthetic frame node
|
|
195
|
+
if (element.attributes._frame_url) {
|
|
196
|
+
return new AccessibilityElement(undefined, undefined, undefined, element.tag, undefined, undefined, undefined, undefined, undefined, undefined, undefined, this.frameMap.get(rawId), { _synthetic_frame: true, _frame_url: element.attributes._frame_url });
|
|
197
|
+
}
|
|
198
|
+
// Regular Playwright node with locator info
|
|
199
|
+
const locatorInfo = element.attributes
|
|
200
|
+
._locator_info
|
|
201
|
+
? JSON.parse(element.attributes._locator_info)
|
|
202
|
+
: {};
|
|
203
|
+
return new AccessibilityElement(undefined, undefined, undefined, element.tag, undefined, undefined, undefined, undefined, undefined, undefined, undefined, this.frameMap.get(rawId), locatorInfo);
|
|
204
|
+
}
|
|
205
|
+
// Existing CDP node logic
|
|
133
206
|
const backendNodeIdStr = element.attributes.backendDOMNodeId;
|
|
134
207
|
if (!backendNodeIdStr) {
|
|
135
208
|
throw new Error(`Element with raw_id=${rawId} has no backendDOMNodeId attribute`);
|
|
136
209
|
}
|
|
137
|
-
return new AccessibilityElement(undefined, undefined, undefined, element.tag, undefined, parseInt(backendNodeIdStr));
|
|
210
|
+
return new AccessibilityElement(undefined, undefined, undefined, element.tag, undefined, parseInt(backendNodeIdStr), undefined, undefined, undefined, undefined, undefined, this.frameMap.get(rawId), undefined, this.frameChainMap.get(rawId));
|
|
138
211
|
}
|
|
139
212
|
scopeToArea(rawId) {
|
|
140
213
|
const rawXml = this.toStr();
|
|
@@ -143,7 +216,7 @@ export class ChromiumAccessibilityTree extends BaseAccessibilityTree {
|
|
|
143
216
|
return this;
|
|
144
217
|
}
|
|
145
218
|
const scopedXml = this.elementToString(element, 0);
|
|
146
|
-
return ChromiumAccessibilityTree.fromXml(scopedXml);
|
|
219
|
+
return ChromiumAccessibilityTree.fromXml(scopedXml, this.frameMap);
|
|
147
220
|
}
|
|
148
221
|
findElementByRawId(xmlString, targetRawId) {
|
|
149
222
|
// Parse the XML string to find the element
|
|
@@ -195,6 +268,10 @@ export class ChromiumAccessibilityTree extends BaseAccessibilityTree {
|
|
|
195
268
|
while ((attrMatch = attrRegex.exec(attrsString)) !== null) {
|
|
196
269
|
attributes[attrMatch[1]] = attrMatch[2];
|
|
197
270
|
}
|
|
271
|
+
// Unescape XML entities in attribute values
|
|
272
|
+
for (const key of Object.keys(attributes)) {
|
|
273
|
+
attributes[key] = this.unescapeXml(attributes[key]);
|
|
274
|
+
}
|
|
198
275
|
const elem = {
|
|
199
276
|
tag: tagName,
|
|
200
277
|
attributes,
|
|
@@ -11,6 +11,8 @@ export interface SessionRequest {
|
|
|
11
11
|
tools: {
|
|
12
12
|
[key: string]: any;
|
|
13
13
|
}[];
|
|
14
|
+
planner?: boolean;
|
|
15
|
+
excluded_attributes?: string[];
|
|
14
16
|
}
|
|
15
17
|
export interface SessionResponse {
|
|
16
18
|
session_id: string;
|
|
@@ -20,6 +22,7 @@ export interface PlanRequest {
|
|
|
20
22
|
accessibility_tree: string;
|
|
21
23
|
url?: string;
|
|
22
24
|
title?: string;
|
|
25
|
+
app?: string;
|
|
23
26
|
}
|
|
24
27
|
export interface PlanResponse {
|
|
25
28
|
explanation: string;
|
|
@@ -29,8 +32,10 @@ export interface StepRequest {
|
|
|
29
32
|
goal: string;
|
|
30
33
|
step: string;
|
|
31
34
|
accessibility_tree: string;
|
|
35
|
+
app?: string;
|
|
32
36
|
}
|
|
33
37
|
export interface StepResponse {
|
|
38
|
+
explanation: string;
|
|
34
39
|
actions: {
|
|
35
40
|
[key: string]: any;
|
|
36
41
|
}[];
|
|
@@ -41,6 +46,7 @@ export interface StatementRequest {
|
|
|
41
46
|
url?: string;
|
|
42
47
|
title?: string;
|
|
43
48
|
screenshot?: string | null;
|
|
49
|
+
app?: string;
|
|
44
50
|
}
|
|
45
51
|
export interface StatementResponse {
|
|
46
52
|
result: string | string[];
|
|
@@ -49,6 +55,7 @@ export interface StatementResponse {
|
|
|
49
55
|
export interface AreaRequest {
|
|
50
56
|
description: string;
|
|
51
57
|
accessibility_tree: string;
|
|
58
|
+
app?: string;
|
|
52
59
|
}
|
|
53
60
|
export interface AreaResponse {
|
|
54
61
|
id: number;
|
|
@@ -57,6 +64,7 @@ export interface AreaResponse {
|
|
|
57
64
|
export interface FindRequest {
|
|
58
65
|
description: string;
|
|
59
66
|
accessibility_tree: string;
|
|
67
|
+
app?: string;
|
|
60
68
|
}
|
|
61
69
|
export interface FindResponse {
|
|
62
70
|
elements: {
|
|
@@ -67,6 +75,17 @@ export interface AddExampleRequest {
|
|
|
67
75
|
goal: string;
|
|
68
76
|
actions: string[];
|
|
69
77
|
}
|
|
78
|
+
export interface ChangeState {
|
|
79
|
+
accessibility_tree: string;
|
|
80
|
+
url: string;
|
|
81
|
+
}
|
|
82
|
+
export interface ChangesRequest {
|
|
83
|
+
before: ChangeState;
|
|
84
|
+
after: ChangeState;
|
|
85
|
+
}
|
|
86
|
+
export interface ChangesResponse {
|
|
87
|
+
result: string;
|
|
88
|
+
}
|
|
70
89
|
export interface StatsResponse {
|
|
71
90
|
[key: string]: {
|
|
72
91
|
[key: string]: number;
|
|
@@ -6,27 +6,33 @@ export declare class HttpClient {
|
|
|
6
6
|
private model;
|
|
7
7
|
private platform;
|
|
8
8
|
private tools;
|
|
9
|
+
private planner;
|
|
10
|
+
private excludedAttributes;
|
|
9
11
|
private baseUrl;
|
|
10
12
|
private sessionId;
|
|
11
13
|
private sessionPromise;
|
|
12
14
|
private timeout;
|
|
13
|
-
constructor(baseUrl: string, model: Model, platform: string, tools: Record<string, ToolClass>);
|
|
15
|
+
constructor(baseUrl: string, model: Model, platform: string, tools: Record<string, ToolClass>, planner?: boolean, excludedAttributes?: Set<string>);
|
|
14
16
|
private fetchWithTimeout;
|
|
15
17
|
private ensureSession;
|
|
16
18
|
quit(): Promise<void>;
|
|
17
|
-
planActions(goal: string, accessibilityTree: string): Promise<{
|
|
19
|
+
planActions(goal: string, accessibilityTree: string, app?: string): Promise<{
|
|
18
20
|
explanation: string;
|
|
19
21
|
steps: string[];
|
|
20
22
|
}>;
|
|
21
23
|
addExample(goal: string, actions: string[]): Promise<void>;
|
|
22
24
|
clearExamples(): Promise<void>;
|
|
23
|
-
executeAction(goal: string, step: string, accessibilityTree: string): Promise<
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
executeAction(goal: string, step: string, accessibilityTree: string, app?: string): Promise<{
|
|
26
|
+
explanation: string;
|
|
27
|
+
actions: StepResponse["actions"];
|
|
28
|
+
}>;
|
|
29
|
+
retrieve(statement: string, accessibilityTree: string, title: string, url: string, screenshot?: string, app?: string): Promise<[string, Data]>;
|
|
30
|
+
findArea(description: string, accessibilityTree: string, app?: string): Promise<{
|
|
26
31
|
id: number;
|
|
27
32
|
explanation: string;
|
|
28
33
|
}>;
|
|
29
|
-
findElement(description: string, accessibilityTree: string): Promise<FindResponse["elements"][0]>;
|
|
34
|
+
findElement(description: string, accessibilityTree: string, app?: string): Promise<FindResponse["elements"][0]>;
|
|
35
|
+
analyzeChanges(beforeAccessibilityTree: string, beforeUrl: string, afterAccessibilityTree: string, afterUrl: string): Promise<string>;
|
|
30
36
|
saveCache(): Promise<void>;
|
|
31
37
|
discardCache(): Promise<void>;
|
|
32
38
|
getStats(): Promise<StatsResponse>;
|
|
@@ -3,10 +3,12 @@ import { getLogger } from "../utils/logger.js";
|
|
|
3
3
|
import { looselyTypecast } from "./typecasting.js";
|
|
4
4
|
const logger = getLogger(["HttpClient"]);
|
|
5
5
|
export class HttpClient {
|
|
6
|
-
constructor(baseUrl, model, platform, tools) {
|
|
6
|
+
constructor(baseUrl, model, platform, tools, planner = true, excludedAttributes = new Set()) {
|
|
7
7
|
this.model = model;
|
|
8
8
|
this.platform = platform;
|
|
9
9
|
this.tools = tools;
|
|
10
|
+
this.planner = planner;
|
|
11
|
+
this.excludedAttributes = excludedAttributes;
|
|
10
12
|
this.sessionId = null;
|
|
11
13
|
this.sessionPromise = null;
|
|
12
14
|
this.timeout = 300000; // 5 minutes
|
|
@@ -34,6 +36,8 @@ export class HttpClient {
|
|
|
34
36
|
name: this.model.name,
|
|
35
37
|
platform: this.platform,
|
|
36
38
|
tools: toolSchemas,
|
|
39
|
+
planner: this.planner,
|
|
40
|
+
excluded_attributes: [...this.excludedAttributes],
|
|
37
41
|
};
|
|
38
42
|
const response = await this.fetchWithTimeout(`${this.baseUrl}/v1/sessions`, {
|
|
39
43
|
method: "POST",
|
|
@@ -57,11 +61,12 @@ export class HttpClient {
|
|
|
57
61
|
this.sessionId = null;
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
|
-
async planActions(goal, accessibilityTree) {
|
|
64
|
+
async planActions(goal, accessibilityTree, app = "unknown") {
|
|
61
65
|
await this.ensureSession();
|
|
62
66
|
const requestBody = {
|
|
63
67
|
goal,
|
|
64
68
|
accessibility_tree: accessibilityTree,
|
|
69
|
+
app,
|
|
65
70
|
};
|
|
66
71
|
const response = await this.fetchWithTimeout(`${this.baseUrl}/v1/sessions/${this.sessionId}/plans`, {
|
|
67
72
|
method: "POST",
|
|
@@ -93,12 +98,13 @@ export class HttpClient {
|
|
|
93
98
|
method: "DELETE",
|
|
94
99
|
});
|
|
95
100
|
}
|
|
96
|
-
async executeAction(goal, step, accessibilityTree) {
|
|
101
|
+
async executeAction(goal, step, accessibilityTree, app = "unknown") {
|
|
97
102
|
await this.ensureSession();
|
|
98
103
|
const requestBody = {
|
|
99
104
|
goal,
|
|
100
105
|
step,
|
|
101
106
|
accessibility_tree: accessibilityTree,
|
|
107
|
+
app,
|
|
102
108
|
};
|
|
103
109
|
const response = await this.fetchWithTimeout(`${this.baseUrl}/v1/sessions/${this.sessionId}/steps`, {
|
|
104
110
|
method: "POST",
|
|
@@ -108,9 +114,12 @@ export class HttpClient {
|
|
|
108
114
|
body: JSON.stringify(requestBody),
|
|
109
115
|
});
|
|
110
116
|
const responseData = (await response.json());
|
|
111
|
-
return
|
|
117
|
+
return {
|
|
118
|
+
explanation: responseData.explanation,
|
|
119
|
+
actions: responseData.actions,
|
|
120
|
+
};
|
|
112
121
|
}
|
|
113
|
-
async retrieve(statement, accessibilityTree, title, url, screenshot) {
|
|
122
|
+
async retrieve(statement, accessibilityTree, title, url, screenshot, app = "unknown") {
|
|
114
123
|
await this.ensureSession();
|
|
115
124
|
const requestBody = {
|
|
116
125
|
statement,
|
|
@@ -118,6 +127,7 @@ export class HttpClient {
|
|
|
118
127
|
title,
|
|
119
128
|
url,
|
|
120
129
|
screenshot: screenshot || null,
|
|
130
|
+
app,
|
|
121
131
|
};
|
|
122
132
|
const response = await this.fetchWithTimeout(`${this.baseUrl}/v1/sessions/${this.sessionId}/statements`, {
|
|
123
133
|
method: "POST",
|
|
@@ -129,11 +139,12 @@ export class HttpClient {
|
|
|
129
139
|
const responseData = (await response.json());
|
|
130
140
|
return [responseData.explanation, looselyTypecast(responseData.result)];
|
|
131
141
|
}
|
|
132
|
-
async findArea(description, accessibilityTree) {
|
|
142
|
+
async findArea(description, accessibilityTree, app = "unknown") {
|
|
133
143
|
await this.ensureSession();
|
|
134
144
|
const requestBody = {
|
|
135
145
|
description,
|
|
136
146
|
accessibility_tree: accessibilityTree,
|
|
147
|
+
app,
|
|
137
148
|
};
|
|
138
149
|
const response = await this.fetchWithTimeout(`${this.baseUrl}/v1/sessions/${this.sessionId}/areas`, {
|
|
139
150
|
method: "POST",
|
|
@@ -145,11 +156,12 @@ export class HttpClient {
|
|
|
145
156
|
const responseData = (await response.json());
|
|
146
157
|
return { id: responseData.id, explanation: responseData.explanation };
|
|
147
158
|
}
|
|
148
|
-
async findElement(description, accessibilityTree) {
|
|
159
|
+
async findElement(description, accessibilityTree, app = "unknown") {
|
|
149
160
|
await this.ensureSession();
|
|
150
161
|
const requestBody = {
|
|
151
162
|
description,
|
|
152
163
|
accessibility_tree: accessibilityTree,
|
|
164
|
+
app,
|
|
153
165
|
};
|
|
154
166
|
const response = await this.fetchWithTimeout(`${this.baseUrl}/v1/sessions/${this.sessionId}/elements`, {
|
|
155
167
|
method: "POST",
|
|
@@ -161,6 +173,22 @@ export class HttpClient {
|
|
|
161
173
|
const responseData = (await response.json());
|
|
162
174
|
return responseData.elements[0];
|
|
163
175
|
}
|
|
176
|
+
async analyzeChanges(beforeAccessibilityTree, beforeUrl, afterAccessibilityTree, afterUrl) {
|
|
177
|
+
await this.ensureSession();
|
|
178
|
+
const requestBody = {
|
|
179
|
+
before: { accessibility_tree: beforeAccessibilityTree, url: beforeUrl },
|
|
180
|
+
after: { accessibility_tree: afterAccessibilityTree, url: afterUrl },
|
|
181
|
+
};
|
|
182
|
+
const response = await this.fetchWithTimeout(`${this.baseUrl}/v1/sessions/${this.sessionId}/changes`, {
|
|
183
|
+
method: "POST",
|
|
184
|
+
headers: {
|
|
185
|
+
"Content-Type": "application/json",
|
|
186
|
+
},
|
|
187
|
+
body: JSON.stringify(requestBody),
|
|
188
|
+
});
|
|
189
|
+
const responseData = (await response.json());
|
|
190
|
+
return responseData.result;
|
|
191
|
+
}
|
|
164
192
|
async saveCache() {
|
|
165
193
|
await this.ensureSession();
|
|
166
194
|
await this.fetchWithTimeout(`${this.baseUrl}/v1/sessions/${this.sessionId}/caches`, {
|