@nexface/tools 0.1.0

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.
Files changed (82) hide show
  1. package/dist/browser/controller.d.ts +30 -0
  2. package/dist/browser/controller.js +783 -0
  3. package/dist/browser/controller.js.map +1 -0
  4. package/dist/browser/dom.d.ts +43 -0
  5. package/dist/browser/dom.js +438 -0
  6. package/dist/browser/dom.js.map +1 -0
  7. package/dist/browser/feedback.d.ts +4 -0
  8. package/dist/browser/feedback.js +5 -0
  9. package/dist/browser/feedback.js.map +1 -0
  10. package/dist/browser/locator.d.ts +19 -0
  11. package/dist/browser/locator.js +277 -0
  12. package/dist/browser/locator.js.map +1 -0
  13. package/dist/browser/prompt.d.ts +5 -0
  14. package/dist/browser/prompt.generated.d.ts +1 -0
  15. package/dist/browser/prompt.generated.js +50 -0
  16. package/dist/browser/prompt.generated.js.map +1 -0
  17. package/dist/browser/prompt.js +9 -0
  18. package/dist/browser/prompt.js.map +1 -0
  19. package/dist/browser/snapshot.d.ts +7 -0
  20. package/dist/browser/snapshot.js +424 -0
  21. package/dist/browser/snapshot.js.map +1 -0
  22. package/dist/browser/toolset.d.ts +35 -0
  23. package/dist/browser/toolset.js +383 -0
  24. package/dist/browser/toolset.js.map +1 -0
  25. package/dist/browser/types.d.ts +129 -0
  26. package/dist/browser/types.js +2 -0
  27. package/dist/browser/types.js.map +1 -0
  28. package/dist/browser/worker-runner.d.ts +11 -0
  29. package/dist/browser/worker-runner.js +400 -0
  30. package/dist/browser/worker-runner.js.map +1 -0
  31. package/dist/definition.d.ts +16 -0
  32. package/dist/definition.js +72 -0
  33. package/dist/definition.js.map +1 -0
  34. package/dist/index.d.ts +7 -0
  35. package/dist/index.js +4 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/internal-react.d.ts +12 -0
  38. package/dist/internal-react.js +23 -0
  39. package/dist/internal-react.js.map +1 -0
  40. package/dist/internal-types.d.ts +11 -0
  41. package/dist/internal-types.js +2 -0
  42. package/dist/internal-types.js.map +1 -0
  43. package/dist/internal.d.ts +9 -0
  44. package/dist/internal.js +5 -0
  45. package/dist/internal.js.map +1 -0
  46. package/dist/react.d.ts +25 -0
  47. package/dist/react.js +258 -0
  48. package/dist/react.js.map +1 -0
  49. package/dist/runtime.d.ts +93 -0
  50. package/dist/runtime.js +492 -0
  51. package/dist/runtime.js.map +1 -0
  52. package/dist/schema.d.ts +2 -0
  53. package/dist/schema.js +46 -0
  54. package/dist/schema.js.map +1 -0
  55. package/dist/types.d.ts +80 -0
  56. package/dist/types.js +2 -0
  57. package/dist/types.js.map +1 -0
  58. package/dist/webmcp.d.ts +2 -0
  59. package/dist/webmcp.js +73 -0
  60. package/dist/webmcp.js.map +1 -0
  61. package/package.json +26 -0
  62. package/src/browser/controller.ts +984 -0
  63. package/src/browser/dom.ts +507 -0
  64. package/src/browser/feedback.ts +5 -0
  65. package/src/browser/locator.ts +383 -0
  66. package/src/browser/prompt.generated.ts +50 -0
  67. package/src/browser/prompt.ts +10 -0
  68. package/src/browser/snapshot.ts +542 -0
  69. package/src/browser/toolset.ts +522 -0
  70. package/src/browser/types.ts +172 -0
  71. package/src/browser/worker-runner.ts +468 -0
  72. package/src/definition.ts +115 -0
  73. package/src/index.ts +35 -0
  74. package/src/internal-react.tsx +62 -0
  75. package/src/internal-types.ts +12 -0
  76. package/src/internal.ts +36 -0
  77. package/src/react.tsx +397 -0
  78. package/src/runtime.ts +722 -0
  79. package/src/schema.ts +52 -0
  80. package/src/types.ts +123 -0
  81. package/src/webmcp.ts +102 -0
  82. package/tsconfig.json +8 -0
@@ -0,0 +1,36 @@
1
+ export {
2
+ AgentContextError,
3
+ ToolRuntimeImplementation,
4
+ ToolRuntimeImplementation as ToolRuntime,
5
+ AgentToolError,
6
+ createToolRuntime,
7
+ getInternalToolRuntime,
8
+ } from "./runtime.js";
9
+ export type {
10
+ ToolRuntime as PublicToolRuntime,
11
+ ToolRuntimeOptions,
12
+ InternalToolRuntime,
13
+ } from "./runtime.js";
14
+ export type { AgentToolExecutionEvent } from "./internal-types.js";
15
+ export { defineSystemAgentTool } from "./definition.js";
16
+ export {
17
+ browserToolsAvailable,
18
+ createBrowserToolset,
19
+ executeCodeTool,
20
+ getSnapshotTool,
21
+ startPageTransitionTracker,
22
+ } from "./browser/toolset.js";
23
+ export {
24
+ BROWSER_TOOLS_SKILL,
25
+ serializeBrowserToolsSkill,
26
+ } from "./browser/prompt.js";
27
+ export type {
28
+ BrowserToolHandler,
29
+ BrowserToolsConfig,
30
+ BrowserToolsOptions,
31
+ BrowserToolset,
32
+ PageTransitionSettleResult,
33
+ PageTransitionTracker,
34
+ } from "./browser/toolset.js";
35
+ export type * from "./browser/types.js";
36
+ export type { AgentContextRegistration } from "./types.js";
package/src/react.tsx ADDED
@@ -0,0 +1,397 @@
1
+ import {
2
+ Children,
3
+ Fragment,
4
+ cloneElement,
5
+ useCallback,
6
+ useEffect,
7
+ useLayoutEffect,
8
+ useMemo,
9
+ useRef,
10
+ useState,
11
+ type MouseEvent,
12
+ type Ref,
13
+ type ReactElement,
14
+ type ReactNode,
15
+ } from "react";
16
+
17
+ import { defineAgentTool } from "./definition.js";
18
+ import { AgentToolError, getInternalToolRuntime } from "./runtime.js";
19
+ import {
20
+ InternalToolRuntimeScope,
21
+ useInternalToolRuntime,
22
+ } from "./internal-react.js";
23
+ import type { ToolRuntime } from "./runtime.js";
24
+ import type {
25
+ AgentContextEntry,
26
+ AgentContextRegistration,
27
+ AgentToolDefinition,
28
+ AgentToolExecutor,
29
+ } from "./types.js";
30
+ import { connectToolRuntimeToWebMcp } from "./webmcp.js";
31
+
32
+ function useAgentBinding<
33
+ TInput,
34
+ TResult,
35
+ >(
36
+ definition: AgentToolDefinition<TInput, TResult>,
37
+ execute: AgentToolExecutor<TInput, TResult>,
38
+ ): void {
39
+ const runtime = useInternalToolRuntime();
40
+ const executeRef = useRef(execute);
41
+ executeRef.current = execute;
42
+ useLayoutEffect(() => {
43
+ const controller = new AbortController();
44
+ runtime.register(
45
+ definition,
46
+ (input, context) => executeRef.current(input, context),
47
+ { signal: controller.signal },
48
+ );
49
+ return () => controller.abort();
50
+ }, [runtime, definition]);
51
+ }
52
+
53
+ export function useAgentTool<
54
+ TInput,
55
+ TResult,
56
+ >(
57
+ definition: AgentToolDefinition<TInput, TResult>,
58
+ execute: AgentToolExecutor<TInput, TResult>,
59
+ ): void {
60
+ useAgentBinding(definition, execute);
61
+ }
62
+
63
+ export function AgentToolsProvider({
64
+ children,
65
+ toolRuntime,
66
+ webMcp = true,
67
+ }: {
68
+ children: ReactNode;
69
+ toolRuntime: ToolRuntime;
70
+ webMcp?: boolean;
71
+ }) {
72
+ const runtime = getInternalToolRuntime(toolRuntime);
73
+ return (
74
+ <InternalToolRuntimeScope runtime={toolRuntime}>
75
+ <WebMcpRuntimeBridge enabled={webMcp} runtime={runtime} />
76
+ {children}
77
+ </InternalToolRuntimeScope>
78
+ );
79
+ }
80
+
81
+ function WebMcpRuntimeBridge({
82
+ enabled,
83
+ runtime,
84
+ }: {
85
+ enabled: boolean;
86
+ runtime: ReturnType<typeof getInternalToolRuntime>;
87
+ }) {
88
+ useEffect(() => {
89
+ if (!enabled) return undefined;
90
+ return connectToolRuntimeToWebMcp(runtime);
91
+ }, [enabled, runtime]);
92
+ return null;
93
+ }
94
+
95
+ export function useAgentContext(context: AgentContextEntry): void {
96
+ const runtime = useInternalToolRuntime();
97
+ const registrationRef = useRef<AgentContextRegistration | undefined>(undefined);
98
+ useLayoutEffect(() => {
99
+ const controller = new AbortController();
100
+ const registration = runtime.registerContext(context, { signal: controller.signal });
101
+ registrationRef.current = registration;
102
+ return () => {
103
+ if (registrationRef.current === registration) registrationRef.current = undefined;
104
+ controller.abort();
105
+ };
106
+ }, [runtime, context.name]);
107
+ useLayoutEffect(() => {
108
+ registrationRef.current?.update(context);
109
+ }, [runtime, context.name, context.description, context.value]);
110
+ }
111
+
112
+ export function AgentContext(context: AgentContextEntry): null {
113
+ useAgentContext(context);
114
+ return null;
115
+ }
116
+
117
+ function waitForActionResult<TResult>(
118
+ result: TResult | Promise<TResult>,
119
+ signal: AbortSignal,
120
+ ): Promise<TResult> {
121
+ if (signal.aborted) return Promise.reject(signal.reason);
122
+ return new Promise((resolve, reject) => {
123
+ const onAbort = () => {
124
+ signal.removeEventListener("abort", onAbort);
125
+ reject(signal.reason);
126
+ };
127
+ signal.addEventListener("abort", onAbort, { once: true });
128
+ Promise.resolve(result).then(
129
+ (value) => {
130
+ signal.removeEventListener("abort", onAbort);
131
+ resolve(value);
132
+ },
133
+ (error) => {
134
+ signal.removeEventListener("abort", onAbort);
135
+ reject(error);
136
+ },
137
+ );
138
+ });
139
+ }
140
+
141
+ export type AgentUIActionEvent = "click";
142
+
143
+ type Awaitable<TResult> = TResult | Promise<TResult>;
144
+
145
+ export interface AgentUIActionChildProps<TResult = void> {
146
+ onClick: (event: MouseEvent<HTMLElement>) => Awaitable<TResult>;
147
+ disabled?: boolean;
148
+ }
149
+
150
+ export interface AgentUIActionProps<TResult = void> {
151
+ name: string;
152
+ description: string;
153
+ event?: AgentUIActionEvent;
154
+ children: ReactElement<AgentUIActionChildProps<TResult>>;
155
+ }
156
+
157
+ type PendingInvocation<TResult> = {
158
+ resolve: (value: TResult | PromiseLike<TResult>) => void;
159
+ reject: (reason?: unknown) => void;
160
+ };
161
+
162
+ function setRefValue<T>(ref: Ref<T> | undefined, value: T | null): (() => void) | undefined {
163
+ if (typeof ref === "function") {
164
+ const cleanup = ref(value);
165
+ return typeof cleanup === "function" ? cleanup : undefined;
166
+ }
167
+ if (ref) ref.current = value;
168
+ }
169
+
170
+ function getElementRef(element: ReactElement): Ref<HTMLElement> | undefined {
171
+ const props = element.props as Record<string, unknown>;
172
+ const refDescriptor = Object.getOwnPropertyDescriptor(element, "ref");
173
+ if (refDescriptor && "value" in refDescriptor) {
174
+ return refDescriptor.value as Ref<HTMLElement> | undefined;
175
+ }
176
+ return (props as { ref?: Ref<HTMLElement> }).ref;
177
+ }
178
+
179
+ function isHTMLElement(value: unknown): value is HTMLElement {
180
+ if (!value || typeof value !== "object") return false;
181
+ const ownerDocument = (value as { ownerDocument?: Document }).ownerDocument;
182
+ const ElementConstructor =
183
+ ownerDocument?.defaultView?.HTMLElement ??
184
+ (typeof HTMLElement === "undefined" ? undefined : HTMLElement);
185
+ return ElementConstructor !== undefined && value instanceof ElementConstructor;
186
+ }
187
+
188
+ function warnAgentUIAction(message: string): void {
189
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
190
+ console.warn(message);
191
+ }
192
+ }
193
+
194
+ const eventAdapters: Record<AgentUIActionEvent, { invoke(target: HTMLElement): void }> = {
195
+ click: {
196
+ invoke(target) {
197
+ target.click();
198
+ },
199
+ },
200
+ };
201
+
202
+ export function AgentUIAction<TResult = void>({
203
+ name,
204
+ description,
205
+ event = "click",
206
+ children,
207
+ }: AgentUIActionProps<TResult>) {
208
+ const runtime = useInternalToolRuntime();
209
+ const child = Children.only(children) as ReactElement<AgentUIActionChildProps<TResult>>;
210
+ const isFragment = child.type === Fragment;
211
+ const handler = isFragment ? undefined : child.props.onClick;
212
+ const childDisabled = isFragment ? false : Boolean(child.props.disabled);
213
+ const childRef = isFragment ? undefined : getElementRef(child);
214
+ const [target, setTarget] = useState<HTMLElement | null>(null);
215
+ const targetRef = useRef<HTMLElement | null>(null);
216
+ const handlerRef = useRef(handler);
217
+ const disabledRef = useRef(childDisabled);
218
+ const pendingInvocationRef = useRef<PendingInvocation<TResult> | null>(null);
219
+ const invalidTargetRef = useRef(false);
220
+ const warnedTargetRef = useRef(false);
221
+ const warnedChildRef = useRef<"fragment" | "handler" | null>(null);
222
+ const invokingRef = useRef(false);
223
+
224
+ useLayoutEffect(() => {
225
+ handlerRef.current = handler;
226
+ disabledRef.current = childDisabled;
227
+ });
228
+
229
+ const mergedRef = useCallback(
230
+ (value: unknown) => {
231
+ const element = isHTMLElement(value) ? value : null;
232
+ const cleanupChildRef = setRefValue(childRef as Ref<unknown> | undefined, value);
233
+ invalidTargetRef.current = value !== null && !element;
234
+ targetRef.current = element;
235
+ setTarget(element);
236
+ if (element) warnedTargetRef.current = false;
237
+ if (cleanupChildRef) {
238
+ return () => {
239
+ cleanupChildRef();
240
+ if (targetRef.current === element) {
241
+ invalidTargetRef.current = false;
242
+ targetRef.current = null;
243
+ setTarget(null);
244
+ }
245
+ };
246
+ }
247
+ },
248
+ [childRef],
249
+ );
250
+
251
+ const invokeHandler = useCallback((mouseEvent: MouseEvent<HTMLElement>) => {
252
+ const currentHandler = handlerRef.current;
253
+ const pending = pendingInvocationRef.current;
254
+ if (pending) pendingInvocationRef.current = null;
255
+ if (!currentHandler) {
256
+ pending?.reject(
257
+ new AgentToolError(
258
+ "ACTION_NOT_HANDLED",
259
+ `AgentUIAction "${name}" does not have an onClick handler.`,
260
+ ),
261
+ );
262
+ return undefined as TResult;
263
+ }
264
+ try {
265
+ const result = currentHandler(mouseEvent);
266
+ if (pending) Promise.resolve(result).then(pending.resolve, pending.reject);
267
+ return result;
268
+ } catch (error) {
269
+ if (pending) {
270
+ pending.reject(error);
271
+ return undefined as TResult;
272
+ }
273
+ throw error;
274
+ }
275
+ }, [name]);
276
+
277
+ const definition = useMemo(
278
+ () =>
279
+ defineAgentTool<TResult>({
280
+ name,
281
+ description,
282
+ }),
283
+ [name, description],
284
+ );
285
+
286
+ useLayoutEffect(() => {
287
+ if (isFragment || !handler) {
288
+ const warningType = isFragment ? "fragment" : "handler";
289
+ if (warnedChildRef.current !== warningType) {
290
+ warnedChildRef.current = warningType;
291
+ warnAgentUIAction(
292
+ isFragment
293
+ ? `AgentUIAction "${name}" does not support React.Fragment children.`
294
+ : `AgentUIAction "${name}" needs a child with onClick.`,
295
+ );
296
+ }
297
+ return;
298
+ }
299
+ warnedChildRef.current = null;
300
+ if (!target) {
301
+ if (!targetRef.current && !warnedTargetRef.current) {
302
+ warnedTargetRef.current = true;
303
+ warnAgentUIAction(
304
+ invalidTargetRef.current
305
+ ? `AgentUIAction "${name}" needs its child ref to resolve to an HTMLElement; the Tool is not registered.`
306
+ : `AgentUIAction "${name}" needs its child to forward ref to an HTMLElement; the Tool is not registered.`,
307
+ );
308
+ }
309
+ return;
310
+ }
311
+
312
+ const controller = new AbortController();
313
+ runtime.register(
314
+ definition,
315
+ async (_input, context) => {
316
+ if (invokingRef.current) {
317
+ throw new AgentToolError(
318
+ "ACTION_IN_FLIGHT",
319
+ `AgentUIAction "${name}" is already being invoked.`,
320
+ );
321
+ }
322
+ if (disabledRef.current) {
323
+ throw new AgentToolError(
324
+ "TOOL_UNAVAILABLE",
325
+ `AgentUIAction "${name}" is disabled.`,
326
+ );
327
+ }
328
+ const currentTarget = targetRef.current;
329
+ if (!currentTarget || !currentTarget.isConnected) {
330
+ throw new AgentToolError(
331
+ "ACTION_TARGET_UNAVAILABLE",
332
+ `AgentUIAction "${name}" target is not available.`,
333
+ );
334
+ }
335
+ invokingRef.current = true;
336
+ let resolveInvocation!: (value: TResult | PromiseLike<TResult>) => void;
337
+ let rejectInvocation!: (reason?: unknown) => void;
338
+ const actionPromise = new Promise<TResult>((resolve, reject) => {
339
+ resolveInvocation = resolve;
340
+ rejectInvocation = reject;
341
+ });
342
+ const pendingInvocation = {
343
+ resolve: resolveInvocation,
344
+ reject: rejectInvocation,
345
+ };
346
+ pendingInvocationRef.current = pendingInvocation;
347
+ try {
348
+ eventAdapters[event].invoke(currentTarget);
349
+ } catch (error) {
350
+ if (pendingInvocationRef.current === pendingInvocation) {
351
+ pendingInvocationRef.current = null;
352
+ }
353
+ invokingRef.current = false;
354
+ throw error;
355
+ }
356
+ if (pendingInvocationRef.current === pendingInvocation) {
357
+ pendingInvocationRef.current = null;
358
+ invokingRef.current = false;
359
+ throw new AgentToolError(
360
+ "ACTION_NOT_HANDLED",
361
+ `AgentUIAction "${name}" click did not invoke its onClick handler.`,
362
+ );
363
+ }
364
+ try {
365
+ return await waitForActionResult(actionPromise, context.signal);
366
+ } finally {
367
+ if (context.signal.aborted) {
368
+ void actionPromise.then(
369
+ () => {
370
+ invokingRef.current = false;
371
+ },
372
+ () => {
373
+ invokingRef.current = false;
374
+ },
375
+ );
376
+ } else {
377
+ invokingRef.current = false;
378
+ }
379
+ }
380
+ },
381
+ { signal: controller.signal },
382
+ );
383
+ return () => controller.abort();
384
+ }, [runtime, definition, event, handler !== undefined, isFragment, name, target]);
385
+
386
+ if (isFragment) return child;
387
+ return cloneElement(child, {
388
+ onClick: invokeHandler,
389
+ ref: mergedRef,
390
+ } as AgentUIActionChildProps<TResult> & { ref: Ref<HTMLElement> });
391
+ }
392
+
393
+ export type {
394
+ AgentContextValue,
395
+ AgentToolDefinition,
396
+ AgentToolExecutor,
397
+ } from "./types.js";