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.
Files changed (43) hide show
  1. package/dist/Alumni.d.ts +4 -0
  2. package/dist/Alumni.js +27 -9
  3. package/dist/Area.js +13 -7
  4. package/dist/Model.d.ts +1 -0
  5. package/dist/Model.js +3 -1
  6. package/dist/accessibility/AccessibilityElement.d.ts +4 -1
  7. package/dist/accessibility/AccessibilityElement.js +4 -1
  8. package/dist/accessibility/ChromiumAccessibilityTree.d.ts +3 -0
  9. package/dist/accessibility/ChromiumAccessibilityTree.js +86 -9
  10. package/dist/clients/ApiModels.d.ts +19 -0
  11. package/dist/clients/HttpClient.d.ts +12 -6
  12. package/dist/clients/HttpClient.js +35 -7
  13. package/dist/drivers/AppiumDriver.d.ts +7 -0
  14. package/dist/drivers/AppiumDriver.js +32 -8
  15. package/dist/drivers/BaseDriver.d.ts +15 -8
  16. package/dist/drivers/PlaywrightDriver.d.ts +28 -3
  17. package/dist/drivers/PlaywrightDriver.js +434 -34
  18. package/dist/drivers/SeleniumDriver.d.ts +14 -1
  19. package/dist/drivers/SeleniumDriver.js +198 -24
  20. package/dist/drivers/scripts/waiter.js +117 -14
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +2 -0
  23. package/dist/result.d.ts +1 -0
  24. package/dist/tools/BaseTool.js +1 -1
  25. package/dist/tools/ClickTool.js +1 -2
  26. package/dist/tools/DragSliderTool.d.ts +14 -0
  27. package/dist/tools/DragSliderTool.js +25 -0
  28. package/dist/tools/PressKeyTool.js +1 -1
  29. package/dist/tools/PrintToPdfTool.d.ts +12 -0
  30. package/dist/tools/PrintToPdfTool.js +19 -0
  31. package/dist/tools/SwitchToNextTabTool.d.ts +6 -0
  32. package/dist/tools/SwitchToNextTabTool.js +7 -0
  33. package/dist/tools/SwitchToPreviousTabTool.d.ts +6 -0
  34. package/dist/tools/SwitchToPreviousTabTool.js +7 -0
  35. package/dist/tools/TypeTool.js +1 -1
  36. package/dist/tools/UploadTool.js +4 -1
  37. package/dist/tools/WaitForElementTool.d.ts +14 -0
  38. package/dist/tools/WaitForElementTool.js +33 -0
  39. package/dist/tools/WaitTool.d.ts +12 -0
  40. package/dist/tools/WaitTool.js +26 -0
  41. package/package.json +1 -1
  42. package/dist/tools/SelectTool.d.ts +0 -15
  43. 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 { explanation, steps } = await this.client.planActions(goal, initialAccessibilityTree.toStr());
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 actorResponse = await this.client.executeAction(goal, step, accessibilityTree.toStr());
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 actorResponse) {
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
- return { explanation, steps: executedSteps };
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 { explanation, steps } = await this.client.planActions(goal, this.accessibilityTree.toStr());
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 actorResponse = await this.client.executeAction(goal, step, this.accessibilityTree.toStr());
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 actorResponse) {
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
@@ -1,4 +1,5 @@
1
1
  export declare enum Provider {
2
+ AZURE_FOUNDRY = "azure_foundry",
2
3
  AZURE_OPENAI = "azure_openai",
3
4
  ANTHROPIC = "anthropic",
4
5
  AWS_ANTHROPIC = "aws_anthropic",
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
- constructor(id?: number, name?: string, label?: string, type?: string, value?: string, backendNodeId?: number, androidResourceId?: string, androidClass?: string, androidText?: string, androidContentDesc?: string, androidBounds?: string);
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 tree structure and convert to XML
25
- const rootNodes = [];
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 xmlNode = this.nodeToXml(node, nodeLookup);
29
- rootNodes.push(xmlNode);
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(/&apos;/g, "'")
181
+ .replace(/&quot;/g, '"')
182
+ .replace(/&gt;/g, ">")
183
+ .replace(/&lt;/g, "<")
184
+ .replace(/&amp;/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<StepResponse["actions"]>;
24
- retrieve(statement: string, accessibilityTree: string, title: string, url: string, screenshot?: string): Promise<[string, Data]>;
25
- findArea(description: string, accessibilityTree: string): Promise<{
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 responseData.actions;
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`, {