@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.
- package/dist/agent.js +867 -294
- package/dist/browser-policy/gpt.js +36 -11
- package/dist/browser-policy/jev.js +21 -8
- package/dist/browser-policy/response-error.js +4 -1
- package/dist/browser-policy/shared.js +49 -20
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +3 -3
- package/dist/history-compaction.js +47 -25
- package/dist/history-version.js +11 -3
- package/dist/model/internal-adapter.js +1 -1
- package/dist/page-decision.js +197 -58
- package/dist/page-decisions/shared.js +26 -8
- package/dist/page-diagnostics.js +8 -2
- package/dist/page-observation.js +11 -5
- package/dist/request.js +169 -40
- package/dist/shared/errors.d.ts +1 -1
- package/dist/shared/schema.js +45 -18
- package/dist/tool-catalog.js +23 -24
- package/dist/tool-result.js +7 -2
- package/package.json +2 -2
package/dist/page-decision.js
CHANGED
|
@@ -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 [
|
|
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([
|
|
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 ||
|
|
16
|
-
|
|
17
|
-
|
|
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 ||
|
|
22
|
-
|
|
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 &&
|
|
45
|
-
|
|
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" &&
|
|
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 {
|
|
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 &&
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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 ||
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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") ||
|
|
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 {
|
|
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 ||
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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) ||
|
|
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({
|
|
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
|
-
: {
|
|
39
|
+
: {
|
|
40
|
+
status: "action",
|
|
41
|
+
actionId: result.action.id,
|
|
42
|
+
...(usage === undefined ? {} : { usage }),
|
|
43
|
+
};
|
|
26
44
|
},
|
|
27
45
|
});
|
|
28
46
|
}
|
package/dist/page-diagnostics.js
CHANGED
|
@@ -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", {
|
|
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 {
|
|
12
|
+
catch {
|
|
13
|
+
/* Diagnostics must never interrupt a Run. */
|
|
14
|
+
}
|
|
9
15
|
}
|
package/dist/page-observation.js
CHANGED
|
@@ -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
|
-
|
|
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" ||
|
|
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" ||
|
|
35
|
-
|
|
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
|
}
|