@nexface/tools 0.1.13-alpha.3 → 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.
@@ -1,13 +1,19 @@
1
1
  import { CursorIcon as Cursor } from "@phosphor-icons/react/Cursor";
2
2
  import { SparkleIcon as Sparkle } from "@phosphor-icons/react/Sparkle";
3
3
  import * as React from "react";
4
- import { useCallback, useLayoutEffect, useEffect, useRef, useState } from "react";
4
+ import { useCallback, useLayoutEffect, useEffect, useRef, useState, } from "react";
5
5
  import { createPortal } from "react-dom";
6
6
  import { PageCommandError } from "./browser/locator.js";
7
7
  import { lockOperationSurface } from "./operation-lock.js";
8
- const center = (rect) => ({ left: rect.left + rect.width / 2, top: rect.top + rect.height / 2 });
8
+ const center = (rect) => ({
9
+ left: rect.left + rect.width / 2,
10
+ top: rect.top + rect.height / 2,
11
+ });
9
12
  const distance = (a, b) => Math.hypot(a.left - b.left, a.top - b.top);
10
- const interrupted = () => new PageCommandError({ code: "EXECUTION_CANCELLED", message: "Page action presentation was interrupted." });
13
+ const interrupted = () => new PageCommandError({
14
+ code: "EXECUTION_CANCELLED",
15
+ message: "Page action presentation was interrupted.",
16
+ });
11
17
  function presentPointer(pointer, target, presentation) {
12
18
  let finished = false;
13
19
  const finish = (error) => {
@@ -19,45 +25,57 @@ function presentPointer(pointer, target, presentation) {
19
25
  presentation.signal?.removeEventListener("abort", onAbort);
20
26
  presentation.finish(error);
21
27
  };
22
- const reached = () => pointer.isConnected && target.isConnected && distance(pointer.getBoundingClientRect(), center(target.getBoundingClientRect())) < 2;
28
+ const reached = () => pointer.isConnected &&
29
+ target.isConnected &&
30
+ distance(pointer.getBoundingClientRect(), center(target.getBoundingClientRect())) < 2;
23
31
  const onTransition = (event) => {
24
32
  if (event.target === pointer && event.propertyName === "transform" && reached())
25
33
  finish();
26
34
  };
27
35
  const onAbort = () => finish(interrupted());
28
- const timer = setTimeout(() => reached() ? finish() : finish(new PageCommandError({
29
- code: "ACTION_TIMEOUT", message: "Page action pointer did not reach its target in time.",
30
- })), 750);
36
+ const timer = setTimeout(() => reached()
37
+ ? finish()
38
+ : finish(new PageCommandError({
39
+ code: "ACTION_TIMEOUT",
40
+ message: "Page action pointer did not reach its target in time.",
41
+ })), 750);
31
42
  pointer.addEventListener("transitionend", onTransition);
32
43
  presentation.signal?.addEventListener("abort", onAbort, { once: true });
33
- queueMicrotask(() => { if (presentation.signal?.aborted)
34
- onAbort();
35
- else if (reached())
36
- finish(); });
44
+ queueMicrotask(() => {
45
+ if (presentation.signal?.aborted)
46
+ onAbort();
47
+ else if (reached())
48
+ finish();
49
+ });
37
50
  return () => finish(interrupted());
38
51
  }
39
52
  function FeedbackLabel({ feedback }) {
40
- return React.createElement("span", { className: "agent-operation__pointer-label", "data-browser-tool": feedback.centered ? "true" : undefined, "data-operation-effect": feedback.effect, title: feedback.label },
41
- feedback.effect === "processing" ? React.createElement("span", { className: "agent-operation__pointer-processing" }) : null,
42
- feedback.effect === "read" ? React.createElement("span", { className: "agent-operation__pointer-read-scan" },
53
+ return (React.createElement("span", { className: "agent-operation__pointer-label", "data-browser-tool": feedback.centered ? "true" : undefined, "data-operation-effect": feedback.effect, title: feedback.label },
54
+ feedback.effect === "processing" ? (React.createElement("span", { className: "agent-operation__pointer-processing" })) : null,
55
+ feedback.effect === "read" ? (React.createElement("span", { className: "agent-operation__pointer-read-scan" },
43
56
  React.createElement("i", null),
44
57
  React.createElement("i", null),
45
58
  React.createElement("i", null),
46
59
  React.createElement("i", null),
47
- React.createElement("i", null)) : null,
48
- feedback.effect === "navigate" ? React.createElement("span", { className: "agent-operation__pointer-navigation-trail" }) : null,
49
- React.createElement("span", { className: "agent-operation__pointer-label-text" }, feedback.label));
60
+ React.createElement("i", null))) : null,
61
+ feedback.effect === "navigate" ? (React.createElement("span", { className: "agent-operation__pointer-navigation-trail" })) : null,
62
+ React.createElement("span", { className: "agent-operation__pointer-label-text" }, feedback.label)));
50
63
  }
51
- function FeedbackIndicator({ feedback, position }) {
64
+ function FeedbackIndicator({ feedback, position, }) {
52
65
  const [lastPosition, setLastPosition] = useState(position ?? feedback.position);
53
- useLayoutEffect(() => { if (position)
54
- setLastPosition(position); }, [position?.left, position?.top]);
66
+ useLayoutEffect(() => {
67
+ if (position)
68
+ setLastPosition(position);
69
+ }, [position?.left, position?.top]);
55
70
  const current = position ?? lastPosition;
56
- return current ? React.createElement("div", { "aria-hidden": true, className: "agent-operation__pointer-effect", "data-active": "true", "data-operation-effect": feedback.effect, style: { "--agent-operation-pointer-duration": "0ms", "--agent-operation-pointer-x": `${current.left}px`,
57
- "--agent-operation-pointer-y": `${current.top}px` } },
58
- React.createElement(FeedbackLabel, { feedback: feedback })) : null;
71
+ return current ? (React.createElement("div", { "aria-hidden": true, className: "agent-operation__pointer-effect", "data-active": "true", "data-operation-effect": feedback.effect, style: {
72
+ "--agent-operation-pointer-duration": "0ms",
73
+ "--agent-operation-pointer-x": `${current.left}px`,
74
+ "--agent-operation-pointer-y": `${current.top}px`,
75
+ } },
76
+ React.createElement(FeedbackLabel, { feedback: feedback }))) : null;
59
77
  }
60
- function AgentPointer({ active, feedback, targetPosition, surfaceCenter }) {
78
+ function AgentPointer({ active, feedback, targetPosition, surfaceCenter, }) {
61
79
  const pointer = useRef(null);
62
80
  const [motion, setMotion] = useState();
63
81
  const action = feedback?.effect === "action" && (!feedback.requiresTarget || Boolean(targetPosition));
@@ -75,8 +93,11 @@ function AgentPointer({ active, feedback, targetPosition, surfaceCenter }) {
75
93
  return;
76
94
  }
77
95
  if (!motion) {
78
- setMotion({ position: feedback?.persistentCursor && surfaceCenter ? surfaceCenter : targetPosition,
79
- durationMs: 0, persistent: Boolean(feedback?.persistentCursor) });
96
+ setMotion({
97
+ position: feedback?.persistentCursor && surfaceCenter ? surfaceCenter : targetPosition,
98
+ durationMs: 0,
99
+ persistent: Boolean(feedback?.persistentCursor),
100
+ });
80
101
  return;
81
102
  }
82
103
  if (distance(motion.position, targetPosition) < 1) {
@@ -85,9 +106,14 @@ function AgentPointer({ active, feedback, targetPosition, surfaceCenter }) {
85
106
  return;
86
107
  }
87
108
  const from = pointer.current?.getBoundingClientRect() ?? motion.position;
88
- const durationMs = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ? 0
109
+ const durationMs = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
110
+ ? 0
89
111
  : Math.round(Math.min(360, Math.max(180, 180 + distance(from, targetPosition) * 0.18)));
90
- setMotion({ position: targetPosition, durationMs, persistent: motion.persistent || Boolean(feedback?.persistentCursor) });
112
+ setMotion({
113
+ position: targetPosition,
114
+ durationMs,
115
+ persistent: motion.persistent || Boolean(feedback?.persistentCursor),
116
+ });
91
117
  }, [active, action, feedback?.id, targetPosition?.left, targetPosition?.top, motion]);
92
118
  const show = Boolean(active && motion && (motion.persistent || action));
93
119
  useLayoutEffect(() => {
@@ -100,18 +126,22 @@ function AgentPointer({ active, feedback, targetPosition, surfaceCenter }) {
100
126
  if (show && pointer.current)
101
127
  return presentPointer(pointer.current, feedback.target, feedback.presentation);
102
128
  }, [active, show, feedback?.presentation, feedback?.target, Boolean(targetPosition)]);
103
- return show && motion ? React.createElement("div", { "aria-hidden": true, ref: pointer, className: "agent-operation__pointer-effect", "data-active": action, "data-operation-effect": action ? "action" : undefined, style: { "--agent-operation-pointer-duration": `${motion.durationMs}ms`,
104
- "--agent-operation-pointer-x": `${motion.position.left}px`, "--agent-operation-pointer-y": `${motion.position.top}px` } },
129
+ return show && motion ? (React.createElement("div", { "aria-hidden": true, ref: pointer, className: "agent-operation__pointer-effect", "data-active": action, "data-operation-effect": action ? "action" : undefined, style: {
130
+ "--agent-operation-pointer-duration": `${motion.durationMs}ms`,
131
+ "--agent-operation-pointer-x": `${motion.position.left}px`,
132
+ "--agent-operation-pointer-y": `${motion.position.top}px`,
133
+ } },
105
134
  React.createElement("span", { className: "agent-operation__pointer-cursor" },
106
135
  React.createElement(Cursor, { className: "agent-operation__pointer", size: 24, weight: "fill" })),
107
- action && feedback ? React.createElement(FeedbackLabel, { feedback: feedback }) : null) : null;
136
+ action && feedback ? React.createElement(FeedbackLabel, { feedback: feedback }) : null)) : null;
108
137
  }
109
- export function OperationOverlayView({ operation, feedback, progressHistory = [], running, humanHasControl = false, cancelling = false, onCancel: cancel, resolveSurface }) {
138
+ export function OperationOverlayView({ operation, feedback, progressHistory = [], running, humanHasControl = false, cancelling = false, onCancel: cancel, resolveSurface, }) {
110
139
  const [, setGeometry] = useState("");
111
140
  const [historyOpen, setHistoryOpen] = useState(false);
112
141
  const [historyId] = useState(() => `nexface-operation-history-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`}`);
113
142
  const cancelButtonRef = useRef(null);
114
- const controllerShouldBeVisible = operation.status !== "cancelled" && (running || humanHasControl || operation.status === "failed");
143
+ const controllerShouldBeVisible = operation.status !== "cancelled" &&
144
+ (running || humanHasControl || operation.status === "failed");
115
145
  const [controllerVisibility, setControllerVisibility] = useState(controllerShouldBeVisible ? "visible" : undefined);
116
146
  useEffect(() => {
117
147
  if (controllerShouldBeVisible) {
@@ -119,12 +149,14 @@ export function OperationOverlayView({ operation, feedback, progressHistory = []
119
149
  return;
120
150
  }
121
151
  setHistoryOpen(false);
122
- setControllerVisibility((current) => current ? "exiting" : undefined);
152
+ setControllerVisibility((current) => (current ? "exiting" : undefined));
123
153
  const timer = setTimeout(() => setControllerVisibility(undefined), 300);
124
154
  return () => clearTimeout(timer);
125
155
  }, [controllerShouldBeVisible]);
126
- useEffect(() => { if (!progressHistory.length)
127
- setHistoryOpen(false); }, [progressHistory.length]);
156
+ useEffect(() => {
157
+ if (!progressHistory.length)
158
+ setHistoryOpen(false);
159
+ }, [progressHistory.length]);
128
160
  useLayoutEffect(() => {
129
161
  const surface = resolveSurface?.();
130
162
  if (!surface || !running || humanHasControl)
@@ -144,17 +176,22 @@ export function OperationOverlayView({ operation, feedback, progressHistory = []
144
176
  useLayoutEffect(() => {
145
177
  const update = () => {
146
178
  const { surfaceRect, targetRect } = readGeometry();
147
- const geometry = [surfaceRect, targetRect].map((rect) => {
179
+ const geometry = [surfaceRect, targetRect]
180
+ .map((rect) => {
148
181
  if (!rect)
149
182
  return "";
150
183
  return [rect.left, rect.top, rect.width, rect.height].join(",");
151
- }).join(";");
184
+ })
185
+ .join(";");
152
186
  setGeometry(geometry);
153
187
  };
154
188
  update();
155
189
  if (!running && !humanHasControl)
156
190
  return;
157
- let frame = requestAnimationFrame(function track() { update(); frame = requestAnimationFrame(track); });
191
+ let frame = requestAnimationFrame(function track() {
192
+ update();
193
+ frame = requestAnimationFrame(track);
194
+ });
158
195
  return () => cancelAnimationFrame(frame);
159
196
  }, [readGeometry, operation, running, humanHasControl]);
160
197
  if (typeof document === "undefined")
@@ -170,29 +207,53 @@ export function OperationOverlayView({ operation, feedback, progressHistory = []
170
207
  const controllerExiting = controllerVisibility === "exiting";
171
208
  const controllerLabel = humanHasControl ? undefined : operation.progressLabel;
172
209
  const compactStatusLabel = humanHasControl ? "等待你完成操作" : operation.progressLabel;
173
- const statusLabel = humanHasControl ? "等待你完成操作" : {
174
- running: "Agent 正在控制", completed: running ? "工具已完成,任务继续中" : "操作已完成",
175
- failed: running ? "Agent 正在处理错误" : "操作失败", cancelled: "任务已终止",
176
- }[operation.status];
210
+ const statusLabel = humanHasControl
211
+ ? "等待你完成操作"
212
+ : {
213
+ running: "Agent 正在控制",
214
+ completed: running ? "工具已完成,任务继续中" : "操作已完成",
215
+ failed: running ? "Agent 正在处理错误" : "操作失败",
216
+ cancelled: "任务已终止",
217
+ }[operation.status];
177
218
  const targetPosition = rect ? center(rect) : undefined;
178
219
  return createPortal(React.createElement("div", { className: "agent-operation", "data-nexface-ignore": true, "data-feedback-active": operationFeedbackActive, "data-structured-target-lost": structuredTargetLost, "data-human-control": humanHasControl, "data-operation-effect": effect ?? "action", "data-run-active": running, "data-status": operation.status },
179
- agentControlsSurface && surfaceRect ? React.createElement("div", { "aria-hidden": true, className: "agent-operation__surface", style: {
180
- ...(highlightedRect ? { "--agent-operation-cutout-height": `${highlightedRect.height}px`,
181
- "--agent-operation-cutout-left": `${highlightedRect.left - surfaceRect.left}px`,
182
- "--agent-operation-cutout-top": `${highlightedRect.top - surfaceRect.top}px`, "--agent-operation-cutout-width": `${highlightedRect.width}px` } : {}),
183
- height: surfaceRect.height, left: surfaceRect.left, top: surfaceRect.top, width: surfaceRect.width,
184
- } }) : null,
185
- activeFeedback && highlightedRect ? React.createElement("div", { "aria-hidden": true, className: "agent-operation__target", "data-operation-effect": effect, style: { height: highlightedRect.height, left: highlightedRect.left, top: highlightedRect.top, width: highlightedRect.width } }) : null,
220
+ agentControlsSurface && surfaceRect ? (React.createElement("div", { "aria-hidden": true, className: "agent-operation__surface", style: {
221
+ ...(highlightedRect
222
+ ? {
223
+ "--agent-operation-cutout-height": `${highlightedRect.height}px`,
224
+ "--agent-operation-cutout-left": `${highlightedRect.left - surfaceRect.left}px`,
225
+ "--agent-operation-cutout-top": `${highlightedRect.top - surfaceRect.top}px`,
226
+ "--agent-operation-cutout-width": `${highlightedRect.width}px`,
227
+ }
228
+ : {}),
229
+ height: surfaceRect.height,
230
+ left: surfaceRect.left,
231
+ top: surfaceRect.top,
232
+ width: surfaceRect.width,
233
+ } })) : null,
234
+ activeFeedback && highlightedRect ? (React.createElement("div", { "aria-hidden": true, className: "agent-operation__target", "data-operation-effect": effect, style: {
235
+ height: highlightedRect.height,
236
+ left: highlightedRect.left,
237
+ top: highlightedRect.top,
238
+ width: highlightedRect.width,
239
+ } })) : null,
186
240
  React.createElement(AgentPointer, { active: agentControlsSurface && !cancelling, feedback: feedback?.presentation ? feedback : activeFeedback, targetPosition: targetPosition, surfaceCenter: surfaceRect ? center(surfaceRect) : undefined }),
187
- activeFeedback && effect !== "action" && surfaceRect ? React.createElement(FeedbackIndicator, { key: activeFeedback.id, feedback: activeFeedback, position: activeFeedback.centered ? center(surfaceRect) : targetPosition }) : null,
188
- controllerMounted ? React.createElement("section", { "aria-live": "polite", className: "agent-operation__controller", "data-visibility": controllerExiting ? "exiting" : "visible", style: surfaceRect ? { left: surfaceRect.left + surfaceRect.width / 2, right: "auto", "--agent-operation-controller-x": "-50%", maxWidth: Math.max(0, surfaceRect.width - 32) } : undefined },
189
- historyOpen ? React.createElement("aside", { "aria-label": "\u672C\u6B21\u8FD0\u884C\u7684\u64CD\u4F5C\u8BB0\u5F55", className: "agent-operation__history", id: historyId },
241
+ activeFeedback && effect !== "action" && surfaceRect ? (React.createElement(FeedbackIndicator, { key: activeFeedback.id, feedback: activeFeedback, position: activeFeedback.centered ? center(surfaceRect) : targetPosition })) : null,
242
+ controllerMounted ? (React.createElement("section", { "aria-live": "polite", className: "agent-operation__controller", "data-visibility": controllerExiting ? "exiting" : "visible", style: surfaceRect
243
+ ? {
244
+ left: surfaceRect.left + surfaceRect.width / 2,
245
+ right: "auto",
246
+ "--agent-operation-controller-x": "-50%",
247
+ maxWidth: Math.max(0, surfaceRect.width - 32),
248
+ }
249
+ : undefined },
250
+ historyOpen ? (React.createElement("aside", { "aria-label": "\u672C\u6B21\u8FD0\u884C\u7684\u64CD\u4F5C\u8BB0\u5F55", className: "agent-operation__history", id: historyId },
190
251
  React.createElement("header", null,
191
252
  React.createElement("strong", null, "\u64CD\u4F5C\u8BB0\u5F55"),
192
253
  React.createElement("span", null, progressHistory.length)),
193
- React.createElement("ol", null, progressHistory.map(({ progressLabel: label, toolCallId }, index) => React.createElement("li", { key: toolCallId },
254
+ React.createElement("ol", null, progressHistory.map(({ progressLabel: label, toolCallId }, index) => (React.createElement("li", { key: toolCallId },
194
255
  React.createElement("span", { "aria-hidden": true }, index + 1),
195
- React.createElement("p", null, label))))) : null,
256
+ React.createElement("p", null, label))))))) : null,
196
257
  React.createElement("span", { "aria-hidden": true, className: "agent-operation__edge-flow" }),
197
258
  React.createElement("span", { "aria-hidden": true, className: "agent-operation__pause-mark" },
198
259
  React.createElement("i", null),
@@ -204,8 +265,8 @@ export function OperationOverlayView({ operation, feedback, progressHistory = []
204
265
  React.createElement("div", null,
205
266
  React.createElement("span", { className: "agent-operation__heading" },
206
267
  React.createElement("strong", null, controllerLabel)),
207
- statusLabel ? React.createElement("small", { className: "agent-operation__description" },
208
- React.createElement("span", { className: "agent-operation__status" }, statusLabel)) : null),
209
- (running || humanHasControl) && operation.status !== "cancelled" ? React.createElement("span", { className: "agent-operation__compact-status", title: compactStatusLabel }, compactStatusLabel) : null,
210
- cancel && (running || humanHasControl) && operation.status !== "cancelled" ? React.createElement("button", { ref: cancelButtonRef, className: "agent-operation__stop", type: "button", disabled: cancelling, onClick: cancel }, cancelling ? "正在终止" : "终止任务") : null) : null), document.body);
268
+ statusLabel ? (React.createElement("small", { className: "agent-operation__description" },
269
+ React.createElement("span", { className: "agent-operation__status" }, statusLabel))) : null),
270
+ (running || humanHasControl) && operation.status !== "cancelled" ? (React.createElement("span", { className: "agent-operation__compact-status", title: compactStatusLabel }, compactStatusLabel)) : null,
271
+ cancel && (running || humanHasControl) && operation.status !== "cancelled" ? (React.createElement("button", { ref: cancelButtonRef, className: "agent-operation__stop", type: "button", disabled: cancelling, onClick: cancel }, cancelling ? "正在终止" : "终止任务")) : null)) : null), document.body);
211
272
  }
package/dist/react.js CHANGED
@@ -3,7 +3,7 @@ import { Children, Fragment, cloneElement, useCallback, useEffect, useLayoutEffe
3
3
  import { defineAgentTool } from "./definition.js";
4
4
  import { getAgentNavigationEntries, } from "./navigation.js";
5
5
  import { AgentToolError, getInternalToolRuntime } from "./runtime.js";
6
- import { InternalToolRuntimeScope, useInternalToolRuntime, } from "./internal-react.js";
6
+ import { InternalToolRuntimeScope, useInternalToolRuntime } from "./internal-react.js";
7
7
  import { connectToolRuntimeToWebMcp } from "./webmcp.js";
8
8
  const bindingInternals = new WeakMap();
9
9
  export function getAgentToolBindingInternals(binding) {
@@ -55,10 +55,10 @@ function createAgentToolBinding(toolName) {
55
55
  function useAgentBinding(definition, execute, options) {
56
56
  const runtime = useInternalToolRuntime();
57
57
  const confirmation = options.requiresConfirmation;
58
- if (confirmation !== undefined
59
- && (!confirmation
60
- || typeof confirmation !== "object"
61
- || (confirmation.mode !== "chat" && confirmation.mode !== "page"))) {
58
+ if (confirmation !== undefined &&
59
+ (!confirmation ||
60
+ typeof confirmation !== "object" ||
61
+ (confirmation.mode !== "chat" && confirmation.mode !== "page"))) {
62
62
  throw new Error("requiresConfirmation must explicitly use chat or page mode.");
63
63
  }
64
64
  const executeRef = useRef(execute);
@@ -82,9 +82,7 @@ export function useAgentTool(definition, execute, options = {}) {
82
82
  return useAgentBinding(definition, execute, options);
83
83
  }
84
84
  function getAgentNavigationExecutor(executors, key) {
85
- if (!executors
86
- || typeof executors !== "object"
87
- || !Object.hasOwn(executors, key)) {
85
+ if (!executors || typeof executors !== "object" || !Object.hasOwn(executors, key)) {
88
86
  throw new Error(`Missing navigation executor: ${key}.`);
89
87
  }
90
88
  const executor = executors[key];
@@ -1,6 +1,8 @@
1
1
  import { assertAgentToolInputSchema } from "./shared/schema.js";
2
2
  export function registerAgentTool(definition, options) {
3
- if (!definition || typeof definition.name !== "string" || typeof options?.execute !== "function") {
3
+ if (!definition ||
4
+ typeof definition.name !== "string" ||
5
+ typeof options?.execute !== "function") {
4
6
  throw new TypeError("registerAgentTool requires a definition and execute function.");
5
7
  }
6
8
  if ("requiresConfirmation" in options) {
package/dist/runtime.d.ts CHANGED
@@ -14,7 +14,7 @@ export interface ToolRuntimeToolChangeEvent {
14
14
  readonly change: "registered" | "unregistered" | "visibility";
15
15
  }
16
16
  declare const CONTEXT_ERROR_CODES: readonly ["CONTEXT_DISPOSED", "CONTEXT_NAME_MISMATCH", "DUPLICATE_CONTEXT", "INVALID_CONTEXT"];
17
- type AgentContextErrorCode = typeof CONTEXT_ERROR_CODES[number];
17
+ type AgentContextErrorCode = (typeof CONTEXT_ERROR_CODES)[number];
18
18
  export declare function isToolRuntimeError(error: unknown): error is {
19
19
  readonly type: "tool_runtime_error";
20
20
  readonly domain: "tool" | "context";
package/dist/runtime.js CHANGED
@@ -2,13 +2,13 @@ import { searchNavigationTools, searchToolDefinition } from "./tool-search.js";
2
2
  import { pageDecisionSymbol } from "./shared/runtime-execution.js";
3
3
  import { PageCommandError } from "./browser/locator.js";
4
4
  import { measurePagePhase } from "./browser/diagnostics.js";
5
- import { runtimeControllerSymbol } from "./shared/runtime-controller.js";
5
+ import { runtimeControllerSymbol, } from "./shared/runtime-controller.js";
6
6
  import { AgentToolError, TOOL_ERROR_CODES } from "./shared/errors.js";
7
7
  export { AgentToolError } from "./shared/errors.js";
8
8
  import { OperationTracker } from "./operation-runtime.js";
9
9
  import { isValidAgentContextName } from "./definition.js";
10
10
  import { assertAgentToolInputSchema, validateAgentToolInput } from "./shared/schema.js";
11
- import { createBrowserToolset, } from "./browser/toolset.js";
11
+ import { createBrowserToolset } from "./browser/toolset.js";
12
12
  const CONTEXT_ERROR_CODES = [
13
13
  "CONTEXT_DISPOSED",
14
14
  "CONTEXT_NAME_MISMATCH",
@@ -21,9 +21,9 @@ export function isToolRuntimeError(error) {
21
21
  if (!error || typeof error !== "object")
22
22
  return false;
23
23
  const candidate = error;
24
- if (candidate.type !== "tool_runtime_error"
25
- || typeof candidate.message !== "string"
26
- || typeof candidate.code !== "string") {
24
+ if (candidate.type !== "tool_runtime_error" ||
25
+ typeof candidate.message !== "string" ||
26
+ typeof candidate.code !== "string") {
27
27
  return false;
28
28
  }
29
29
  if (candidate.domain === "tool") {
@@ -44,9 +44,7 @@ function assertContextName(name) {
44
44
  }
45
45
  }
46
46
  function normalizeContextValue(value, path = "value", seen = new WeakSet()) {
47
- if (value === null
48
- || typeof value === "string"
49
- || typeof value === "boolean") {
47
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
50
48
  return value;
51
49
  }
52
50
  if (typeof value === "number") {
@@ -83,8 +81,8 @@ function normalizeContextValue(value, path = "value", seen = new WeakSet()) {
83
81
  }
84
82
  function normalizeContext(context) {
85
83
  assertContextName(context.name);
86
- if (context.description !== undefined
87
- && (typeof context.description !== "string" || !context.description.trim())) {
84
+ if (context.description !== undefined &&
85
+ (typeof context.description !== "string" || !context.description.trim())) {
88
86
  throw new AgentContextError("INVALID_CONTEXT", "Context description must be a non-empty string when provided.");
89
87
  }
90
88
  return {
@@ -139,16 +137,22 @@ export class ToolRuntimeImplementation extends EventTarget {
139
137
  beginInternalOperation(options = {}) {
140
138
  return this.#operations.begin(options);
141
139
  }
142
- endBridgeOperation(handle) { return this.#operations.disposeBridge(handle); }
140
+ endBridgeOperation(handle) {
141
+ return this.#operations.disposeBridge(handle);
142
+ }
143
143
  setTaskProgress = (handle, progress) => this.#operations.setTaskProgress(handle, progress);
144
144
  setOperationWaiting = (handle, waiting) => this.#operations.setWaitingForUser(handle, waiting);
145
145
  getActivitySnapshot = () => this.#operations.getSnapshot();
146
146
  subscribeActivity = (listener) => this.#operations.subscribe(listener);
147
- requestOperationCancel(id) { this.#operations.requestCancel(id); }
147
+ requestOperationCancel(id) {
148
+ this.#operations.requestCancel(id);
149
+ }
148
150
  setPageActionFeedback(listener) {
149
151
  this.#pageActionFeedback = listener;
150
- return () => { if (this.#pageActionFeedback === listener)
151
- this.#pageActionFeedback = undefined; };
152
+ return () => {
153
+ if (this.#pageActionFeedback === listener)
154
+ this.#pageActionFeedback = undefined;
155
+ };
152
156
  }
153
157
  #emitExecution(event) {
154
158
  this.#operations.observe(event);
@@ -156,8 +160,7 @@ export class ToolRuntimeImplementation extends EventTarget {
156
160
  }
157
161
  constructor(options = {}) {
158
162
  super();
159
- if (options.pageAutomation !== undefined &&
160
- typeof options.pageAutomation !== "boolean") {
163
+ if (options.pageAutomation !== undefined && typeof options.pageAutomation !== "boolean") {
161
164
  throw new AgentToolError("INVALID_INPUT", "pageAutomation must be a boolean.");
162
165
  }
163
166
  if (options.routeSearch !== undefined && typeof options.routeSearch !== "boolean") {
@@ -172,7 +175,9 @@ export class ToolRuntimeImplementation extends EventTarget {
172
175
  if (searchQueries?.some((phrase) => typeof phrase !== "string" || !phrase.trim()))
173
176
  throw new AgentToolError("INVALID_INPUT", "Search queries must contain nonblank strings.");
174
177
  const navigation = this.getRegisteredTools().filter(({ definition }) => definition.effect === "navigate");
175
- const candidates = searchQueries === undefined ? navigation : searchNavigationTools(navigation, searchQueries.map((phrase) => phrase.trim()));
178
+ const candidates = searchQueries === undefined
179
+ ? navigation
180
+ : searchNavigationTools(navigation, searchQueries.map((phrase) => phrase.trim()));
176
181
  if (searchQueries !== undefined) {
177
182
  const previous = this.#visibleNavigation;
178
183
  this.#visibleNavigation = new Set(candidates);
@@ -185,7 +190,9 @@ export class ToolRuntimeImplementation extends EventTarget {
185
190
  })));
186
191
  }
187
192
  }
188
- return { tools: candidates.map(({ definition: { name, description } }) => ({ name, description })) };
193
+ return {
194
+ tools: candidates.map(({ definition: { name, description } }) => ({ name, description })),
195
+ };
189
196
  });
190
197
  this.#browserToolset = options.pageAutomation ? createBrowserToolset() : undefined;
191
198
  runtimeInternals.set(this, this);
@@ -227,9 +234,7 @@ export class ToolRuntimeImplementation extends EventTarget {
227
234
  definition,
228
235
  get confirmation() {
229
236
  const confirmation = options.getConfirmation?.();
230
- return confirmation === undefined
231
- ? undefined
232
- : Object.freeze({ ...confirmation });
237
+ return confirmation === undefined ? undefined : Object.freeze({ ...confirmation });
233
238
  },
234
239
  lifecycleSignal: lifecycleController.signal,
235
240
  });
@@ -238,9 +243,7 @@ export class ToolRuntimeImplementation extends EventTarget {
238
243
  definition,
239
244
  runtimeTool,
240
245
  execute,
241
- ...(options.getConfirmation
242
- ? { getConfirmation: options.getConfirmation }
243
- : {}),
246
+ ...(options.getConfirmation ? { getConfirmation: options.getConfirmation } : {}),
244
247
  lifecycleController,
245
248
  };
246
249
  this.#bindings.set(definition.name, binding);
@@ -378,7 +381,9 @@ export class ToolRuntimeImplementation extends EventTarget {
378
381
  getWebMcpTools() {
379
382
  return [...this.#bindings.values()]
380
383
  .map(({ runtimeTool }) => runtimeTool)
381
- .filter((tool) => !this.#routeSearch || tool.definition.effect !== "navigate" || this.#visibleNavigation.has(tool))
384
+ .filter((tool) => !this.#routeSearch ||
385
+ tool.definition.effect !== "navigate" ||
386
+ this.#visibleNavigation.has(tool))
382
387
  .sort((left, right) => compareStrings(left.definition.name, right.definition.name));
383
388
  }
384
389
  isRegistered(name) {
@@ -386,10 +391,8 @@ export class ToolRuntimeImplementation extends EventTarget {
386
391
  }
387
392
  getTools() {
388
393
  const applicationTools = this.getWebMcpTools();
389
- const browserTools = (this.#browserToolset?.definitions ?? [])
390
- .map((definition) => this.#getBrowserRuntimeTool(definition));
391
- return [...applicationTools, ...browserTools]
392
- .sort((left, right) => compareStrings(left.definition.name, right.definition.name));
394
+ const browserTools = (this.#browserToolset?.definitions ?? []).map((definition) => this.#getBrowserRuntimeTool(definition));
395
+ return [...applicationTools, ...browserTools].sort((left, right) => compareStrings(left.definition.name, right.definition.name));
393
396
  }
394
397
  waitForTool(definition, options = {}) {
395
398
  const isTargetRegistered = () => this.#bindings.has(definition.name);
@@ -412,8 +415,7 @@ export class ToolRuntimeImplementation extends EventTarget {
412
415
  };
413
416
  const onToolChange = (event) => {
414
417
  const detail = event.detail;
415
- if (detail?.toolName !== definition.name
416
- || detail.change !== "registered")
418
+ if (detail?.toolName !== definition.name || detail.change !== "registered")
417
419
  return;
418
420
  if (isTargetRegistered())
419
421
  settle(resolve);
@@ -439,7 +441,11 @@ export class ToolRuntimeImplementation extends EventTarget {
439
441
  }
440
442
  execute(tool, input, options = {}) {
441
443
  const label = options.progressLabel?.trim().replace(/\s+/g, " ").slice(0, 48);
442
- options = { ...(options.signal ? { signal: options.signal } : {}), ...(label ? { progressLabel: label } : {}), ...(options[pageDecisionSymbol] ? { [pageDecisionSymbol]: true } : {}) };
444
+ options = {
445
+ ...(options.signal ? { signal: options.signal } : {}),
446
+ ...(label ? { progressLabel: label } : {}),
447
+ ...(options[pageDecisionSymbol] ? { [pageDecisionSymbol]: true } : {}),
448
+ };
443
449
  const definition = tool.definition;
444
450
  const name = definition.name;
445
451
  const binding = this.#bindings.get(name);
@@ -455,8 +461,8 @@ export class ToolRuntimeImplementation extends EventTarget {
455
461
  if (!browserHandler) {
456
462
  return Promise.reject(new AgentToolError("UNKNOWN_TOOL", `Unknown tool: ${name}.`));
457
463
  }
458
- if (!browserToolset?.definitions.some((candidate) => candidate === definition)
459
- || this.#getBrowserRuntimeTool(definition) !== tool) {
464
+ if (!browserToolset?.definitions.some((candidate) => candidate === definition) ||
465
+ this.#getBrowserRuntimeTool(definition) !== tool) {
460
466
  return Promise.reject(new AgentToolError("CONTRACT_MISMATCH", `Tool "${name}" is registered with a different Definition.`));
461
467
  }
462
468
  return this.#enqueue((operationId) => this.#observeExecution(tool, input, options.signal, async (onFeedback, assertActive) => {
@@ -498,7 +504,9 @@ export class ToolRuntimeImplementation extends EventTarget {
498
504
  };
499
505
  // Reserve the tail before invoking executors or notifying synchronous listeners.
500
506
  let settleTail;
501
- const tail = new Promise((resolve) => { settleTail = resolve; });
507
+ const tail = new Promise((resolve) => {
508
+ settleTail = resolve;
509
+ });
502
510
  this.#executionTail = tail;
503
511
  const queuedExecution = previous ? previous.then(start) : start();
504
512
  let execution = queuedExecution;
@@ -530,9 +538,13 @@ export class ToolRuntimeImplementation extends EventTarget {
530
538
  const target = binding.resolveTarget?.(input);
531
539
  return target?.isConnected ? target : undefined;
532
540
  }
533
- if (this.#browserRuntimeTools.get(tool.definition) !== tool || !input || typeof input !== "object")
541
+ if (this.#browserRuntimeTools.get(tool.definition) !== tool ||
542
+ !input ||
543
+ typeof input !== "object")
534
544
  return undefined;
535
- return this.#browserToolset?.handlers.get(tool.definition.name)?.resolveTarget?.(input);
545
+ return this.#browserToolset?.handlers
546
+ .get(tool.definition.name)
547
+ ?.resolveTarget?.(input);
536
548
  }
537
549
  async #observeExecution(tool, input, signal, execute, progressLabel, operationId) {
538
550
  const running = {
@@ -549,7 +561,10 @@ export class ToolRuntimeImplementation extends EventTarget {
549
561
  const assertActive = () => {
550
562
  signal?.throwIfAborted();
551
563
  if (presentation && this.#pageActionFeedback !== presentation) {
552
- throw new PageCommandError({ code: "EXECUTION_CANCELLED", message: "Page action presentation was detached." });
564
+ throw new PageCommandError({
565
+ code: "EXECUTION_CANCELLED",
566
+ message: "Page action presentation was detached.",
567
+ });
553
568
  }
554
569
  };
555
570
  this.#emitExecution(running);
@@ -558,26 +573,33 @@ export class ToolRuntimeImplementation extends EventTarget {
558
573
  const result = await execute(async (feedback) => {
559
574
  assertActive();
560
575
  if (presentation)
561
- await measurePagePhase("presentation", async () => { await presentation(running, feedback, signal); });
576
+ await measurePagePhase("presentation", async () => {
577
+ await presentation(running, feedback, signal);
578
+ });
562
579
  assertActive();
563
580
  }, assertActive);
564
581
  signal?.throwIfAborted();
565
582
  this.#emitExecution({
566
- ...running, status: "success", finishedAt: Date.now(), result,
583
+ ...running,
584
+ status: "success",
585
+ finishedAt: Date.now(),
586
+ result,
567
587
  });
568
588
  return result;
569
589
  }
570
590
  catch (error) {
571
591
  this.#emitExecution({
572
- ...running, status: "error", cancelled: signal?.aborted === true,
573
- finishedAt: Date.now(), error,
592
+ ...running,
593
+ status: "error",
594
+ cancelled: signal?.aborted === true,
595
+ finishedAt: Date.now(),
596
+ error,
574
597
  });
575
598
  throw error;
576
599
  }
577
600
  }
578
601
  async #executeApplication(name, input, options, binding, operationId) {
579
- const id = globalThis.crypto?.randomUUID?.()
580
- ?? `${Date.now()}-${Math.random()}`;
602
+ const id = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
581
603
  const startedAt = Date.now();
582
604
  const running = {
583
605
  ...(operationId ? { operationId } : {}),
@@ -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;