@nexface/agent 0.1.13-alpha.4 → 0.1.13-alpha.5

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.
@@ -3,23 +3,50 @@ export function readPageDecisionUsage(value) {
3
3
  return;
4
4
  const raw = value;
5
5
  const usage = {};
6
- for (const [key, source] of [["inputTokens", raw.input_tokens ?? raw.prompt_tokens], ["outputTokens", raw.output_tokens ?? raw.completion_tokens]]) {
6
+ for (const [key, source] of [
7
+ ["inputTokens", raw.input_tokens ?? raw.prompt_tokens],
8
+ ["outputTokens", raw.output_tokens ?? raw.completion_tokens],
9
+ ]) {
7
10
  if (typeof source === "number" && Number.isSafeInteger(source) && source >= 0)
8
11
  usage[key] = source;
9
12
  }
10
13
  return Object.keys(usage).length ? usage : undefined;
11
14
  }
12
- const OPERATIONS = new Set(["click", "fill", "check", "uncheck", "select", "scrollUp", "scrollDown", "wait"]);
15
+ const OPERATIONS = new Set([
16
+ "click",
17
+ "fill",
18
+ "check",
19
+ "uncheck",
20
+ "select",
21
+ "scrollUp",
22
+ "scrollDown",
23
+ "wait",
24
+ ]);
13
25
  export function validatePageDecisionObservation(value) {
14
26
  const page = value;
15
- if (!page || typeof page !== "object" || typeof page.snapshotId !== "string" || !page.snapshotId
16
- || typeof page.url !== "string" || typeof page.title !== "string" || typeof page.content !== "string"
17
- || typeof page.truncated !== "boolean" || !Array.isArray(page.actions) || page.actions.length > 250 || page.content.length > 100_000)
27
+ if (!page ||
28
+ typeof page !== "object" ||
29
+ typeof page.snapshotId !== "string" ||
30
+ !page.snapshotId ||
31
+ typeof page.url !== "string" ||
32
+ typeof page.title !== "string" ||
33
+ typeof page.content !== "string" ||
34
+ typeof page.truncated !== "boolean" ||
35
+ !Array.isArray(page.actions) ||
36
+ page.actions.length > 250 ||
37
+ page.content.length > 100_000)
18
38
  throw new Error("Invalid actions observation.");
19
39
  const ids = new Set();
20
40
  for (const action of page.actions) {
21
- if (!action || typeof action.id !== "string" || !action.id || ids.has(action.id) || !OPERATIONS.has(action.operation)
22
- || typeof action.label !== "string" || typeof action.role !== "string" || !action.state || typeof action.state !== "object")
41
+ if (!action ||
42
+ typeof action.id !== "string" ||
43
+ !action.id ||
44
+ ids.has(action.id) ||
45
+ !OPERATIONS.has(action.operation) ||
46
+ typeof action.label !== "string" ||
47
+ typeof action.role !== "string" ||
48
+ !action.state ||
49
+ typeof action.state !== "object")
23
50
  throw new Error("Invalid action candidate.");
24
51
  ids.add(action.id);
25
52
  }
@@ -41,15 +68,29 @@ export function validatePageDecisionResult(value, observation) {
41
68
  const result = value;
42
69
  if (!result || typeof result !== "object" || Array.isArray(result))
43
70
  throw new Error("Invalid page decision result.");
44
- if (result.usage !== undefined && (!result.usage || typeof result.usage !== "object" || Array.isArray(result.usage)
45
- || Object.entries(result.usage).some(([key, value]) => !["inputTokens", "outputTokens"].includes(key) || typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)))
71
+ if (result.usage !== undefined &&
72
+ (!result.usage ||
73
+ typeof result.usage !== "object" ||
74
+ Array.isArray(result.usage) ||
75
+ Object.entries(result.usage).some(([key, value]) => !["inputTokens", "outputTokens"].includes(key) ||
76
+ typeof value !== "number" ||
77
+ !Number.isSafeInteger(value) ||
78
+ value < 0)))
46
79
  throw new Error("Invalid page decision usage.");
47
80
  if (result.status === "done" || result.status === "blocked") {
48
81
  if ("actionId" in result)
49
82
  throw new Error("Invalid page decision result.");
50
- if (result.status === "blocked" && result.reason !== undefined && (typeof result.reason !== "string" || result.reason.length > 2000))
83
+ if (result.status === "blocked" &&
84
+ result.reason !== undefined &&
85
+ (typeof result.reason !== "string" || result.reason.length > 2000))
51
86
  throw new Error("Invalid page decision reason.");
52
- return { status: result.status, usage: result.usage, ...(result.status === "blocked" && result.reason !== undefined ? { reason: result.reason } : {}) };
87
+ return {
88
+ status: result.status,
89
+ usage: result.usage,
90
+ ...(result.status === "blocked" && result.reason !== undefined
91
+ ? { reason: result.reason }
92
+ : {}),
93
+ };
53
94
  }
54
95
  if (result.status !== "action" || typeof result.actionId !== "string")
55
96
  throw new Error("Invalid page decision result.");
@@ -60,12 +101,23 @@ export function validatePageDecisionResult(value, observation) {
60
101
  }
61
102
  export function validatePageTask(value) {
62
103
  const task = value;
63
- const validStage = (stage, maxLength) => stage && typeof stage === "object" && !Array.isArray(stage)
64
- && Object.keys(stage).every((key) => key === "goal")
65
- && typeof stage.goal === "string" && stage.goal.trim() && stage.goal.length <= maxLength;
66
- if (!task || typeof task !== "object" || Array.isArray(task) || Object.keys(task).some((key) => !["goal", "stages"].includes(key))
67
- || !validStage({ goal: task.goal }, 8000)
68
- || (task.stages !== undefined && (!Array.isArray(task.stages) || task.stages.length < 1 || task.stages.length > 8 || !task.stages.every((stage) => validStage(stage, 2000))))) {
104
+ const validStage = (stage, maxLength) => stage &&
105
+ typeof stage === "object" &&
106
+ !Array.isArray(stage) &&
107
+ Object.keys(stage).every((key) => key === "goal") &&
108
+ typeof stage.goal === "string" &&
109
+ stage.goal.trim() &&
110
+ stage.goal.length <= maxLength;
111
+ if (!task ||
112
+ typeof task !== "object" ||
113
+ Array.isArray(task) ||
114
+ Object.keys(task).some((key) => !["goal", "stages"].includes(key)) ||
115
+ !validStage({ goal: task.goal }, 8000) ||
116
+ (task.stages !== undefined &&
117
+ (!Array.isArray(task.stages) ||
118
+ task.stages.length < 1 ||
119
+ task.stages.length > 8 ||
120
+ !task.stages.every((stage) => validStage(stage, 2000))))) {
69
121
  throw new Error("Invalid page task state.");
70
122
  }
71
123
  return { goal: task.goal, ...(task.stages ? { stages: structuredClone(task.stages) } : {}) };
@@ -73,47 +125,134 @@ export function validatePageTask(value) {
73
125
  export function validatePageProgress(value) {
74
126
  const progress = value;
75
127
  const stageCount = progress?.stages?.length ?? 1;
76
- const validCompletedStages = progress?.completedStages === undefined || (Array.isArray(progress.completedStages)
77
- && progress.completedStages.length <= 8
78
- && progress.completedStages.every((item, index) => item && typeof item === "object" && !Array.isArray(item)
79
- && Object.keys(item).every((key) => ["stageIndex", "lastObservedUrl", "lastObservedPage"].includes(key))
80
- && item.stageIndex === index && item.stageIndex < stageCount
81
- && typeof item.lastObservedUrl === "string" && item.lastObservedUrl.length <= 2000
82
- && item.lastObservedPage && typeof item.lastObservedPage === "object" && !Array.isArray(item.lastObservedPage)
83
- && Object.keys(item.lastObservedPage).every((key) => ["url", "title", "content", "truncated"].includes(key))
84
- && typeof item.lastObservedPage.url === "string" && item.lastObservedPage.url.length <= 2000
85
- && typeof item.lastObservedPage.title === "string" && item.lastObservedPage.title.length <= 2000
86
- && typeof item.lastObservedPage.content === "string" && item.lastObservedPage.content.length <= 2000
87
- && typeof item.lastObservedPage.truncated === "boolean"));
88
- const validTransitions = progress?.transitions === undefined || (Array.isArray(progress.transitions)
89
- && progress.transitions.length <= 24 && progress.transitions.every((transition) => transition
90
- && Object.keys(transition).length === 4 && ["from", "to", "operation", "label"].every((key) => typeof transition[key] === "string" && transition[key].length <= 200)));
91
- const validHistory = progress?.actionHistory === undefined || (Array.isArray(progress.actionHistory)
92
- && progress.actionHistory.length <= 64
93
- && progress.actionHistory.every((action) => action && typeof action === "object"
94
- && Object.keys(action).every((key) => ["operation", "label", "outcome", "fromUrl", "toUrl", "text", "completedActions", "errorCode"].includes(key))
95
- && typeof action.operation === "string" && action.operation.length <= 100
96
- && typeof action.label === "string" && action.label.length <= 200
97
- && ["executed", "partial", "failed", "unknown"].includes(action.outcome)
98
- && Number.isInteger(action.completedActions) && action.completedActions >= 0 && action.completedActions <= 30
99
- && (action.text === undefined || (typeof action.text === "string" && action.text.length <= 200))
100
- && (action.errorCode === undefined || (typeof action.errorCode === "string" && action.errorCode.length <= 100))
101
- && [action.fromUrl, action.toUrl].every((url) => url === undefined || (typeof url === "string" && url.length <= 2000))));
102
- if (!progress || Object.keys(progress).some((key) => !["goal", "stages", "stageIndex", "completedStages", "stage", "recentActions", "blockedRecheckUsed", "actionHistory", "actionHistoryTruncated", "transitions"].includes(key))
103
- || !Number.isInteger(progress.stageIndex ?? 0) || (progress.stageIndex ?? 0) < 0 || (progress.stageIndex ?? 0) >= stageCount
104
- || !validCompletedStages || (progress.completedStages?.length ?? 0) !== (progress.stageIndex ?? 0)
105
- || (progress.blockedRecheckUsed !== undefined && typeof progress.blockedRecheckUsed !== "boolean")
106
- || (progress.actionHistoryTruncated !== undefined && typeof progress.actionHistoryTruncated !== "boolean")
107
- || !["observe", "decide"].includes(progress.stage) || !Array.isArray(progress.recentActions) || progress.recentActions.length > 6
108
- || !validHistory || !validTransitions
109
- || progress.recentActions.some((action) => !action || Object.keys(action).some((key) => !["actionId", "operation", "label", "text", "outcome", "fromUrl", "toUrl", "observationChanged"].includes(key))
110
- || [action.actionId, action.operation, action.label].some((v) => typeof v !== "string" || v.length > 2000)
111
- || (action.text !== undefined && (typeof action.text !== "string" || action.text.length > 2000))
112
- || (action.observationChanged !== undefined && typeof action.observationChanged !== "boolean")
113
- || [action.fromUrl, action.toUrl].some((url) => url !== undefined && (typeof url !== "string" || url.length > 16000))
114
- || !["executed", "failed", "unknown"].includes(action.outcome)))
128
+ const validCompletedStages = progress?.completedStages === undefined ||
129
+ (Array.isArray(progress.completedStages) &&
130
+ progress.completedStages.length <= 8 &&
131
+ progress.completedStages.every((item, index) => item &&
132
+ typeof item === "object" &&
133
+ !Array.isArray(item) &&
134
+ Object.keys(item).every((key) => ["stageIndex", "lastObservedUrl", "lastObservedPage"].includes(key)) &&
135
+ item.stageIndex === index &&
136
+ item.stageIndex < stageCount &&
137
+ typeof item.lastObservedUrl === "string" &&
138
+ item.lastObservedUrl.length <= 2000 &&
139
+ item.lastObservedPage &&
140
+ typeof item.lastObservedPage === "object" &&
141
+ !Array.isArray(item.lastObservedPage) &&
142
+ Object.keys(item.lastObservedPage).every((key) => ["url", "title", "content", "truncated"].includes(key)) &&
143
+ typeof item.lastObservedPage.url === "string" &&
144
+ item.lastObservedPage.url.length <= 2000 &&
145
+ typeof item.lastObservedPage.title === "string" &&
146
+ item.lastObservedPage.title.length <= 2000 &&
147
+ typeof item.lastObservedPage.content === "string" &&
148
+ item.lastObservedPage.content.length <= 2000 &&
149
+ typeof item.lastObservedPage.truncated === "boolean"));
150
+ const validTransitions = progress?.transitions === undefined ||
151
+ (Array.isArray(progress.transitions) &&
152
+ progress.transitions.length <= 24 &&
153
+ progress.transitions.every((transition) => transition &&
154
+ Object.keys(transition).length === 4 &&
155
+ ["from", "to", "operation", "label"].every((key) => typeof transition[key] === "string" &&
156
+ transition[key].length <= 200)));
157
+ const validHistory = progress?.actionHistory === undefined ||
158
+ (Array.isArray(progress.actionHistory) &&
159
+ progress.actionHistory.length <= 64 &&
160
+ progress.actionHistory.every((action) => action &&
161
+ typeof action === "object" &&
162
+ Object.keys(action).every((key) => [
163
+ "operation",
164
+ "label",
165
+ "outcome",
166
+ "fromUrl",
167
+ "toUrl",
168
+ "text",
169
+ "completedActions",
170
+ "errorCode",
171
+ ].includes(key)) &&
172
+ typeof action.operation === "string" &&
173
+ action.operation.length <= 100 &&
174
+ typeof action.label === "string" &&
175
+ action.label.length <= 200 &&
176
+ ["executed", "partial", "failed", "unknown"].includes(action.outcome) &&
177
+ Number.isInteger(action.completedActions) &&
178
+ action.completedActions >= 0 &&
179
+ action.completedActions <= 30 &&
180
+ (action.text === undefined ||
181
+ (typeof action.text === "string" && action.text.length <= 200)) &&
182
+ (action.errorCode === undefined ||
183
+ (typeof action.errorCode === "string" && action.errorCode.length <= 100)) &&
184
+ [action.fromUrl, action.toUrl].every((url) => url === undefined || (typeof url === "string" && url.length <= 2000))));
185
+ if (!progress ||
186
+ Object.keys(progress).some((key) => ![
187
+ "goal",
188
+ "stages",
189
+ "stageIndex",
190
+ "completedStages",
191
+ "stage",
192
+ "recentActions",
193
+ "blockedRecheckUsed",
194
+ "actionHistory",
195
+ "actionHistoryTruncated",
196
+ "transitions",
197
+ ].includes(key)) ||
198
+ !Number.isInteger(progress.stageIndex ?? 0) ||
199
+ (progress.stageIndex ?? 0) < 0 ||
200
+ (progress.stageIndex ?? 0) >= stageCount ||
201
+ !validCompletedStages ||
202
+ (progress.completedStages?.length ?? 0) !== (progress.stageIndex ?? 0) ||
203
+ (progress.blockedRecheckUsed !== undefined &&
204
+ typeof progress.blockedRecheckUsed !== "boolean") ||
205
+ (progress.actionHistoryTruncated !== undefined &&
206
+ typeof progress.actionHistoryTruncated !== "boolean") ||
207
+ !["observe", "decide"].includes(progress.stage) ||
208
+ !Array.isArray(progress.recentActions) ||
209
+ progress.recentActions.length > 6 ||
210
+ !validHistory ||
211
+ !validTransitions ||
212
+ progress.recentActions.some((action) => !action ||
213
+ Object.keys(action).some((key) => ![
214
+ "actionId",
215
+ "operation",
216
+ "label",
217
+ "text",
218
+ "outcome",
219
+ "fromUrl",
220
+ "toUrl",
221
+ "observationChanged",
222
+ ].includes(key)) ||
223
+ [action.actionId, action.operation, action.label].some((v) => typeof v !== "string" || v.length > 2000) ||
224
+ (action.text !== undefined &&
225
+ (typeof action.text !== "string" || action.text.length > 2000)) ||
226
+ (action.observationChanged !== undefined &&
227
+ typeof action.observationChanged !== "boolean") ||
228
+ [action.fromUrl, action.toUrl].some((url) => url !== undefined && (typeof url !== "string" || url.length > 16000)) ||
229
+ !["executed", "failed", "unknown"].includes(action.outcome)))
115
230
  throw new Error("Invalid page progress.");
116
- if (progress.recentActions.some((action) => action.outcome === "unknown") || progress.actionHistory?.some((action) => action.outcome === "unknown"))
231
+ if (progress.recentActions.some((action) => action.outcome === "unknown") ||
232
+ progress.actionHistory?.some((action) => action.outcome === "unknown"))
117
233
  throw new Error("Cannot resume an unknown page outcome.");
118
- return { ...validatePageTask({ goal: progress.goal, ...(progress.stages ? { stages: progress.stages } : {}) }), stage: progress.stage, ...(progress.stageIndex === undefined ? {} : { stageIndex: progress.stageIndex }), ...(progress.completedStages === undefined ? {} : { completedStages: structuredClone(progress.completedStages) }), ...(progress.blockedRecheckUsed === undefined ? {} : { blockedRecheckUsed: progress.blockedRecheckUsed }), recentActions: structuredClone(progress.recentActions), ...(progress.actionHistory === undefined ? {} : { actionHistory: structuredClone(progress.actionHistory) }), ...(progress.actionHistoryTruncated === undefined ? {} : { actionHistoryTruncated: progress.actionHistoryTruncated }), ...(progress.transitions === undefined ? {} : { transitions: structuredClone(progress.transitions) }) };
234
+ return {
235
+ ...validatePageTask({
236
+ goal: progress.goal,
237
+ ...(progress.stages ? { stages: progress.stages } : {}),
238
+ }),
239
+ stage: progress.stage,
240
+ ...(progress.stageIndex === undefined ? {} : { stageIndex: progress.stageIndex }),
241
+ ...(progress.completedStages === undefined
242
+ ? {}
243
+ : { completedStages: structuredClone(progress.completedStages) }),
244
+ ...(progress.blockedRecheckUsed === undefined
245
+ ? {}
246
+ : { blockedRecheckUsed: progress.blockedRecheckUsed }),
247
+ recentActions: structuredClone(progress.recentActions),
248
+ ...(progress.actionHistory === undefined
249
+ ? {}
250
+ : { actionHistory: structuredClone(progress.actionHistory) }),
251
+ ...(progress.actionHistoryTruncated === undefined
252
+ ? {}
253
+ : { actionHistoryTruncated: progress.actionHistoryTruncated }),
254
+ ...(progress.transitions === undefined
255
+ ? {}
256
+ : { transitions: structuredClone(progress.transitions) }),
257
+ };
119
258
  }
@@ -1,9 +1,16 @@
1
- import { definePageDecision, readPageDecisionUsage } from "../page-decision.js";
1
+ import { definePageDecision, readPageDecisionUsage, } from "../page-decision.js";
2
2
  export function createHttpPageDecision(options, decide) {
3
- if (!options || typeof options !== "object" || typeof options.endpoint !== "string" || !options.endpoint.trim()
4
- || Object.keys(options).some((key) => !["endpoint", "model", "headers"].includes(key))
5
- || (options.model !== undefined && (typeof options.model !== "string" || !options.model.trim()))
6
- || (options.headers !== undefined && (!options.headers || typeof options.headers !== "object" || Array.isArray(options.headers) || Object.values(options.headers).some((v) => typeof v !== "string")))) {
3
+ if (!options ||
4
+ typeof options !== "object" ||
5
+ typeof options.endpoint !== "string" ||
6
+ !options.endpoint.trim() ||
7
+ Object.keys(options).some((key) => !["endpoint", "model", "headers"].includes(key)) ||
8
+ (options.model !== undefined && (typeof options.model !== "string" || !options.model.trim())) ||
9
+ (options.headers !== undefined &&
10
+ (!options.headers ||
11
+ typeof options.headers !== "object" ||
12
+ Array.isArray(options.headers) ||
13
+ Object.values(options.headers).some((v) => typeof v !== "string")))) {
7
14
  throw new Error("Invalid page decision HTTP options.");
8
15
  }
9
16
  let endpoint;
@@ -13,16 +20,27 @@ export function createHttpPageDecision(options, decide) {
13
20
  catch {
14
21
  throw new Error("Page decision endpoint must be an HTTP URL (or a relative URL in a browser).");
15
22
  }
16
- if (!["http:", "https:"].includes(endpoint.protocol) || endpoint.username || endpoint.password || endpoint.hash)
23
+ if (!["http:", "https:"].includes(endpoint.protocol) ||
24
+ endpoint.username ||
25
+ endpoint.password ||
26
+ endpoint.hash)
17
27
  throw new Error("Invalid page decision endpoint.");
18
- const config = Object.freeze({ ...options, endpoint: endpoint.href, ...(options.headers ? { headers: Object.freeze({ ...options.headers }) } : {}) });
28
+ const config = Object.freeze({
29
+ ...options,
30
+ endpoint: endpoint.href,
31
+ ...(options.headers ? { headers: Object.freeze({ ...options.headers }) } : {}),
32
+ });
19
33
  return definePageDecision({
20
34
  async decide({ goal, observation, recentActions, signal }) {
21
35
  const result = await decide(config, { goal }, observation, [...recentActions], signal);
22
36
  const usage = readPageDecisionUsage(result.usage);
23
37
  return result.status
24
38
  ? { status: result.status, ...(usage === undefined ? {} : { usage }) }
25
- : { status: "action", actionId: result.action.id, ...(usage === undefined ? {} : { usage }) };
39
+ : {
40
+ status: "action",
41
+ actionId: result.action.id,
42
+ ...(usage === undefined ? {} : { usage }),
43
+ };
26
44
  },
27
45
  });
28
46
  }
@@ -2,8 +2,14 @@
2
2
  export function recordPageDiagnostic(kind, detail = {}) {
3
3
  try {
4
4
  const now = performance.now();
5
- performance.measure("nexface.page.event", { start: now, end: now, detail: { kind, ...detail } });
5
+ performance.measure("nexface.page.event", {
6
+ start: now,
7
+ end: now,
8
+ detail: { kind, ...detail },
9
+ });
6
10
  performance.clearMeasures("nexface.page.event");
7
11
  }
8
- catch { /* Diagnostics must never interrupt a Run. */ }
12
+ catch {
13
+ /* Diagnostics must never interrupt a Run. */
14
+ }
9
15
  }
@@ -14,8 +14,8 @@ export function isPendingPage(page) {
14
14
  return false;
15
15
  if (page.actions.every((action) => action.operation === "wait"))
16
16
  return true;
17
- return /(?:^|\n)\s*- status(?:\s|$)/m.test(page.content)
18
- && page.actions.every((action) => action.operation === "wait" || (action.operation === "click" && action.role === "link"));
17
+ return (/(?:^|\n)\s*- status(?:\s|$)/m.test(page.content) &&
18
+ page.actions.every((action) => action.operation === "wait" || (action.operation === "click" && action.role === "link")));
19
19
  }
20
20
  export function isNavigationLoadingPage(page) {
21
21
  return isPendingPage(page) && /(?:^|\n)\s*- status(?:\s|$)/m.test(page.content);
@@ -25,14 +25,20 @@ export function navigationLoadingWaits(history, url) {
25
25
  let waits = 0;
26
26
  while (index >= 0) {
27
27
  const action = history[index];
28
- if (action.operation !== "wait" || action.outcome !== "executed" || action.fromUrl !== url || action.toUrl !== url)
28
+ if (action.operation !== "wait" ||
29
+ action.outcome !== "executed" ||
30
+ action.fromUrl !== url ||
31
+ action.toUrl !== url)
29
32
  break;
30
33
  waits++;
31
34
  index--;
32
35
  }
33
36
  const navigation = history[index];
34
- if (navigation?.operation !== "click" || navigation.outcome !== "executed"
35
- || !navigation.fromUrl || navigation.fromUrl === url || navigation.toUrl !== url)
37
+ if (navigation?.operation !== "click" ||
38
+ navigation.outcome !== "executed" ||
39
+ !navigation.fromUrl ||
40
+ navigation.fromUrl === url ||
41
+ navigation.toUrl !== url)
36
42
  return undefined;
37
43
  return waits;
38
44
  }