@nexface/tools 0.1.15-alpha.1 → 0.1.15-alpha.11
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/browser/actions.js +34 -10
- package/dist/browser/controller.js +91 -76
- package/dist/browser/diagnostics.js +8 -3
- package/dist/browser/dom.js +23 -30
- package/dist/browser/feedback.js +6 -5
- package/dist/browser/focus.d.ts +0 -1
- package/dist/browser/focus.js +1 -13
- package/dist/browser/locator.js +3 -9
- package/dist/browser/prompt.d.ts +1 -1
- package/dist/browser/prompt.generated.d.ts +1 -1
- package/dist/browser/prompt.generated.js +9 -4
- package/dist/browser/prompt.js +0 -1
- package/dist/browser/snapshot.js +199 -117
- package/dist/browser/toolset.js +42 -36
- package/dist/browser/types.d.ts +1 -1
- package/dist/browser/types.js +0 -1
- package/dist/browser/worker-runner.js +0 -1
- package/dist/definition.d.ts +1 -3
- package/dist/definition.js +2 -2
- package/dist/index.browser.js +0 -1
- package/dist/index.js +0 -1
- package/dist/internal-react.d.ts +1 -1
- package/dist/internal-react.js +3 -4
- package/dist/internal-types.js +0 -1
- package/dist/navigation.js +0 -1
- package/dist/operation-lock.d.ts +1 -1
- package/dist/operation-lock.js +36 -19
- package/dist/operation-runtime.js +41 -13
- package/dist/operation-surface.js +118 -41
- package/dist/operation-types.js +0 -1
- package/dist/operation-view.d.ts +1 -1
- package/dist/operation-view.js +232 -82
- package/dist/react.js +9 -14
- package/dist/registration.js +4 -3
- package/dist/runtime.d.ts +4 -2
- package/dist/runtime.js +114 -52
- package/dist/shared/definition.js +2 -3
- package/dist/shared/errors.d.ts +1 -1
- package/dist/shared/errors.js +0 -1
- package/dist/shared/page-protocol.js +0 -1
- package/dist/shared/page-tools.js +2 -3
- package/dist/shared/runtime-controller.d.ts +1 -0
- package/dist/shared/runtime-controller.js +0 -1
- package/dist/shared/runtime-execution.js +0 -1
- package/dist/shared/schema.js +45 -19
- package/dist/styles.css +882 -109
- package/dist/tool-search.d.ts +11 -0
- package/dist/tool-search.js +117 -0
- package/dist/types.js +0 -1
- package/dist/webmcp.js +39 -13
- package/package.json +8 -4
- package/dist/browser/actions.js.map +0 -1
- package/dist/browser/controller.js.map +0 -1
- package/dist/browser/diagnostics.js.map +0 -1
- package/dist/browser/dom.js.map +0 -1
- package/dist/browser/feedback.js.map +0 -1
- package/dist/browser/focus.js.map +0 -1
- package/dist/browser/locator.js.map +0 -1
- package/dist/browser/prompt.generated.js.map +0 -1
- package/dist/browser/prompt.js.map +0 -1
- package/dist/browser/snapshot.js.map +0 -1
- package/dist/browser/toolset.js.map +0 -1
- package/dist/browser/types.js.map +0 -1
- package/dist/browser/worker-runner.js.map +0 -1
- package/dist/definition.js.map +0 -1
- package/dist/index.browser.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal-react.js.map +0 -1
- package/dist/internal-types.js.map +0 -1
- package/dist/navigation.js.map +0 -1
- package/dist/operation-lock.js.map +0 -1
- package/dist/operation-runtime.js.map +0 -1
- package/dist/operation-surface.js.map +0 -1
- package/dist/operation-types.js.map +0 -1
- package/dist/operation-view.js.map +0 -1
- package/dist/react.js.map +0 -1
- package/dist/registration.js.map +0 -1
- package/dist/runtime.js.map +0 -1
- package/dist/shared/definition.js.map +0 -1
- package/dist/shared/errors.js.map +0 -1
- package/dist/shared/page-protocol.js.map +0 -1
- package/dist/shared/page-tools.js.map +0 -1
- package/dist/shared/runtime-controller.js.map +0 -1
- package/dist/shared/runtime-execution.js.map +0 -1
- package/dist/shared/schema.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/webmcp.js.map +0 -1
package/dist/runtime.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
import { searchNavigationTools, searchToolDefinition } from "./tool-search.js";
|
|
1
2
|
import { pageDecisionSymbol } from "./shared/runtime-execution.js";
|
|
2
3
|
import { PageCommandError } from "./browser/locator.js";
|
|
3
4
|
import { measurePagePhase } from "./browser/diagnostics.js";
|
|
4
|
-
import { runtimeControllerSymbol } from "./shared/runtime-controller.js";
|
|
5
|
+
import { runtimeControllerSymbol, } from "./shared/runtime-controller.js";
|
|
5
6
|
import { AgentToolError, TOOL_ERROR_CODES } from "./shared/errors.js";
|
|
6
7
|
export { AgentToolError } from "./shared/errors.js";
|
|
7
8
|
import { assertAgentToolRegistration } from "./registration.js";
|
|
8
9
|
import { OperationTracker } from "./operation-runtime.js";
|
|
9
10
|
import { defineAgentTool, isValidAgentContextName } from "./definition.js";
|
|
10
11
|
import { assertAgentToolInputSchema, validateAgentToolInput } from "./shared/schema.js";
|
|
11
|
-
import { createBrowserToolset
|
|
12
|
+
import { createBrowserToolset } from "./browser/toolset.js";
|
|
12
13
|
const CONTEXT_ERROR_CODES = [
|
|
13
14
|
"CONTEXT_DISPOSED",
|
|
14
15
|
"CONTEXT_NAME_MISMATCH",
|
|
@@ -21,9 +22,9 @@ export function isToolRuntimeError(error) {
|
|
|
21
22
|
if (!error || typeof error !== "object")
|
|
22
23
|
return false;
|
|
23
24
|
const candidate = error;
|
|
24
|
-
if (candidate.type !== "tool_runtime_error"
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
if (candidate.type !== "tool_runtime_error" ||
|
|
26
|
+
typeof candidate.message !== "string" ||
|
|
27
|
+
typeof candidate.code !== "string") {
|
|
27
28
|
return false;
|
|
28
29
|
}
|
|
29
30
|
if (candidate.domain === "tool") {
|
|
@@ -44,9 +45,7 @@ function assertContextName(name) {
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
function normalizeContextValue(value, path = "value", seen = new WeakSet()) {
|
|
47
|
-
if (value === null
|
|
48
|
-
|| typeof value === "string"
|
|
49
|
-
|| typeof value === "boolean") {
|
|
48
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
50
49
|
return value;
|
|
51
50
|
}
|
|
52
51
|
if (typeof value === "number") {
|
|
@@ -83,8 +82,8 @@ function normalizeContextValue(value, path = "value", seen = new WeakSet()) {
|
|
|
83
82
|
}
|
|
84
83
|
function normalizeContext(context) {
|
|
85
84
|
assertContextName(context.name);
|
|
86
|
-
if (context.description !== undefined
|
|
87
|
-
|
|
85
|
+
if (context.description !== undefined &&
|
|
86
|
+
(typeof context.description !== "string" || !context.description.trim())) {
|
|
88
87
|
throw new AgentContextError("INVALID_CONTEXT", "Context description must be a non-empty string when provided.");
|
|
89
88
|
}
|
|
90
89
|
return {
|
|
@@ -110,20 +109,20 @@ export class AgentContextError extends Error {
|
|
|
110
109
|
this.name = "ToolRuntimeError";
|
|
111
110
|
}
|
|
112
111
|
}
|
|
113
|
-
const runtimeInternals = new WeakMap();
|
|
114
112
|
export function createToolRuntime(options = {}) {
|
|
115
113
|
return new ToolRuntimeImplementation(options);
|
|
116
114
|
}
|
|
117
115
|
export function getInternalToolRuntime(runtime) {
|
|
118
|
-
|
|
119
|
-
if (!internal) {
|
|
116
|
+
if (typeof runtime !== "object" || runtime === null || runtime[toolRuntimeBrand] !== true) {
|
|
120
117
|
throw new AgentToolError("INVALID_TOOL_RUNTIME", "ToolRuntime must be created by createToolRuntime().");
|
|
121
118
|
}
|
|
122
|
-
return
|
|
119
|
+
return runtime;
|
|
123
120
|
}
|
|
124
121
|
export class ToolRuntimeImplementation extends EventTarget {
|
|
125
122
|
[toolRuntimeBrand] = true;
|
|
126
123
|
#bindings = new Map();
|
|
124
|
+
#routeSearch;
|
|
125
|
+
#visibleNavigation = new Set();
|
|
127
126
|
#contexts = new Map();
|
|
128
127
|
#contextRevision = 0;
|
|
129
128
|
#browserToolset;
|
|
@@ -137,16 +136,22 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
137
136
|
beginInternalOperation(options = {}) {
|
|
138
137
|
return this.#operations.begin(options);
|
|
139
138
|
}
|
|
140
|
-
endBridgeOperation(handle) {
|
|
139
|
+
endBridgeOperation(handle) {
|
|
140
|
+
return this.#operations.disposeBridge(handle);
|
|
141
|
+
}
|
|
141
142
|
setTaskProgress = (handle, progress) => this.#operations.setTaskProgress(handle, progress);
|
|
142
143
|
setOperationWaiting = (handle, waiting) => this.#operations.setWaitingForUser(handle, waiting);
|
|
143
144
|
getActivitySnapshot = () => this.#operations.getSnapshot();
|
|
144
145
|
subscribeActivity = (listener) => this.#operations.subscribe(listener);
|
|
145
|
-
requestOperationCancel(id) {
|
|
146
|
+
requestOperationCancel(id) {
|
|
147
|
+
this.#operations.requestCancel(id);
|
|
148
|
+
}
|
|
146
149
|
setPageActionFeedback(listener) {
|
|
147
150
|
this.#pageActionFeedback = listener;
|
|
148
|
-
return () => {
|
|
149
|
-
this.#pageActionFeedback
|
|
151
|
+
return () => {
|
|
152
|
+
if (this.#pageActionFeedback === listener)
|
|
153
|
+
this.#pageActionFeedback = undefined;
|
|
154
|
+
};
|
|
150
155
|
}
|
|
151
156
|
#emitExecution(event) {
|
|
152
157
|
this.#operations.observe(event);
|
|
@@ -154,18 +159,48 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
154
159
|
}
|
|
155
160
|
constructor(options = {}) {
|
|
156
161
|
super();
|
|
157
|
-
if (options.pageAutomation !== undefined &&
|
|
158
|
-
typeof options.pageAutomation !== "boolean") {
|
|
162
|
+
if (options.pageAutomation !== undefined && typeof options.pageAutomation !== "boolean") {
|
|
159
163
|
throw new AgentToolError("INVALID_INPUT", "pageAutomation must be a boolean.");
|
|
160
164
|
}
|
|
165
|
+
if (options.routeSearch !== undefined && typeof options.routeSearch !== "boolean") {
|
|
166
|
+
throw new AgentToolError("INVALID_INPUT", "routeSearch must be a boolean.");
|
|
167
|
+
}
|
|
168
|
+
this.#routeSearch = options.routeSearch ?? false;
|
|
169
|
+
if (this.#routeSearch)
|
|
170
|
+
this.bindTool(searchToolDefinition, ({ query, queries }) => {
|
|
171
|
+
if (query !== undefined && queries !== undefined)
|
|
172
|
+
throw new AgentToolError("INVALID_INPUT", "Use either query or queries, not both.");
|
|
173
|
+
const searchQueries = queries === undefined ? (query === undefined ? undefined : [query]) : [...queries];
|
|
174
|
+
if (searchQueries?.some((phrase) => typeof phrase !== "string" || !phrase.trim()))
|
|
175
|
+
throw new AgentToolError("INVALID_INPUT", "Search queries must contain nonblank strings.");
|
|
176
|
+
const navigation = this.getRegisteredTools().filter(({ definition }) => definition.effect === "navigate");
|
|
177
|
+
const candidates = searchQueries === undefined
|
|
178
|
+
? navigation
|
|
179
|
+
: searchNavigationTools(navigation, searchQueries.map((phrase) => phrase.trim()));
|
|
180
|
+
if (searchQueries !== undefined) {
|
|
181
|
+
const previous = this.#visibleNavigation;
|
|
182
|
+
this.#visibleNavigation = new Set(candidates);
|
|
183
|
+
for (const tool of new Set([...previous, ...candidates])) {
|
|
184
|
+
if (previous.has(tool) === this.#visibleNavigation.has(tool))
|
|
185
|
+
continue;
|
|
186
|
+
this.dispatchEvent(new DetailEvent("toolchange", Object.freeze({
|
|
187
|
+
toolName: tool.definition.name,
|
|
188
|
+
change: "visibility",
|
|
189
|
+
})));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
tools: candidates.map(({ definition: { name, description } }) => ({ name, description })),
|
|
194
|
+
};
|
|
195
|
+
});
|
|
161
196
|
this.#browserToolset = options.pageAutomation ? createBrowserToolset() : undefined;
|
|
162
|
-
runtimeInternals.set(this, this);
|
|
163
197
|
const controller = Object.freeze({
|
|
164
198
|
beginInternalOperation: (options) => this.beginInternalOperation(options),
|
|
165
199
|
setOperationWaiting: this.setOperationWaiting,
|
|
166
200
|
setTaskProgress: this.setTaskProgress,
|
|
167
201
|
getOperationSnapshot: () => this.getActivitySnapshot().operation,
|
|
168
202
|
subscribeOperation: this.subscribeActivity,
|
|
203
|
+
getRegisteredTools: () => this.getRegisteredTools(),
|
|
169
204
|
});
|
|
170
205
|
Object.defineProperty(this, runtimeControllerSymbol, { value: controller });
|
|
171
206
|
}
|
|
@@ -191,13 +226,19 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
191
226
|
assertAgentToolRegistration(definition, options);
|
|
192
227
|
const execute = options.execute;
|
|
193
228
|
if (definition.inputSchema?.type !== "object") {
|
|
194
|
-
throw new TypeError(`Tool "${definition.name}"
|
|
229
|
+
throw new TypeError(`Tool "${definition.name}" was not registered: inputSchema root type must be object.`);
|
|
230
|
+
}
|
|
231
|
+
let copied;
|
|
232
|
+
try {
|
|
233
|
+
copied = defineAgentTool(definition);
|
|
234
|
+
}
|
|
235
|
+
catch (cause) {
|
|
236
|
+
throw new TypeError(`Tool "${definition.name}" was not registered: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
195
237
|
}
|
|
196
|
-
const copied = defineAgentTool(definition);
|
|
197
238
|
return this.#bindTool(copied, (input, { signal }) => execute(input, { signal }));
|
|
198
239
|
}
|
|
199
240
|
bindTool(definition, execute, options = {}) {
|
|
200
|
-
assertAgentToolInputSchema(definition.inputSchema, `Tool "${definition.name}"
|
|
241
|
+
assertAgentToolInputSchema(definition.inputSchema, `Tool "${definition.name}" was not registered: inputSchema`);
|
|
201
242
|
return this.#bindTool(definition, execute, options);
|
|
202
243
|
}
|
|
203
244
|
#bindTool(definition, execute, options = {}) {
|
|
@@ -209,9 +250,7 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
209
250
|
definition,
|
|
210
251
|
get confirmation() {
|
|
211
252
|
const confirmation = options.getConfirmation?.();
|
|
212
|
-
return confirmation === undefined
|
|
213
|
-
? undefined
|
|
214
|
-
: Object.freeze({ ...confirmation });
|
|
253
|
+
return confirmation === undefined ? undefined : Object.freeze({ ...confirmation });
|
|
215
254
|
},
|
|
216
255
|
lifecycleSignal: lifecycleController.signal,
|
|
217
256
|
});
|
|
@@ -220,9 +259,7 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
220
259
|
definition,
|
|
221
260
|
runtimeTool,
|
|
222
261
|
execute,
|
|
223
|
-
...(options.getConfirmation
|
|
224
|
-
? { getConfirmation: options.getConfirmation }
|
|
225
|
-
: {}),
|
|
262
|
+
...(options.getConfirmation ? { getConfirmation: options.getConfirmation } : {}),
|
|
226
263
|
lifecycleController,
|
|
227
264
|
};
|
|
228
265
|
this.#bindings.set(definition.name, binding);
|
|
@@ -237,6 +274,7 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
237
274
|
disposed = true;
|
|
238
275
|
options.signal?.removeEventListener("abort", dispose);
|
|
239
276
|
this.#bindings.delete(definition.name);
|
|
277
|
+
this.#visibleNavigation.delete(runtimeTool);
|
|
240
278
|
lifecycleController.abort();
|
|
241
279
|
this.dispatchEvent(new DetailEvent("toolchange", Object.freeze({
|
|
242
280
|
toolName: definition.name,
|
|
@@ -350,21 +388,27 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
350
388
|
revision: this.#contextRevision,
|
|
351
389
|
}));
|
|
352
390
|
}
|
|
391
|
+
getRegisteredTools() {
|
|
392
|
+
return [...this.#bindings.values()]
|
|
393
|
+
.map(({ runtimeTool }) => runtimeTool)
|
|
394
|
+
.filter(({ definition }) => !this.#routeSearch || definition !== searchToolDefinition)
|
|
395
|
+
.sort((left, right) => compareStrings(left.definition.name, right.definition.name));
|
|
396
|
+
}
|
|
353
397
|
getWebMcpTools() {
|
|
354
398
|
return [...this.#bindings.values()]
|
|
355
399
|
.map(({ runtimeTool }) => runtimeTool)
|
|
400
|
+
.filter((tool) => !this.#routeSearch ||
|
|
401
|
+
tool.definition.effect !== "navigate" ||
|
|
402
|
+
this.#visibleNavigation.has(tool))
|
|
356
403
|
.sort((left, right) => compareStrings(left.definition.name, right.definition.name));
|
|
357
404
|
}
|
|
358
405
|
isRegistered(name) {
|
|
359
406
|
return this.#bindings.has(name);
|
|
360
407
|
}
|
|
361
408
|
getTools() {
|
|
362
|
-
const applicationTools =
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
.map((definition) => this.#getBrowserRuntimeTool(definition));
|
|
366
|
-
return [...applicationTools, ...browserTools]
|
|
367
|
-
.sort((left, right) => compareStrings(left.definition.name, right.definition.name));
|
|
409
|
+
const applicationTools = this.getWebMcpTools();
|
|
410
|
+
const browserTools = (this.#browserToolset?.definitions ?? []).map((definition) => this.#getBrowserRuntimeTool(definition));
|
|
411
|
+
return [...applicationTools, ...browserTools].sort((left, right) => compareStrings(left.definition.name, right.definition.name));
|
|
368
412
|
}
|
|
369
413
|
waitForTool(definition, options = {}) {
|
|
370
414
|
const isTargetRegistered = () => this.#bindings.has(definition.name);
|
|
@@ -387,8 +431,7 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
387
431
|
};
|
|
388
432
|
const onToolChange = (event) => {
|
|
389
433
|
const detail = event.detail;
|
|
390
|
-
if (detail?.toolName !== definition.name
|
|
391
|
-
|| detail.change !== "registered")
|
|
434
|
+
if (detail?.toolName !== definition.name || detail.change !== "registered")
|
|
392
435
|
return;
|
|
393
436
|
if (isTargetRegistered())
|
|
394
437
|
settle(resolve);
|
|
@@ -414,7 +457,11 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
414
457
|
}
|
|
415
458
|
execute(tool, input, options = {}) {
|
|
416
459
|
const label = options.progressLabel?.trim().replace(/\s+/g, " ").slice(0, 48);
|
|
417
|
-
options = {
|
|
460
|
+
options = {
|
|
461
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
462
|
+
...(label ? { progressLabel: label } : {}),
|
|
463
|
+
...(options[pageDecisionSymbol] ? { [pageDecisionSymbol]: true } : {}),
|
|
464
|
+
};
|
|
418
465
|
const definition = tool.definition;
|
|
419
466
|
const name = definition.name;
|
|
420
467
|
const binding = this.#bindings.get(name);
|
|
@@ -430,8 +477,8 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
430
477
|
if (!browserHandler) {
|
|
431
478
|
return Promise.reject(new AgentToolError("UNKNOWN_TOOL", `Unknown tool: ${name}.`));
|
|
432
479
|
}
|
|
433
|
-
if (!browserToolset?.definitions.some((candidate) => candidate === definition)
|
|
434
|
-
|
|
480
|
+
if (!browserToolset?.definitions.some((candidate) => candidate === definition) ||
|
|
481
|
+
this.#getBrowserRuntimeTool(definition) !== tool) {
|
|
435
482
|
return Promise.reject(new AgentToolError("CONTRACT_MISMATCH", `Tool "${name}" is registered with a different Definition.`));
|
|
436
483
|
}
|
|
437
484
|
return this.#enqueue((operationId) => this.#observeExecution(tool, input, options.signal, async (onFeedback, assertActive) => {
|
|
@@ -473,7 +520,9 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
473
520
|
};
|
|
474
521
|
// Reserve the tail before invoking executors or notifying synchronous listeners.
|
|
475
522
|
let settleTail;
|
|
476
|
-
const tail = new Promise((resolve) => {
|
|
523
|
+
const tail = new Promise((resolve) => {
|
|
524
|
+
settleTail = resolve;
|
|
525
|
+
});
|
|
477
526
|
this.#executionTail = tail;
|
|
478
527
|
const queuedExecution = previous ? previous.then(start) : start();
|
|
479
528
|
let execution = queuedExecution;
|
|
@@ -505,9 +554,13 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
505
554
|
const target = binding.resolveTarget?.(input);
|
|
506
555
|
return target?.isConnected ? target : undefined;
|
|
507
556
|
}
|
|
508
|
-
if (this.#browserRuntimeTools.get(tool.definition) !== tool ||
|
|
557
|
+
if (this.#browserRuntimeTools.get(tool.definition) !== tool ||
|
|
558
|
+
!input ||
|
|
559
|
+
typeof input !== "object")
|
|
509
560
|
return undefined;
|
|
510
|
-
return this.#browserToolset?.handlers
|
|
561
|
+
return this.#browserToolset?.handlers
|
|
562
|
+
.get(tool.definition.name)
|
|
563
|
+
?.resolveTarget?.(input);
|
|
511
564
|
}
|
|
512
565
|
async #observeExecution(tool, input, signal, execute, progressLabel, operationId) {
|
|
513
566
|
const running = {
|
|
@@ -524,7 +577,10 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
524
577
|
const assertActive = () => {
|
|
525
578
|
signal?.throwIfAborted();
|
|
526
579
|
if (presentation && this.#pageActionFeedback !== presentation) {
|
|
527
|
-
throw new PageCommandError({
|
|
580
|
+
throw new PageCommandError({
|
|
581
|
+
code: "EXECUTION_CANCELLED",
|
|
582
|
+
message: "Page action presentation was detached.",
|
|
583
|
+
});
|
|
528
584
|
}
|
|
529
585
|
};
|
|
530
586
|
this.#emitExecution(running);
|
|
@@ -533,26 +589,33 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
533
589
|
const result = await execute(async (feedback) => {
|
|
534
590
|
assertActive();
|
|
535
591
|
if (presentation)
|
|
536
|
-
await measurePagePhase("presentation", async () => {
|
|
592
|
+
await measurePagePhase("presentation", async () => {
|
|
593
|
+
await presentation(running, feedback, signal);
|
|
594
|
+
});
|
|
537
595
|
assertActive();
|
|
538
596
|
}, assertActive);
|
|
539
597
|
signal?.throwIfAborted();
|
|
540
598
|
this.#emitExecution({
|
|
541
|
-
...running,
|
|
599
|
+
...running,
|
|
600
|
+
status: "success",
|
|
601
|
+
finishedAt: Date.now(),
|
|
602
|
+
result,
|
|
542
603
|
});
|
|
543
604
|
return result;
|
|
544
605
|
}
|
|
545
606
|
catch (error) {
|
|
546
607
|
this.#emitExecution({
|
|
547
|
-
...running,
|
|
548
|
-
|
|
608
|
+
...running,
|
|
609
|
+
status: "error",
|
|
610
|
+
cancelled: signal?.aborted === true,
|
|
611
|
+
finishedAt: Date.now(),
|
|
612
|
+
error,
|
|
549
613
|
});
|
|
550
614
|
throw error;
|
|
551
615
|
}
|
|
552
616
|
}
|
|
553
617
|
async #executeApplication(name, input, options, binding, operationId) {
|
|
554
|
-
const id = globalThis.crypto?.randomUUID?.()
|
|
555
|
-
?? `${Date.now()}-${Math.random()}`;
|
|
618
|
+
const id = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
|
|
556
619
|
const startedAt = Date.now();
|
|
557
620
|
const running = {
|
|
558
621
|
...(operationId ? { operationId } : {}),
|
|
@@ -612,4 +675,3 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
612
675
|
return runtimeTool;
|
|
613
676
|
}
|
|
614
677
|
}
|
|
615
|
-
//# sourceMappingURL=runtime.js.map
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import { assertAgentToolInputSchema } from "./schema.js";
|
|
3
3
|
export function createAgentToolDefinition(metadata) {
|
|
4
4
|
if (!metadata.description.trim())
|
|
5
|
-
throw new Error("Tool description is required
|
|
5
|
+
throw new Error(`Tool "${metadata.name}": Tool description is required.`);
|
|
6
6
|
if (metadata.inputSchema?.type !== "object") {
|
|
7
7
|
throw new TypeError(`Tool inputSchema root type must be object. Tool "${metadata.name}".inputSchema.type is invalid.`);
|
|
8
8
|
}
|
|
9
9
|
assertAgentToolInputSchema(metadata.inputSchema, `Tool "${metadata.name}".inputSchema`);
|
|
10
10
|
const effect = metadata.effect ?? "action";
|
|
11
11
|
if (!["read", "action", "navigate"].includes(effect)) {
|
|
12
|
-
throw new Error(`Invalid agent tool effect: ${String(effect)}.`);
|
|
12
|
+
throw new Error(`Tool "${metadata.name}": Invalid agent tool effect: ${String(effect)}.`);
|
|
13
13
|
}
|
|
14
14
|
return Object.freeze({
|
|
15
15
|
name: metadata.name,
|
|
@@ -30,4 +30,3 @@ function cloneAndFreeze(value) {
|
|
|
30
30
|
}
|
|
31
31
|
return value;
|
|
32
32
|
}
|
|
33
|
-
//# sourceMappingURL=definition.js.map
|
package/dist/shared/errors.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const TOOL_ERROR_CODES: readonly ["EXECUTION_OUTCOME_UNKNOWN", "ACTION_IN_FLIGHT", "ACTION_NOT_HANDLED", "ACTION_TARGET_UNAVAILABLE", "BROWSER_EXECUTION_FAILED", "BROWSER_SNAPSHOT_REQUIRED", "BROWSER_TOOLS_UNAVAILABLE", "CONTRACT_MISMATCH", "DUPLICATE_BINDING", "INVALID_INPUT", "INVALID_TOOL_RUNTIME", "OPERATION_BUSY", "TOOL_CANCELLED", "TOOL_CONFIRMATION_INVALIDATED", "TOOL_CONFIRMATION_REJECTED", "TOOL_ERROR", "TOOL_REGISTRATION_TIMEOUT", "TOOL_SKIPPED", "TOOL_UNAVAILABLE", "UNKNOWN_TOOL"];
|
|
2
|
-
type AgentToolErrorCode = typeof TOOL_ERROR_CODES[number];
|
|
2
|
+
type AgentToolErrorCode = (typeof TOOL_ERROR_CODES)[number];
|
|
3
3
|
export declare class AgentToolError extends Error {
|
|
4
4
|
readonly code: AgentToolErrorCode;
|
|
5
5
|
readonly details?: unknown | undefined;
|
package/dist/shared/errors.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createAgentToolDefinition } from "./definition.js";
|
|
|
3
3
|
export const getSnapshotTool = createAgentToolDefinition({
|
|
4
4
|
name: "nxf_getSnapshot",
|
|
5
5
|
effect: "read",
|
|
6
|
-
description: "
|
|
6
|
+
description: "When the needed information or requested action is unavailable through application tools, observe the current viewport or full rendered page to read page content and identify available actions.",
|
|
7
7
|
inputSchema: {
|
|
8
8
|
type: "object",
|
|
9
9
|
properties: {
|
|
@@ -15,7 +15,7 @@ export const getSnapshotTool = createAgentToolDefinition({
|
|
|
15
15
|
});
|
|
16
16
|
export const executeActionTool = createAgentToolDefinition({
|
|
17
17
|
name: "nxf_executeAction",
|
|
18
|
-
description: '
|
|
18
|
+
description: 'When application tools do not cover the requested page action, execute async JavaScript through the host-provided restricted "ui" API. A current valid snapshot is required; if missing or expired, call nxf_getSnapshot in a separate model step first.',
|
|
19
19
|
inputSchema: {
|
|
20
20
|
type: "object",
|
|
21
21
|
properties: {
|
|
@@ -40,4 +40,3 @@ export const applyActionTool = createAgentToolDefinition({
|
|
|
40
40
|
additionalProperties: false,
|
|
41
41
|
},
|
|
42
42
|
});
|
|
43
|
-
//# sourceMappingURL=page-tools.js.map
|
|
@@ -12,6 +12,7 @@ interface OperationSnapshot {
|
|
|
12
12
|
readonly taskProgress?: TaskProgress;
|
|
13
13
|
}
|
|
14
14
|
export interface RuntimeController {
|
|
15
|
+
getRegisteredTools(): ReturnType<ToolRuntime["getTools"]>;
|
|
15
16
|
beginInternalOperation(options?: {
|
|
16
17
|
signal?: AbortSignal;
|
|
17
18
|
onCancel?: () => void;
|
package/dist/shared/schema.js
CHANGED
|
@@ -3,8 +3,17 @@ const types = new Set(["object", "array", "string", "number", "integer", "boolea
|
|
|
3
3
|
const counts = ["minLength", "maxLength", "minItems", "maxItems"];
|
|
4
4
|
const bounds = ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum"];
|
|
5
5
|
const keywords = new Set([
|
|
6
|
-
"type",
|
|
7
|
-
|
|
6
|
+
"type",
|
|
7
|
+
"description",
|
|
8
|
+
"properties",
|
|
9
|
+
"required",
|
|
10
|
+
"items",
|
|
11
|
+
"enum",
|
|
12
|
+
"additionalProperties",
|
|
13
|
+
...counts,
|
|
14
|
+
...bounds,
|
|
15
|
+
"pattern",
|
|
16
|
+
"uniqueItems",
|
|
8
17
|
]);
|
|
9
18
|
const checkedSchemas = new WeakSet();
|
|
10
19
|
const patterns = new WeakMap();
|
|
@@ -24,7 +33,9 @@ function assertJson(value, path, ancestors = new Set()) {
|
|
|
24
33
|
invalid(path, "must be JSON-compatible.");
|
|
25
34
|
if (ancestors.has(value))
|
|
26
35
|
invalid(path, "must not contain circular references.");
|
|
27
|
-
if (!Array.isArray(value) &&
|
|
36
|
+
if (!Array.isArray(value) &&
|
|
37
|
+
Object.getPrototypeOf(value) !== Object.prototype &&
|
|
38
|
+
Object.getPrototypeOf(value) !== null) {
|
|
28
39
|
invalid(path, "must be a plain JSON object.");
|
|
29
40
|
}
|
|
30
41
|
if (Object.getOwnPropertySymbols(value).length)
|
|
@@ -85,7 +96,8 @@ function assertSchema(value, path) {
|
|
|
85
96
|
invalid(`${path}.${key}`, "must be a non-negative integer.");
|
|
86
97
|
}
|
|
87
98
|
for (const key of bounds) {
|
|
88
|
-
if (value[key] !== undefined &&
|
|
99
|
+
if (value[key] !== undefined &&
|
|
100
|
+
(typeof value[key] !== "number" || !Number.isFinite(value[key])))
|
|
89
101
|
invalid(`${path}.${key}`, "must be a finite number.");
|
|
90
102
|
}
|
|
91
103
|
if (value.pattern !== undefined) {
|
|
@@ -95,7 +107,8 @@ function assertSchema(value, path) {
|
|
|
95
107
|
}
|
|
96
108
|
if (value.enum !== undefined && (!Array.isArray(value.enum) || value.enum.length === 0))
|
|
97
109
|
invalid(`${path}.enum`, "must be a non-empty array.");
|
|
98
|
-
if (Array.isArray(value.enum) &&
|
|
110
|
+
if (Array.isArray(value.enum) &&
|
|
111
|
+
value.enum.some((item, index, entries) => entries.slice(0, index).some((other) => jsonEqual(item, other))))
|
|
99
112
|
invalid(`${path}.enum`, "must contain unique values.");
|
|
100
113
|
if (value.properties !== undefined) {
|
|
101
114
|
if (value.type !== "object" || !isRecord(value.properties))
|
|
@@ -104,7 +117,10 @@ function assertSchema(value, path) {
|
|
|
104
117
|
assertSchema(property, `${path}.properties.${name}`);
|
|
105
118
|
}
|
|
106
119
|
if (value.required !== undefined) {
|
|
107
|
-
if (value.type !== "object" ||
|
|
120
|
+
if (value.type !== "object" ||
|
|
121
|
+
!Array.isArray(value.required) ||
|
|
122
|
+
value.required.some((name) => typeof name !== "string") ||
|
|
123
|
+
new Set(value.required).size !== value.required.length)
|
|
108
124
|
invalid(`${path}.required`, "must contain unique property names.");
|
|
109
125
|
const properties = value.properties;
|
|
110
126
|
if (value.required.some((name) => !properties || !Object.hasOwn(properties, name)))
|
|
@@ -118,7 +134,7 @@ function assertSchema(value, path) {
|
|
|
118
134
|
}
|
|
119
135
|
export function assertAgentToolInputSchema(value, path = "inputSchema") {
|
|
120
136
|
if (!isRecord(value) || value.type !== "object")
|
|
121
|
-
invalid(path,
|
|
137
|
+
invalid(path, "root type must be object.");
|
|
122
138
|
if (checkedSchemas.has(value))
|
|
123
139
|
return;
|
|
124
140
|
const frozen = assertJson(value, path);
|
|
@@ -130,21 +146,29 @@ function jsonEqual(left, right) {
|
|
|
130
146
|
if (left === right)
|
|
131
147
|
return true;
|
|
132
148
|
if (Array.isArray(left) && Array.isArray(right))
|
|
133
|
-
return left.length === right.length && left.every((item, index) => jsonEqual(item, right[index]));
|
|
149
|
+
return (left.length === right.length && left.every((item, index) => jsonEqual(item, right[index])));
|
|
134
150
|
if (!isRecord(left) || !isRecord(right))
|
|
135
151
|
return false;
|
|
136
152
|
const keys = Object.keys(left);
|
|
137
|
-
return keys.length === Object.keys(right).length &&
|
|
153
|
+
return (keys.length === Object.keys(right).length &&
|
|
154
|
+
keys.every((key) => Object.hasOwn(right, key) && jsonEqual(left[key], right[key])));
|
|
138
155
|
}
|
|
139
156
|
function typeMatches(schema, value) {
|
|
140
157
|
switch (schema.type) {
|
|
141
|
-
case undefined:
|
|
142
|
-
|
|
143
|
-
case "
|
|
144
|
-
|
|
145
|
-
case "
|
|
146
|
-
|
|
147
|
-
|
|
158
|
+
case undefined:
|
|
159
|
+
return true;
|
|
160
|
+
case "null":
|
|
161
|
+
return value === null;
|
|
162
|
+
case "array":
|
|
163
|
+
return Array.isArray(value);
|
|
164
|
+
case "object":
|
|
165
|
+
return isRecord(value);
|
|
166
|
+
case "integer":
|
|
167
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
168
|
+
case "number":
|
|
169
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
170
|
+
default:
|
|
171
|
+
return typeof value === schema.type;
|
|
148
172
|
}
|
|
149
173
|
}
|
|
150
174
|
export function validateAgentToolInput(schema, value, path = "input") {
|
|
@@ -177,7 +201,8 @@ export function validateAgentToolInput(schema, value, path = "input") {
|
|
|
177
201
|
errors.push(`${path} must have at least ${schema.minItems} items.`);
|
|
178
202
|
if (schema.maxItems !== undefined && value.length > schema.maxItems)
|
|
179
203
|
errors.push(`${path} must have at most ${schema.maxItems} items.`);
|
|
180
|
-
if (schema.uniqueItems &&
|
|
204
|
+
if (schema.uniqueItems &&
|
|
205
|
+
value.some((item, index) => value.slice(0, index).some((other) => jsonEqual(item, other))))
|
|
181
206
|
errors.push(`${path} must contain unique items.`);
|
|
182
207
|
if (schema.items)
|
|
183
208
|
value.forEach((item, index) => errors.push(...validateAgentToolInput(schema.items, item, `${path}[${index}]`)));
|
|
@@ -188,7 +213,9 @@ export function validateAgentToolInput(schema, value, path = "input") {
|
|
|
188
213
|
errors.push(`${path}.${key} is required.`);
|
|
189
214
|
}
|
|
190
215
|
for (const [key, item] of Object.entries(value)) {
|
|
191
|
-
const property = schema.properties && Object.hasOwn(schema.properties, key)
|
|
216
|
+
const property = schema.properties && Object.hasOwn(schema.properties, key)
|
|
217
|
+
? schema.properties[key]
|
|
218
|
+
: undefined;
|
|
192
219
|
if (property)
|
|
193
220
|
errors.push(...validateAgentToolInput(property, item, `${path}.${key}`));
|
|
194
221
|
else if (schema.additionalProperties === false)
|
|
@@ -197,4 +224,3 @@ export function validateAgentToolInput(schema, value, path = "input") {
|
|
|
197
224
|
}
|
|
198
225
|
return errors;
|
|
199
226
|
}
|
|
200
|
-
//# sourceMappingURL=schema.js.map
|