@nexface/tools 0.1.15-alpha.0 → 0.1.15-alpha.10
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 +53 -74
- package/dist/browser/diagnostics.js +8 -3
- package/dist/browser/dom.js +12 -28
- 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 +6 -3
- package/dist/browser/prompt.js +0 -1
- package/dist/browser/snapshot.js +141 -120
- 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.d.ts +1 -1
- package/dist/index.js +1 -2
- 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 +12 -17
- package/dist/registration.d.ts +3 -6
- package/dist/registration.js +8 -27
- package/dist/runtime.d.ts +19 -4
- package/dist/runtime.js +127 -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/shared/tool-registry.d.ts +0 -6
- package/dist/shared/tool-registry.js +0 -10
- package/dist/shared/tool-registry.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/webmcp.js.map +0 -1
package/dist/runtime.d.ts
CHANGED
|
@@ -2,18 +2,20 @@ import { type InternalExecutionOptions } from "./shared/runtime-execution.js";
|
|
|
2
2
|
import type { PageActionFeedback } from "./browser/feedback.js";
|
|
3
3
|
import { type TaskProgress } from "./shared/runtime-controller.js";
|
|
4
4
|
export { AgentToolError } from "./shared/errors.js";
|
|
5
|
+
import { type RegisteredAgentTool } from "./registration.js";
|
|
5
6
|
import { OperationTracker, type RuntimeActivitySnapshot } from "./operation-runtime.js";
|
|
6
7
|
import type { AgentContextEntry, AgentContextInput, AgentContextRegistration, AgentToolDefinition, AgentToolExecutor } from "./types.js";
|
|
7
8
|
import type { AgentToolExecutionEvent } from "./internal-types.js";
|
|
8
9
|
export interface ToolRuntimeOptions {
|
|
9
10
|
readonly pageAutomation?: boolean;
|
|
11
|
+
readonly routeSearch?: boolean;
|
|
10
12
|
}
|
|
11
13
|
export interface ToolRuntimeToolChangeEvent {
|
|
12
14
|
readonly toolName: string;
|
|
13
|
-
readonly change: "registered" | "unregistered";
|
|
15
|
+
readonly change: "registered" | "unregistered" | "visibility";
|
|
14
16
|
}
|
|
15
17
|
declare const CONTEXT_ERROR_CODES: readonly ["CONTEXT_DISPOSED", "CONTEXT_NAME_MISMATCH", "DUPLICATE_CONTEXT", "INVALID_CONTEXT"];
|
|
16
|
-
type AgentContextErrorCode = typeof CONTEXT_ERROR_CODES[number];
|
|
18
|
+
type AgentContextErrorCode = (typeof CONTEXT_ERROR_CODES)[number];
|
|
17
19
|
export declare function isToolRuntimeError(error: unknown): error is {
|
|
18
20
|
readonly type: "tool_runtime_error";
|
|
19
21
|
readonly domain: "tool" | "context";
|
|
@@ -51,6 +53,14 @@ export interface ToolRuntime {
|
|
|
51
53
|
beginOperation(options?: {
|
|
52
54
|
onCancel?: () => void;
|
|
53
55
|
}): ToolOperation;
|
|
56
|
+
/**
|
|
57
|
+
* Register a business tool outside React and return an idempotent unregister function.
|
|
58
|
+
* @remarks Copies the definition. Unregistration invalidates unstarted calls; running calls use their execution signal.
|
|
59
|
+
*/
|
|
60
|
+
register<TInput, TResult>(definition: AgentToolDefinition<TInput, TResult>, options: {
|
|
61
|
+
execute: RegisteredAgentTool<TInput, TResult>["execute"];
|
|
62
|
+
requiresConfirmation?: never;
|
|
63
|
+
}): () => void;
|
|
54
64
|
getTools(): readonly RuntimeAgentTool[];
|
|
55
65
|
getContexts(options?: {
|
|
56
66
|
signal?: AbortSignal;
|
|
@@ -121,7 +131,7 @@ export interface InternalToolRuntime extends ToolRuntime, EventTarget {
|
|
|
121
131
|
requestOperationCancel(id: string): void;
|
|
122
132
|
resolveTarget(tool: RuntimeAgentTool, input: unknown): HTMLElement | undefined;
|
|
123
133
|
setPageActionFeedback(listener: (event: AgentToolExecutionEvent, feedback: PageActionFeedback, signal?: AbortSignal) => void | Promise<void>): () => void;
|
|
124
|
-
|
|
134
|
+
bindTool<TInput, TResult>(definition: AgentToolDefinition<TInput, TResult>, execute: AgentToolExecutor<TInput, TResult>, options?: {
|
|
125
135
|
signal?: AbortSignal;
|
|
126
136
|
getConfirmation?: () => AgentToolConfirmationConfig | undefined;
|
|
127
137
|
resolveTarget?: (input: unknown) => HTMLElement | undefined;
|
|
@@ -157,7 +167,11 @@ export declare class ToolRuntimeImplementation extends EventTarget implements To
|
|
|
157
167
|
constructor(options?: ToolRuntimeOptions);
|
|
158
168
|
on(eventName: "toolchange", listener: (event: ToolRuntimeToolChangeEvent) => void): () => void;
|
|
159
169
|
on(eventName: "toolexecution", listener: (event: AgentToolExecutionEvent) => void): () => void;
|
|
160
|
-
register<TInput, TResult>(definition: AgentToolDefinition<TInput, TResult>,
|
|
170
|
+
register<TInput, TResult>(definition: AgentToolDefinition<TInput, TResult>, options: {
|
|
171
|
+
execute: RegisteredAgentTool<TInput, TResult>["execute"];
|
|
172
|
+
requiresConfirmation?: never;
|
|
173
|
+
}): () => void;
|
|
174
|
+
bindTool<TInput, TResult>(definition: AgentToolDefinition<TInput, TResult>, execute: AgentToolExecutor<TInput, TResult>, options?: {
|
|
161
175
|
signal?: AbortSignal;
|
|
162
176
|
getConfirmation?: () => AgentToolConfirmationConfig | undefined;
|
|
163
177
|
resolveTarget?: (input: unknown) => HTMLElement | undefined;
|
|
@@ -170,6 +184,7 @@ export declare class ToolRuntimeImplementation extends EventTarget implements To
|
|
|
170
184
|
getContexts(options?: {
|
|
171
185
|
signal?: AbortSignal;
|
|
172
186
|
}): Promise<readonly AgentContextEntry[]>;
|
|
187
|
+
getRegisteredTools(): readonly RuntimeAgentTool[];
|
|
173
188
|
getWebMcpTools(): readonly RuntimeAgentTool[];
|
|
174
189
|
isRegistered(name: string): boolean;
|
|
175
190
|
getTools(): readonly RuntimeAgentTool[];
|
package/dist/runtime.js
CHANGED
|
@@ -1,13 +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";
|
|
8
|
+
import { assertAgentToolRegistration } from "./registration.js";
|
|
7
9
|
import { OperationTracker } from "./operation-runtime.js";
|
|
8
|
-
import { isValidAgentContextName } from "./definition.js";
|
|
10
|
+
import { defineAgentTool, isValidAgentContextName } from "./definition.js";
|
|
9
11
|
import { assertAgentToolInputSchema, validateAgentToolInput } from "./shared/schema.js";
|
|
10
|
-
import { createBrowserToolset
|
|
12
|
+
import { createBrowserToolset } from "./browser/toolset.js";
|
|
11
13
|
const CONTEXT_ERROR_CODES = [
|
|
12
14
|
"CONTEXT_DISPOSED",
|
|
13
15
|
"CONTEXT_NAME_MISMATCH",
|
|
@@ -20,9 +22,9 @@ export function isToolRuntimeError(error) {
|
|
|
20
22
|
if (!error || typeof error !== "object")
|
|
21
23
|
return false;
|
|
22
24
|
const candidate = error;
|
|
23
|
-
if (candidate.type !== "tool_runtime_error"
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
if (candidate.type !== "tool_runtime_error" ||
|
|
26
|
+
typeof candidate.message !== "string" ||
|
|
27
|
+
typeof candidate.code !== "string") {
|
|
26
28
|
return false;
|
|
27
29
|
}
|
|
28
30
|
if (candidate.domain === "tool") {
|
|
@@ -43,9 +45,7 @@ function assertContextName(name) {
|
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
47
|
function normalizeContextValue(value, path = "value", seen = new WeakSet()) {
|
|
46
|
-
if (value === null
|
|
47
|
-
|| typeof value === "string"
|
|
48
|
-
|| typeof value === "boolean") {
|
|
48
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
49
49
|
return value;
|
|
50
50
|
}
|
|
51
51
|
if (typeof value === "number") {
|
|
@@ -82,8 +82,8 @@ function normalizeContextValue(value, path = "value", seen = new WeakSet()) {
|
|
|
82
82
|
}
|
|
83
83
|
function normalizeContext(context) {
|
|
84
84
|
assertContextName(context.name);
|
|
85
|
-
if (context.description !== undefined
|
|
86
|
-
|
|
85
|
+
if (context.description !== undefined &&
|
|
86
|
+
(typeof context.description !== "string" || !context.description.trim())) {
|
|
87
87
|
throw new AgentContextError("INVALID_CONTEXT", "Context description must be a non-empty string when provided.");
|
|
88
88
|
}
|
|
89
89
|
return {
|
|
@@ -109,20 +109,20 @@ export class AgentContextError extends Error {
|
|
|
109
109
|
this.name = "ToolRuntimeError";
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
-
const runtimeInternals = new WeakMap();
|
|
113
112
|
export function createToolRuntime(options = {}) {
|
|
114
113
|
return new ToolRuntimeImplementation(options);
|
|
115
114
|
}
|
|
116
115
|
export function getInternalToolRuntime(runtime) {
|
|
117
|
-
|
|
118
|
-
if (!internal) {
|
|
116
|
+
if (typeof runtime !== "object" || runtime === null || runtime[toolRuntimeBrand] !== true) {
|
|
119
117
|
throw new AgentToolError("INVALID_TOOL_RUNTIME", "ToolRuntime must be created by createToolRuntime().");
|
|
120
118
|
}
|
|
121
|
-
return
|
|
119
|
+
return runtime;
|
|
122
120
|
}
|
|
123
121
|
export class ToolRuntimeImplementation extends EventTarget {
|
|
124
122
|
[toolRuntimeBrand] = true;
|
|
125
123
|
#bindings = new Map();
|
|
124
|
+
#routeSearch;
|
|
125
|
+
#visibleNavigation = new Set();
|
|
126
126
|
#contexts = new Map();
|
|
127
127
|
#contextRevision = 0;
|
|
128
128
|
#browserToolset;
|
|
@@ -136,16 +136,22 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
136
136
|
beginInternalOperation(options = {}) {
|
|
137
137
|
return this.#operations.begin(options);
|
|
138
138
|
}
|
|
139
|
-
endBridgeOperation(handle) {
|
|
139
|
+
endBridgeOperation(handle) {
|
|
140
|
+
return this.#operations.disposeBridge(handle);
|
|
141
|
+
}
|
|
140
142
|
setTaskProgress = (handle, progress) => this.#operations.setTaskProgress(handle, progress);
|
|
141
143
|
setOperationWaiting = (handle, waiting) => this.#operations.setWaitingForUser(handle, waiting);
|
|
142
144
|
getActivitySnapshot = () => this.#operations.getSnapshot();
|
|
143
145
|
subscribeActivity = (listener) => this.#operations.subscribe(listener);
|
|
144
|
-
requestOperationCancel(id) {
|
|
146
|
+
requestOperationCancel(id) {
|
|
147
|
+
this.#operations.requestCancel(id);
|
|
148
|
+
}
|
|
145
149
|
setPageActionFeedback(listener) {
|
|
146
150
|
this.#pageActionFeedback = listener;
|
|
147
|
-
return () => {
|
|
148
|
-
this.#pageActionFeedback
|
|
151
|
+
return () => {
|
|
152
|
+
if (this.#pageActionFeedback === listener)
|
|
153
|
+
this.#pageActionFeedback = undefined;
|
|
154
|
+
};
|
|
149
155
|
}
|
|
150
156
|
#emitExecution(event) {
|
|
151
157
|
this.#operations.observe(event);
|
|
@@ -153,18 +159,48 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
153
159
|
}
|
|
154
160
|
constructor(options = {}) {
|
|
155
161
|
super();
|
|
156
|
-
if (options.pageAutomation !== undefined &&
|
|
157
|
-
typeof options.pageAutomation !== "boolean") {
|
|
162
|
+
if (options.pageAutomation !== undefined && typeof options.pageAutomation !== "boolean") {
|
|
158
163
|
throw new AgentToolError("INVALID_INPUT", "pageAutomation must be a boolean.");
|
|
159
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
|
+
});
|
|
160
196
|
this.#browserToolset = options.pageAutomation ? createBrowserToolset() : undefined;
|
|
161
|
-
runtimeInternals.set(this, this);
|
|
162
197
|
const controller = Object.freeze({
|
|
163
198
|
beginInternalOperation: (options) => this.beginInternalOperation(options),
|
|
164
199
|
setOperationWaiting: this.setOperationWaiting,
|
|
165
200
|
setTaskProgress: this.setTaskProgress,
|
|
166
201
|
getOperationSnapshot: () => this.getActivitySnapshot().operation,
|
|
167
202
|
subscribeOperation: this.subscribeActivity,
|
|
203
|
+
getRegisteredTools: () => this.getRegisteredTools(),
|
|
168
204
|
});
|
|
169
205
|
Object.defineProperty(this, runtimeControllerSymbol, { value: controller });
|
|
170
206
|
}
|
|
@@ -186,8 +222,26 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
186
222
|
this.removeEventListener(eventName, handleEvent);
|
|
187
223
|
};
|
|
188
224
|
}
|
|
189
|
-
register(definition,
|
|
190
|
-
|
|
225
|
+
register(definition, options) {
|
|
226
|
+
assertAgentToolRegistration(definition, options);
|
|
227
|
+
const execute = options.execute;
|
|
228
|
+
if (definition.inputSchema?.type !== "object") {
|
|
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 });
|
|
237
|
+
}
|
|
238
|
+
return this.#bindTool(copied, (input, { signal }) => execute(input, { signal }));
|
|
239
|
+
}
|
|
240
|
+
bindTool(definition, execute, options = {}) {
|
|
241
|
+
assertAgentToolInputSchema(definition.inputSchema, `Tool "${definition.name}" was not registered: inputSchema`);
|
|
242
|
+
return this.#bindTool(definition, execute, options);
|
|
243
|
+
}
|
|
244
|
+
#bindTool(definition, execute, options = {}) {
|
|
191
245
|
if (this.#bindings.has(definition.name)) {
|
|
192
246
|
throw new AgentToolError("DUPLICATE_BINDING", `Tool "${definition.name}" already has an active binding.`);
|
|
193
247
|
}
|
|
@@ -196,9 +250,7 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
196
250
|
definition,
|
|
197
251
|
get confirmation() {
|
|
198
252
|
const confirmation = options.getConfirmation?.();
|
|
199
|
-
return confirmation === undefined
|
|
200
|
-
? undefined
|
|
201
|
-
: Object.freeze({ ...confirmation });
|
|
253
|
+
return confirmation === undefined ? undefined : Object.freeze({ ...confirmation });
|
|
202
254
|
},
|
|
203
255
|
lifecycleSignal: lifecycleController.signal,
|
|
204
256
|
});
|
|
@@ -207,9 +259,7 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
207
259
|
definition,
|
|
208
260
|
runtimeTool,
|
|
209
261
|
execute,
|
|
210
|
-
...(options.getConfirmation
|
|
211
|
-
? { getConfirmation: options.getConfirmation }
|
|
212
|
-
: {}),
|
|
262
|
+
...(options.getConfirmation ? { getConfirmation: options.getConfirmation } : {}),
|
|
213
263
|
lifecycleController,
|
|
214
264
|
};
|
|
215
265
|
this.#bindings.set(definition.name, binding);
|
|
@@ -224,6 +274,7 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
224
274
|
disposed = true;
|
|
225
275
|
options.signal?.removeEventListener("abort", dispose);
|
|
226
276
|
this.#bindings.delete(definition.name);
|
|
277
|
+
this.#visibleNavigation.delete(runtimeTool);
|
|
227
278
|
lifecycleController.abort();
|
|
228
279
|
this.dispatchEvent(new DetailEvent("toolchange", Object.freeze({
|
|
229
280
|
toolName: definition.name,
|
|
@@ -337,21 +388,27 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
337
388
|
revision: this.#contextRevision,
|
|
338
389
|
}));
|
|
339
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
|
+
}
|
|
340
397
|
getWebMcpTools() {
|
|
341
398
|
return [...this.#bindings.values()]
|
|
342
399
|
.map(({ runtimeTool }) => runtimeTool)
|
|
400
|
+
.filter((tool) => !this.#routeSearch ||
|
|
401
|
+
tool.definition.effect !== "navigate" ||
|
|
402
|
+
this.#visibleNavigation.has(tool))
|
|
343
403
|
.sort((left, right) => compareStrings(left.definition.name, right.definition.name));
|
|
344
404
|
}
|
|
345
405
|
isRegistered(name) {
|
|
346
406
|
return this.#bindings.has(name);
|
|
347
407
|
}
|
|
348
408
|
getTools() {
|
|
349
|
-
const applicationTools =
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
.map((definition) => this.#getBrowserRuntimeTool(definition));
|
|
353
|
-
return [...applicationTools, ...browserTools]
|
|
354
|
-
.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));
|
|
355
412
|
}
|
|
356
413
|
waitForTool(definition, options = {}) {
|
|
357
414
|
const isTargetRegistered = () => this.#bindings.has(definition.name);
|
|
@@ -374,8 +431,7 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
374
431
|
};
|
|
375
432
|
const onToolChange = (event) => {
|
|
376
433
|
const detail = event.detail;
|
|
377
|
-
if (detail?.toolName !== definition.name
|
|
378
|
-
|| detail.change !== "registered")
|
|
434
|
+
if (detail?.toolName !== definition.name || detail.change !== "registered")
|
|
379
435
|
return;
|
|
380
436
|
if (isTargetRegistered())
|
|
381
437
|
settle(resolve);
|
|
@@ -401,7 +457,11 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
401
457
|
}
|
|
402
458
|
execute(tool, input, options = {}) {
|
|
403
459
|
const label = options.progressLabel?.trim().replace(/\s+/g, " ").slice(0, 48);
|
|
404
|
-
options = {
|
|
460
|
+
options = {
|
|
461
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
462
|
+
...(label ? { progressLabel: label } : {}),
|
|
463
|
+
...(options[pageDecisionSymbol] ? { [pageDecisionSymbol]: true } : {}),
|
|
464
|
+
};
|
|
405
465
|
const definition = tool.definition;
|
|
406
466
|
const name = definition.name;
|
|
407
467
|
const binding = this.#bindings.get(name);
|
|
@@ -417,8 +477,8 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
417
477
|
if (!browserHandler) {
|
|
418
478
|
return Promise.reject(new AgentToolError("UNKNOWN_TOOL", `Unknown tool: ${name}.`));
|
|
419
479
|
}
|
|
420
|
-
if (!browserToolset?.definitions.some((candidate) => candidate === definition)
|
|
421
|
-
|
|
480
|
+
if (!browserToolset?.definitions.some((candidate) => candidate === definition) ||
|
|
481
|
+
this.#getBrowserRuntimeTool(definition) !== tool) {
|
|
422
482
|
return Promise.reject(new AgentToolError("CONTRACT_MISMATCH", `Tool "${name}" is registered with a different Definition.`));
|
|
423
483
|
}
|
|
424
484
|
return this.#enqueue((operationId) => this.#observeExecution(tool, input, options.signal, async (onFeedback, assertActive) => {
|
|
@@ -460,7 +520,9 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
460
520
|
};
|
|
461
521
|
// Reserve the tail before invoking executors or notifying synchronous listeners.
|
|
462
522
|
let settleTail;
|
|
463
|
-
const tail = new Promise((resolve) => {
|
|
523
|
+
const tail = new Promise((resolve) => {
|
|
524
|
+
settleTail = resolve;
|
|
525
|
+
});
|
|
464
526
|
this.#executionTail = tail;
|
|
465
527
|
const queuedExecution = previous ? previous.then(start) : start();
|
|
466
528
|
let execution = queuedExecution;
|
|
@@ -492,9 +554,13 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
492
554
|
const target = binding.resolveTarget?.(input);
|
|
493
555
|
return target?.isConnected ? target : undefined;
|
|
494
556
|
}
|
|
495
|
-
if (this.#browserRuntimeTools.get(tool.definition) !== tool ||
|
|
557
|
+
if (this.#browserRuntimeTools.get(tool.definition) !== tool ||
|
|
558
|
+
!input ||
|
|
559
|
+
typeof input !== "object")
|
|
496
560
|
return undefined;
|
|
497
|
-
return this.#browserToolset?.handlers
|
|
561
|
+
return this.#browserToolset?.handlers
|
|
562
|
+
.get(tool.definition.name)
|
|
563
|
+
?.resolveTarget?.(input);
|
|
498
564
|
}
|
|
499
565
|
async #observeExecution(tool, input, signal, execute, progressLabel, operationId) {
|
|
500
566
|
const running = {
|
|
@@ -511,7 +577,10 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
511
577
|
const assertActive = () => {
|
|
512
578
|
signal?.throwIfAborted();
|
|
513
579
|
if (presentation && this.#pageActionFeedback !== presentation) {
|
|
514
|
-
throw new PageCommandError({
|
|
580
|
+
throw new PageCommandError({
|
|
581
|
+
code: "EXECUTION_CANCELLED",
|
|
582
|
+
message: "Page action presentation was detached.",
|
|
583
|
+
});
|
|
515
584
|
}
|
|
516
585
|
};
|
|
517
586
|
this.#emitExecution(running);
|
|
@@ -520,26 +589,33 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
520
589
|
const result = await execute(async (feedback) => {
|
|
521
590
|
assertActive();
|
|
522
591
|
if (presentation)
|
|
523
|
-
await measurePagePhase("presentation", async () => {
|
|
592
|
+
await measurePagePhase("presentation", async () => {
|
|
593
|
+
await presentation(running, feedback, signal);
|
|
594
|
+
});
|
|
524
595
|
assertActive();
|
|
525
596
|
}, assertActive);
|
|
526
597
|
signal?.throwIfAborted();
|
|
527
598
|
this.#emitExecution({
|
|
528
|
-
...running,
|
|
599
|
+
...running,
|
|
600
|
+
status: "success",
|
|
601
|
+
finishedAt: Date.now(),
|
|
602
|
+
result,
|
|
529
603
|
});
|
|
530
604
|
return result;
|
|
531
605
|
}
|
|
532
606
|
catch (error) {
|
|
533
607
|
this.#emitExecution({
|
|
534
|
-
...running,
|
|
535
|
-
|
|
608
|
+
...running,
|
|
609
|
+
status: "error",
|
|
610
|
+
cancelled: signal?.aborted === true,
|
|
611
|
+
finishedAt: Date.now(),
|
|
612
|
+
error,
|
|
536
613
|
});
|
|
537
614
|
throw error;
|
|
538
615
|
}
|
|
539
616
|
}
|
|
540
617
|
async #executeApplication(name, input, options, binding, operationId) {
|
|
541
|
-
const id = globalThis.crypto?.randomUUID?.()
|
|
542
|
-
?? `${Date.now()}-${Math.random()}`;
|
|
618
|
+
const id = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
|
|
543
619
|
const startedAt = Date.now();
|
|
544
620
|
const running = {
|
|
545
621
|
...(operationId ? { operationId } : {}),
|
|
@@ -599,4 +675,3 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
599
675
|
return runtimeTool;
|
|
600
676
|
}
|
|
601
677
|
}
|
|
602
|
-
//# 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
|