@nxisai/nxis 2.1.2 → 2.2.1
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/.github/workflows/deploy.yml +45 -7
- package/README.md +185 -1
- package/build/api.d.ts +13 -2
- package/build/core.d.ts +1 -1
- package/build/nxis.js +1 -1
- package/build/webmcp/contract.d.ts +109 -0
- package/build/webmcp/execute.d.ts +24 -0
- package/build/webmcp/index.d.ts +37 -0
- package/build/webmcp/locate.d.ts +19 -0
- package/build/webmcp/parse.d.ts +20 -0
- package/build/webmcp/reconcile.d.ts +57 -0
- package/build/webmcp/register.d.ts +31 -0
- package/build/webmcp/schema.d.ts +30 -0
- package/build/webmcp/types.d.ts +72 -0
- package/build/webmcp.js +1 -0
- package/package.json +10 -2
- package/rollup.config.js +20 -1
- package/src/api.ts +13 -2
- package/src/client.ts +23 -13
- package/src/core.ts +1 -2
- package/src/webmcp/contract.ts +168 -0
- package/src/webmcp/execute.ts +514 -0
- package/src/webmcp/index.ts +51 -0
- package/src/webmcp/locate.ts +200 -0
- package/src/webmcp/parse.ts +276 -0
- package/src/webmcp/reconcile.ts +170 -0
- package/src/webmcp/register.ts +285 -0
- package/src/webmcp/schema.ts +201 -0
- package/src/webmcp/types.ts +86 -0
|
@@ -19,9 +19,13 @@ jobs:
|
|
|
19
19
|
with:
|
|
20
20
|
node-version: '20'
|
|
21
21
|
cache: 'npm'
|
|
22
|
+
registry-url: 'https://npm.pkg.github.com'
|
|
23
|
+
scope: '@nxisai'
|
|
22
24
|
|
|
23
25
|
- name: Install dependencies
|
|
24
26
|
run: npm ci
|
|
27
|
+
env:
|
|
28
|
+
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
25
29
|
|
|
26
30
|
- name: Build project
|
|
27
31
|
env:
|
|
@@ -59,6 +63,8 @@ jobs:
|
|
|
59
63
|
|
|
60
64
|
- name: Install dependencies
|
|
61
65
|
run: npm ci
|
|
66
|
+
env:
|
|
67
|
+
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
62
68
|
|
|
63
69
|
- name: Build SSR package
|
|
64
70
|
env:
|
|
@@ -78,6 +84,9 @@ jobs:
|
|
|
78
84
|
publish-npmjs:
|
|
79
85
|
needs: publish-npm
|
|
80
86
|
runs-on: ubuntu-latest
|
|
87
|
+
permissions:
|
|
88
|
+
contents: read
|
|
89
|
+
packages: read
|
|
81
90
|
|
|
82
91
|
steps:
|
|
83
92
|
- name: Checkout code
|
|
@@ -88,11 +97,28 @@ jobs:
|
|
|
88
97
|
with:
|
|
89
98
|
node-version: '20'
|
|
90
99
|
cache: 'npm'
|
|
91
|
-
|
|
92
|
-
|
|
100
|
+
|
|
101
|
+
- name: Write .npmrc (auth both registries)
|
|
102
|
+
run: |
|
|
103
|
+
if [ -z "$NPM_TOKEN" ]; then
|
|
104
|
+
echo "::error::NPM_TOKEN secret is empty"; exit 1
|
|
105
|
+
fi
|
|
106
|
+
echo "NPM_TOKEN length: ${#NPM_TOKEN}"
|
|
107
|
+
# Unquoted heredoc so the shell expands the token values into the file
|
|
108
|
+
cat > .npmrc <<EOF
|
|
109
|
+
@nxisai:registry=https://npm.pkg.github.com
|
|
110
|
+
//npm.pkg.github.com/:_authToken=${GH_TOKEN}
|
|
111
|
+
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
|
112
|
+
always-auth=true
|
|
113
|
+
EOF
|
|
114
|
+
env:
|
|
115
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
116
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
93
117
|
|
|
94
118
|
- name: Install dependencies
|
|
95
|
-
run:
|
|
119
|
+
run: |
|
|
120
|
+
npm ci
|
|
121
|
+
rm -f .npmrc
|
|
96
122
|
|
|
97
123
|
- name: Build package
|
|
98
124
|
env:
|
|
@@ -102,9 +128,21 @@ jobs:
|
|
|
102
128
|
npm run build:types
|
|
103
129
|
|
|
104
130
|
- name: Scope package for npm
|
|
105
|
-
run:
|
|
131
|
+
run: |
|
|
132
|
+
npm pkg set name='@nxisai/nxis'
|
|
133
|
+
npm pkg set publishConfig.registry='https://registry.npmjs.org'
|
|
106
134
|
|
|
107
|
-
- name:
|
|
108
|
-
run:
|
|
135
|
+
- name: Rewrite .npmrc for npmjs publish
|
|
136
|
+
run: |
|
|
137
|
+
# Drop the @nxisai->GitHub Packages scope mapping (only needed for install).
|
|
138
|
+
# For a scoped package, that mapping overrides publishConfig.registry and
|
|
139
|
+
# --registry, so it must be gone for the publish to route to npmjs.
|
|
140
|
+
cat > .npmrc <<EOF
|
|
141
|
+
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
|
142
|
+
always-auth=true
|
|
143
|
+
EOF
|
|
109
144
|
env:
|
|
110
|
-
|
|
145
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
146
|
+
|
|
147
|
+
- name: Publish to npm
|
|
148
|
+
run: npm publish --access public --registry https://registry.npmjs.org
|
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Nxis.js supports two integration paths — **client-side (browser)** and **serve
|
|
|
33
33
|
### Client-Side (Browser)
|
|
34
34
|
|
|
35
35
|
1. **Script Loading**: Your page loads `nxis.js` via a `<script>` tag from the Nxis CDN with a signed Nxis JWT token as a query parameter.
|
|
36
|
-
2. **Request**: On execution, the script sends a request to the Nxis AI Proxy (`/v1/nxis:inject`) with the current page URI
|
|
36
|
+
2. **Request**: On execution, the script sends a request to the Nxis AI Proxy (`/v1/nxis:inject`) with the current page URI and referrer.
|
|
37
37
|
3. **Validation & Retrieval**: The Proxy validates the JWT token, and retrieves JSON-LD structured data from the Structured Data Service.
|
|
38
38
|
4. **Injection**: On success, `nxis.js` creates a `<script type="application/ld+json">` element and appends it to the document `<head>`.
|
|
39
39
|
|
|
@@ -367,6 +367,190 @@ app.listen(3000);
|
|
|
367
367
|
|
|
368
368
|
---
|
|
369
369
|
|
|
370
|
+
## WebMCP — AI Agent Tool Registration
|
|
371
|
+
|
|
372
|
+
Nxis.js can automatically expose your site's forms as **WebMCP tools** that AI agents (Chrome 138+) can discover and invoke. When a page contains Nxis Action JSON-LD, the `@nxisai/nxis/webmcp` module reads each Action, converts it to a JSON Schema–described tool, and registers it via `document.modelContext.registerTool()`. The agent sees a callable tool — the library handles form location, field filling, event dispatching, and submission.
|
|
373
|
+
|
|
374
|
+
### How It Works
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
378
|
+
│ Page HTML │
|
|
379
|
+
│ │
|
|
380
|
+
│ <script type="application/ld+json"> │
|
|
381
|
+
│ { "@type": "SearchAction", "name": "search_products", ... } │
|
|
382
|
+
│ </script> │
|
|
383
|
+
│ │
|
|
384
|
+
│ <form action="/search" method="GET"> │
|
|
385
|
+
│ <input name="query" type="text" /> │
|
|
386
|
+
│ </form> │
|
|
387
|
+
└──────────────────────────┬──────────────────────────────────────────┘
|
|
388
|
+
│
|
|
389
|
+
registerActions()
|
|
390
|
+
│
|
|
391
|
+
┌──────────────┼──────────────────┐
|
|
392
|
+
▼ ▼ ▼
|
|
393
|
+
① Parse ② Schema ③ Register
|
|
394
|
+
Read Action Build JSON registerTool()
|
|
395
|
+
JSON-LD from Schema from on modelContext
|
|
396
|
+
<script> tags PVS descriptors
|
|
397
|
+
│ │ │
|
|
398
|
+
└──────────────┼──────────────────┘
|
|
399
|
+
│
|
|
400
|
+
Agent calls tool
|
|
401
|
+
│
|
|
402
|
+
┌──────────────┼──────────────────┐
|
|
403
|
+
▼ ▼ ▼
|
|
404
|
+
④ Locate ⑤ Fill ⑥ Submit
|
|
405
|
+
Find matching Set values via requestSubmit()
|
|
406
|
+
<form> on page native setters with validation
|
|
407
|
+
+ dispatch events
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
1. **Parse**: Reads all `<script type="application/ld+json">` blocks and extracts Action nodes (including subtypes like `SearchAction`, `RegisterAction`, etc.).
|
|
411
|
+
2. **Schema**: Converts each Action's `PropertyValueSpecification` descriptors into a JSON Schema `inputSchema` for the tool.
|
|
412
|
+
3. **Register**: Calls `document.modelContext.registerTool()` with the tool name, description, schema, and an `execute` callback.
|
|
413
|
+
4. **Locate**: When the agent invokes the tool, the execute callback locates the matching `<form>` using URL/method matching and field-set containment disambiguation.
|
|
414
|
+
5. **Fill**: Sets field values using native property setters (bypassing framework interceptors) and dispatches `input`/`change` events for React, Vue, Angular reactivity.
|
|
415
|
+
6. **Submit**: Calls `requestSubmit()` (respects validation and submit event handlers) and returns a structured JSON result so the agent can self-correct on failure.
|
|
416
|
+
|
|
417
|
+
### Browser Requirements
|
|
418
|
+
|
|
419
|
+
WebMCP requires `document.modelContext`, available natively in **Chrome 138+**. On older browsers, the `@nxisai/webmcp` polyfill is installed automatically — `registerActions()` gracefully no-ops in environments where WebMCP is unavailable (SSR, Node.js, workers).
|
|
420
|
+
|
|
421
|
+
### Installation
|
|
422
|
+
|
|
423
|
+
WebMCP is included in the main package:
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
npm install @nxisai/nxis
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Import from the `webmcp` subpath:
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
import { registerActions } from '@nxisai/nxis/webmcp';
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Quick Start
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
import { registerActions } from '@nxisai/nxis/webmcp';
|
|
439
|
+
|
|
440
|
+
// Register all Action tools found in the page's JSON-LD
|
|
441
|
+
const cleanup = registerActions();
|
|
442
|
+
|
|
443
|
+
// Later, to unregister all tools and disconnect observers:
|
|
444
|
+
cleanup();
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### API Reference
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
import { registerActions } from '@nxisai/nxis/webmcp';
|
|
451
|
+
import type { RegisterOptions } from '@nxisai/nxis/webmcp';
|
|
452
|
+
|
|
453
|
+
const cleanup = registerActions({
|
|
454
|
+
doc?: Document, // Document to scan (defaults to `document`)
|
|
455
|
+
signal?: AbortSignal, // External cancellation signal
|
|
456
|
+
observe?: boolean, // MutationObserver re-scan for SPAs (default: true)
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// cleanup() unregisters all tools and disconnects observers
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
#### Options
|
|
463
|
+
|
|
464
|
+
| Option | Type | Default | Description |
|
|
465
|
+
|--------|------|---------|-------------|
|
|
466
|
+
| `doc` | `Document` | `document` | The document to scan for JSON-LD blocks. Useful for iframes. |
|
|
467
|
+
| `signal` | `AbortSignal` | — | An external signal to cancel all registrations. |
|
|
468
|
+
| `observe` | `boolean` | `true` | When `true`, a `MutationObserver` watches for new JSON-LD insertions and auto-registers new tools. Set to `false` for static pages. |
|
|
469
|
+
|
|
470
|
+
### SPA Support
|
|
471
|
+
|
|
472
|
+
By default, `registerActions()` installs a `MutationObserver` that watches the entire document subtree for new `<script type="application/ld+json">` insertions. When your SPA navigates and Nxis injects new structured data, the observer automatically registers any new Action tools (de-duplicated by `@id`).
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
// SPAs: observer is enabled by default
|
|
476
|
+
const cleanup = registerActions();
|
|
477
|
+
|
|
478
|
+
// Static pages: opt out of the observer
|
|
479
|
+
const cleanup = registerActions({ observe: false });
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Supported Action Types
|
|
483
|
+
|
|
484
|
+
The backend classifier maps forms to schema.org Action subtypes:
|
|
485
|
+
|
|
486
|
+
| Action Type | Example Use Case |
|
|
487
|
+
|-------------|-----------------|
|
|
488
|
+
| `SearchAction` | Site search, product search |
|
|
489
|
+
| `RegisterAction` | Event registration, account sign-up |
|
|
490
|
+
| `SubscribeAction` | Newsletter opt-in, mailing list |
|
|
491
|
+
| `BuyAction` | Purchase, checkout |
|
|
492
|
+
| `OrderAction` | Food ordering, service booking |
|
|
493
|
+
| `ScheduleAction` | Appointment scheduling |
|
|
494
|
+
| `ReserveAction` | Reservation forms |
|
|
495
|
+
| `CommunicateAction` | Contact forms, feedback |
|
|
496
|
+
| `ApplyAction` | Job applications |
|
|
497
|
+
| `AskAction` | Q&A, support tickets |
|
|
498
|
+
| `QuoteAction` | Quote requests |
|
|
499
|
+
| `ReviewAction` | Reviews, ratings |
|
|
500
|
+
| `CommentAction` | Comment forms |
|
|
501
|
+
|
|
502
|
+
### CDN Usage (No Build Step)
|
|
503
|
+
|
|
504
|
+
For static sites, no manual configuration or local JavaScript setup is required. The Nxis CDN dynamically customizes the script for your site at request time:
|
|
505
|
+
|
|
506
|
+
1. When a browser requests `nxis.js?token=${TOKEN}`, the proxy decodes the HS256-signed JWT token.
|
|
507
|
+
2. It extracts your organization, site, and entitlement claims, including the **`webmcp`** claim which defines the WebMCP access configuration (e.g., `"browser"`, `"ssr"`, or `"none"`).
|
|
508
|
+
3. The proxy then prepends a dynamic initialization header directly into the served script file:
|
|
509
|
+
```javascript
|
|
510
|
+
window.__NXIS_CONFIG__ = {
|
|
511
|
+
org_id: "your_org_id",
|
|
512
|
+
site_id: "your_site_id",
|
|
513
|
+
sub_id: "your_subscription_id",
|
|
514
|
+
webmcp: { mode: "browser" } // Extracted from the JWT claim
|
|
515
|
+
};
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
To enable this on a static HTML page, simply add the standard CDN script tag to your document's `<head>`:
|
|
519
|
+
|
|
520
|
+
```html
|
|
521
|
+
<script
|
|
522
|
+
type="text/javascript"
|
|
523
|
+
async
|
|
524
|
+
src="https://cdn.nxis.ai/nxis.js?token=${TOKEN}"
|
|
525
|
+
crossorigin="anonymous">
|
|
526
|
+
</script>
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
When the script executes in the browser, it reads the prepended configuration from `window.__NXIS_CONFIG__`, fetches/injects the page's JSON-LD, and automatically registers your forms as WebMCP tools (if the `webmcp.mode` claim is set to `"browser"`, which is the default).
|
|
530
|
+
|
|
531
|
+
### Advanced Exports
|
|
532
|
+
|
|
533
|
+
For consumers who need lower-level access:
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
// Parse Action JSON-LD without registering tools
|
|
537
|
+
import { readActions } from '@nxisai/nxis/webmcp';
|
|
538
|
+
const actions = readActions(document);
|
|
539
|
+
|
|
540
|
+
// Build a JSON Schema from an Action (for testing or custom registration)
|
|
541
|
+
import { buildInputSchema } from '@nxisai/nxis/webmcp';
|
|
542
|
+
const schema = buildInputSchema(action);
|
|
543
|
+
|
|
544
|
+
// Contract types
|
|
545
|
+
import type {
|
|
546
|
+
NxisAction,
|
|
547
|
+
NxisEntryPoint,
|
|
548
|
+
NxisPropertyValueSpecification,
|
|
549
|
+
} from '@nxisai/nxis/webmcp';
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
370
554
|
## Adding Nxis SSR with Claude Code
|
|
371
555
|
|
|
372
556
|
You can use [Claude Code](https://docs.anthropic.com/en/docs/claude-code) to automatically add Nxis SSR to an existing Next.js project. Paste the following prompt into Claude Code from your project's root directory:
|
package/build/api.d.ts
CHANGED
|
@@ -34,6 +34,19 @@ export type NxisConfig = {
|
|
|
34
34
|
site_id: string;
|
|
35
35
|
/** The subscription identifier used for quota and entitlement checks. */
|
|
36
36
|
sub_id: string;
|
|
37
|
+
/**
|
|
38
|
+
* WebMCP configuration. Omit entirely to use defaults (`mode: "browser"`,
|
|
39
|
+
* `declarative: false`).
|
|
40
|
+
*/
|
|
41
|
+
webmcp?: {
|
|
42
|
+
/**
|
|
43
|
+
* Integration mode:
|
|
44
|
+
* - `"browser"` (default): Fetch JSON-LD from API, inject into page, register WebMCP tools.
|
|
45
|
+
* - `"ssr"`: Skip fetch/inject (server already rendered JSON-LD), only register WebMCP tools from existing DOM.
|
|
46
|
+
* - `"none"`: Fetch and inject JSON-LD for SEO, but do not register WebMCP tools.
|
|
47
|
+
*/
|
|
48
|
+
mode: "browser" | "ssr" | "none";
|
|
49
|
+
};
|
|
37
50
|
};
|
|
38
51
|
/**
|
|
39
52
|
* Parameters for a client-side structured data request sent to the
|
|
@@ -48,8 +61,6 @@ export type StructuredDataRequest = {
|
|
|
48
61
|
uri: HttpUri;
|
|
49
62
|
/** The `document.referrer` value, if available. */
|
|
50
63
|
referrer?: string;
|
|
51
|
-
/** A client-generated session ID for request correlation. */
|
|
52
|
-
session?: string;
|
|
53
64
|
};
|
|
54
65
|
/**
|
|
55
66
|
* The response body returned by both the `/v1/nxis:inject` and `/v1/nxis:ssr`
|
package/build/core.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { StructuredDataRequest, StructuredDataResponse } from './api';
|
|
|
4
4
|
* the client-side `/v1/nxis:inject` endpoint.
|
|
5
5
|
*
|
|
6
6
|
* @param request - The structured data request containing org, site, URI,
|
|
7
|
-
*
|
|
7
|
+
* and referrer info.
|
|
8
8
|
* @returns A `URLSearchParams` instance ready to be appended to the request URL.
|
|
9
9
|
*
|
|
10
10
|
* @internal
|
package/build/nxis.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(){const e={NXIS_API_HOST:"https://proxy.nxis.ai"};try{if(process)return process.env=Object.assign({},process.env),void Object.assign(process.env,e)}catch(e){}globalThis.process={env:e}}();class e extends Error{constructor(t){super(t),Object.setPrototypeOf(this,e.prototype)}}var t;!function(e){e[e.OK=0]="OK",e[e.INVALID_ARGUMENT=3]="INVALID_ARGUMENT",e[e.NOT_FOUND=5]="NOT_FOUND",e[e.PERMISSION_DENIED=7]="PERMISSION_DENIED",e[e.RESOURCE_EXHAUSTED=8]="RESOURCE_EXHAUSTED",e[e.INTERNAL=13]="INTERNAL",e[e.UNAUTHENTICATED=16]="UNAUTHENTICATED"}(t||(t={})),t.OK,t.INVALID_ARGUMENT,t.NOT_FOUND,t.PERMISSION_DENIED,t.RESOURCE_EXHAUSTED,t.INTERNAL,t.UNAUTHENTICATED;class n extends Error{constructor(e,t,r){super(r),this.code=e,this.httpStatus=t,this.name="ProxyError",Object.setPrototypeOf(this,n.prototype)}get isRetryable(){return this.code===t.RESOURCE_EXHAUSTED||this.code===t.INTERNAL}get isAuthError(){return this.code===t.UNAUTHENTICATED||this.code===t.PERMISSION_DENIED}static fromResponse(e,t){return new n(t.code,e,t.message)}}const r=[];for(let e=0;e<256;++e)r.push((e+256).toString(16).slice(1));const o=new Uint8Array(16);function s(e,t,n){return t||e||!crypto.randomUUID?function(e,t,n){e=e||{};const s=e.random??e.rng?.()??crypto.getRandomValues(o);if(s.length<16)throw new Error("Random bytes length must be >= 16");if(s[6]=15&s[6]|64,s[8]=63&s[8]|128,t){if((n=n||0)<0||n+16>t.length)throw new RangeError(`UUID byte range ${n}:${n+15} is out of buffer bounds`);for(let e=0;e<16;++e)t[n+e]=s[e];return t}return function(e,t=0){return(r[e[t+0]]+r[e[t+1]]+r[e[t+2]]+r[e[t+3]]+"-"+r[e[t+4]]+r[e[t+5]]+"-"+r[e[t+6]]+r[e[t+7]]+"-"+r[e[t+8]]+r[e[t+9]]+"-"+r[e[t+10]]+r[e[t+11]]+r[e[t+12]]+r[e[t+13]]+r[e[t+14]]+r[e[t+15]]).toLowerCase()}(s)}(e,t,n):crypto.randomUUID()}function i(e){if(e.data&&Object.keys(e.data).length>0){const t=document.createComment("Nxis.ai"),n=document.createComment("End Nxis.ai"),r=document.createElement("script");r.setAttribute("type","application/ld+json"),r.textContent=JSON.stringify(e.data),document.head.appendChild(r),r.before(t),r.after(n)}}function a(t){t instanceof e?console.warn(JSON.parse(t.message).message):console.error(`Unable to retrieve structured data for ${window.location.href}`)}!function(){window.NXIS_SESSION_ID=s();const t=process.env.NXIS_API_HOST,r=window.__NXIS_CONFIG__,o=new Headers,c=new URL(`${t}/v1/nxis:inject`);c.search=function(e){var t,n;const r=new URLSearchParams;return(null===(t=e.organization_id)||void 0===t?void 0:t.value)&&r.append("organization_id",e.organization_id.value),(null===(n=e.site_id)||void 0===n?void 0:n.value)&&r.append("site_id",e.site_id.value),r.append("uri",e.uri.href),e.referrer&&r.append("referrer",e.referrer),e.session&&r.append("session",e.session),r}({organization_id:{value:null==r?void 0:r.org_id},site_id:{value:null==r?void 0:r.site_id},uri:new URL(window.location.href),referrer:document.referrer,session:window.NXIS_SESSION_ID}).toString(),(null==r?void 0:r.sub_id)&&o.set("X-NXIS-SUBSCRIPTION",r.sub_id),o.set("Content-Type","application/json"),o.set("User-Agent",navigator.userAgent),fetch(`${c}`,{headers:o}).then(t=>async function(t,r){const o=null!=r?r:"unknown";if(200===t.status){const n=await t.json();if((null==n?void 0:n.data)&&Object.keys(n.data).length>0)return n;throw new e(JSON.stringify({message:`No Nxis resource found for ${o}`}))}const s=await async function(e){try{return await e.json()}catch(e){return null}}(t);if(void 0!==(null==s?void 0:s.code)&&(null==s?void 0:s.message))throw n.fromResponse(t.status,s);switch(t.status){case 500:throw new n(13,500,"An internal server error occurred, please contact technical support.");case 502:throw new n(13,502,"An internal server error occurred, please contact technical support.");case 503:throw new n(8,503,"Service is temporarily unavailable, please try again later.");default:throw new n(13,t.status,`Unexpected response status: ${t.status}`)}}(t,window.location.href)).then(i).catch(a)}();
|
|
1
|
+
!function(){const e={NXIS_API_HOST:"https://proxy.nxis.ai"};try{if(process)return process.env=Object.assign({},process.env),void Object.assign(process.env,e)}catch(e){}globalThis.process={env:e}}();class e extends Error{constructor(t){super(t),Object.setPrototypeOf(this,e.prototype)}}var t;!function(e){e[e.OK=0]="OK",e[e.INVALID_ARGUMENT=3]="INVALID_ARGUMENT",e[e.NOT_FOUND=5]="NOT_FOUND",e[e.PERMISSION_DENIED=7]="PERMISSION_DENIED",e[e.RESOURCE_EXHAUSTED=8]="RESOURCE_EXHAUSTED",e[e.INTERNAL=13]="INTERNAL",e[e.UNAUTHENTICATED=16]="UNAUTHENTICATED"}(t||(t={})),t.OK,t.INVALID_ARGUMENT,t.NOT_FOUND,t.PERMISSION_DENIED,t.RESOURCE_EXHAUSTED,t.INTERNAL,t.UNAUTHENTICATED;class n extends Error{constructor(e,t,o){super(o),this.code=e,this.httpStatus=t,this.name="ProxyError",Object.setPrototypeOf(this,n.prototype)}get isRetryable(){return this.code===t.RESOURCE_EXHAUSTED||this.code===t.INTERNAL}get isAuthError(){return this.code===t.UNAUTHENTICATED||this.code===t.PERMISSION_DENIED}static fromResponse(e,t){return new n(t.code,e,t.message)}}function o(e){return null===e?"null":Array.isArray(e)?"array":typeof e}function r(e,t){if(e===t)return!0;if(o(e)!==o(t))return!1;if(Array.isArray(e)&&Array.isArray(t))return e.length===t.length&&e.every((e,n)=>r(e,t[n]));if("object"===o(e)){const n=e,o=t,i=Object.keys(n);return i.length===Object.keys(o).length&&i.every(e=>r(n[e],o[e]))}return!1}function i(e,t,n="$"){if(!t||"object"!=typeof t)return[];const a=[];if(void 0===t.const||r(e,t.const)||a.push({path:n,message:`must equal const ${JSON.stringify(t.const)}`}),t.enum&&!t.enum.some(t=>r(e,t))&&a.push({path:n,message:`must be one of ${JSON.stringify(t.enum)}`}),void 0!==t.type){const r=Array.isArray(t.type)?t.type:[t.type];if(!r.some(t=>function(e,t){switch(t){case"object":return"object"===o(e);case"array":return Array.isArray(e);case"string":return"string"==typeof e;case"number":return"number"==typeof e&&Number.isFinite(e);case"integer":return"number"==typeof e&&Number.isInteger(e);case"boolean":return"boolean"==typeof e;case"null":return null===e;default:return!0}}(e,t)))return a.push({path:n,message:`expected type ${r.join(" | ")}, got ${o(e)}`}),a}if("string"==typeof e&&(void 0!==t.minLength&&e.length<t.minLength&&a.push({path:n,message:`must be at least ${t.minLength} characters`}),void 0!==t.maxLength&&e.length>t.maxLength&&a.push({path:n,message:`must be at most ${t.maxLength} characters`}),void 0!==t.pattern)){let o=null;try{o=new RegExp(t.pattern,"u")}catch{}o&&!o.test(e)&&a.push({path:n,message:`must match pattern ${t.pattern}`})}if("number"==typeof e&&(void 0!==t.minimum&&e<t.minimum&&a.push({path:n,message:`must be >= ${t.minimum}`}),void 0!==t.maximum&&e>t.maximum&&a.push({path:n,message:`must be <= ${t.maximum}`})),Array.isArray(e)&&(void 0!==t.minItems&&e.length<t.minItems&&a.push({path:n,message:`must have at least ${t.minItems} items`}),void 0!==t.maxItems&&e.length>t.maxItems&&a.push({path:n,message:`must have at most ${t.maxItems} items`}),t.items&&e.forEach((e,o)=>{a.push(...i(e,t.items,`${n}[${o}]`))})),"object"===o(e)&&!Array.isArray(e)&&null!==e){const o=e;for(const e of t.required??[])e in o||a.push({path:n,message:`missing required property "${e}"`});const r=t.properties??{};for(const[e,t]of Object.entries(r))e in o&&a.push(...i(o[e],t,`${n}.${e}`));if(!1===t.additionalProperties)for(const e of Object.keys(o))e in r||a.push({path:n,message:`unexpected property "${e}"`});else if(t.additionalProperties&&"object"==typeof t.additionalProperties)for(const e of Object.keys(o))e in r||a.push(...i(o[e],t.additionalProperties,`${n}.${e}`))}if(t.allOf)for(const o of t.allOf)a.push(...i(e,o,n));if(t.anyOf&&t.anyOf.length>0){t.anyOf.some(t=>0===i(e,t,n).length)||a.push({path:n,message:"does not match any schema in anyOf"})}if(t.oneOf&&t.oneOf.length>0){const o=t.oneOf.filter(t=>0===i(e,t,n).length).length;1!==o&&a.push({path:n,message:`must match exactly one schema in oneOf (matched ${o})`})}return a}var a,s,l,u,c,f,d=function(e,t,n,o){if("a"===n&&!o)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!o:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?o:"a"===n?o.call(e):o?o.value:t.get(e)},p=function(e,t,n,o,r){if("m"===o)throw new TypeError("Private method is not writable");if("a"===o&&!r)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?e!==t||!r:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===o?r.call(e,n):r?r.value=n:t.set(e,n),n};const m={type:"object"};function h(e,t){if("function"==typeof DOMException)return new DOMException(e,t);const n=new Error(e);return n.name=t,n}function v(e){if(!e||"object"!=typeof e)throw new TypeError("Tool descriptor must be an object.");if("string"!=typeof e.name||0===e.name.length)throw new TypeError("Tool descriptor requires a non-empty string 'name'.");if("string"!=typeof e.description)throw new TypeError(`Tool "${e.name}" requires a string 'description'.`);if("function"!=typeof e.execute)throw new TypeError(`Tool "${e.name}" requires an 'execute' function.`);if(void 0!==e.inputSchema&&"object"!=typeof e.inputSchema)throw new TypeError(`Tool "${e.name}": 'inputSchema' must be an object.`)}class g extends EventTarget{constructor(){super(...arguments),a.add(this),s.set(this,new Map),l.set(this,null),u.set(this,!1)}get ontoolchange(){return d(this,l,"f")}set ontoolchange(e){d(this,l,"f")&&this.removeEventListener("toolchange",d(this,l,"f")),p(this,l,"function"==typeof e?e:null,"f"),d(this,l,"f")&&this.addEventListener("toolchange",d(this,l,"f"))}registerTool(e,t={}){v(e);const{signal:n,exposedTo:o}=t;if(n?.aborted)return;d(this,a,"m",c).call(this,e.name);const r={tool:e,exposedTo:o?[...o]:void 0,pageLevel:!1};if(n){const t=()=>d(this,a,"m",c).call(this,e.name,r,!0);n.addEventListener("abort",t,{once:!0}),r.abortListener={signal:n,listener:t}}d(this,s,"f").set(e.name,r),d(this,a,"m",f).call(this)}provideContext(e={}){const t=e.tools??[];for(const e of t)v(e);if(new Set(t.map(e=>e.name)).size!==t.length)throw new TypeError("provideContext(): duplicate tool names in 'tools'.");for(const[e,t]of d(this,s,"f"))t.pageLevel&&d(this,a,"m",c).call(this,e);for(const e of t){if(d(this,s,"f").has(e.name))throw h(`provideContext(): tool "${e.name}" is already registered via registerTool().`,"InvalidStateError");d(this,s,"f").set(e.name,{tool:e,exposedTo:void 0,pageLevel:!0})}d(this,a,"m",f).call(this)}async getTools(e={}){e.fromOrigins;const t="undefined"!=typeof location?location.origin:void 0;return[...d(this,s,"f").values()].map(({tool:e})=>{const n={name:e.name,description:e.description,inputSchema:structuredClone(e.inputSchema??m)};return e.annotations&&(n.annotations=structuredClone(e.annotations)),t&&(n.origin=t),n})}async executeTool(e,t,n={}){const o=d(this,s,"f").get(e);if(!o)throw h(`No tool registered with name "${e}".`,"NotFoundError");if(n.signal?.aborted)throw h("Tool execution was aborted.","AbortError");let r;if(null==t||""===t)r={};else if("string"==typeof t)try{r=JSON.parse(t)}catch(t){throw new TypeError(`executeTool("${e}"): input is not valid JSON: ${t.message}`)}else r=t;if(null===r||"object"!=typeof r||Array.isArray(r))throw new TypeError(`executeTool("${e}"): input must be a JSON object.`);const a=i(r,o.tool.inputSchema??m);if(a.length>0){const t=a.map(e=>`${e.path}: ${e.message}`).join("; ");throw new TypeError(`executeTool("${e}"): invalid input — ${t}`)}const l=new AbortController,u=n.signal,c=()=>l.abort(u?.reason);u?.addEventListener("abort",c,{once:!0});try{const t=await o.tool.execute(r,{signal:l.signal,toolName:e});if(u?.aborted)throw h("Tool execution was aborted.","AbortError");return function(e){return null==e?{content:[]}:"string"==typeof e?{content:[{type:"text",text:e}]}:"object"==typeof e&&Array.isArray(e.content)?e:{content:[{type:"text",text:JSON.stringify(e)}]}}(t)}catch(e){if(u?.aborted||e instanceof Error&&"AbortError"===e.name)throw h("Tool execution was aborted.","AbortError");return function(e){return{isError:!0,content:[{type:"text",text:e instanceof Error?`${e.name}: ${e.message}`:String(e)}]}}(e)}finally{u?.removeEventListener("abort",c)}}unregisterTool(e){const t=d(this,a,"m",c).call(this,e);return t&&d(this,a,"m",f).call(this),t}clearContext(){if(0!==d(this,s,"f").size){for(const e of[...d(this,s,"f").keys()])d(this,a,"m",c).call(this,e);d(this,a,"m",f).call(this)}}}function y(e,t){Object.defineProperty(e,"modelContext",{value:t,writable:!1,enumerable:!0,configurable:!0})}s=new WeakMap,l=new WeakMap,u=new WeakMap,a=new WeakSet,c=function(e,t,n=!1){const o=d(this,s,"f").get(e);return!!o&&((!t||o===t)&&(o.abortListener&&o.abortListener.signal.removeEventListener("abort",o.abortListener.listener),d(this,s,"f").delete(e),n&&d(this,a,"m",f).call(this),!0))},f=function(){d(this,u,"f")||(p(this,u,!0,"f"),queueMicrotask(()=>{p(this,u,!1,"f"),this.dispatchEvent(new Event("toolchange"))}))};const b=["AskAction","SearchAction","SubscribeAction","RegisterAction","BuyAction","OrderAction","AddAction","ScheduleAction","ReserveAction","RsvpAction","ApplyAction","QuoteAction","DownloadAction","ReviewAction","CommentAction","TrackAction","CommunicateAction","InteractAction"];const w="https://nxis.ai/ns#";function E(e,t,n){var o;return null!==(o=e[t])&&void 0!==o?o:e[n]}function x(e,t,n){const o=E(e,t,n);return Array.isArray(o)?o:[]}function A(e){if(Array.isArray(e)){const t=[];for(const n of e)t.push(...A(n));return t}if(e&&"object"==typeof e){const t=e;if(Array.isArray(t["@graph"])){const e=[];for(const n of t["@graph"])e.push(...A(n));return e}return[t]}return[]}function N(e){if(!e||"object"!=typeof e)return null;const t=e,n="string"==typeof t["@type"]?t["@type"]:null;if(!n)return null;if(!n.endsWith("Action")&&!function(e){return b.includes(e)}(n))return null;const o="string"==typeof t["@id"]?t["@id"]:null,r="string"==typeof t.name?t.name:null,i=function(e){if(!e||"object"!=typeof e)return null;const t=e,n="string"==typeof t.httpMethod?t.httpMethod.toUpperCase():null;if(!n)return null;const o={"@type":"EntryPoint",httpMethod:n};"string"==typeof t.encodingType&&(o.encodingType=t.encodingType);"string"==typeof t.urlTemplate&&(o.urlTemplate=t.urlTemplate);const r=E(t,"nxis:targetKind",w+"targetKind");"selfPage"!==r&&"clientSide"!==r||(o["nxis:targetKind"]=r);const i=E(t,"nxis:pageUrl",w+"pageUrl");"string"==typeof i&&(o["nxis:pageUrl"]=i);const a=E(t,"nxis:jsHandled",w+"jsHandled");!0===a&&(o["nxis:jsHandled"]=!0);return o}(t.target);if(!o||!r||!i)return null;const a=x(t,"nxis:input",w+"input").map($).filter(e=>null!==e),s="string"==typeof t.description?t.description:function(e,t,n){const o=e.replace(/Action$/,"").toLowerCase(),r=n.map(e=>e.valueName).join(", ");if(0===n.length)return`Trigger the ${o} action "${t}".`;return`Fill the ${o} form "${t}" with fields: ${r}.`}(n,r,a);return{"@context":t["@context"],"@type":n,"@id":o,name:r,description:s,target:i,input:a.length>0?a:void 0,"nxis:extractedAt":E(t,"nxis:extractedAt",w+"extractedAt")}}function $(e){if(!e||"object"!=typeof e)return null;const t=e,n="string"==typeof t.valueName?t.valueName:null;if(!n)return null;const o=E(t,"nxis:valueType",w+"valueType"),r={"@type":"PropertyValueSpecification",valueName:n,"nxis:valueType":"string"==typeof o?o:"string"};"string"==typeof t.description&&(r.description=t.description),!0===t.valueRequired&&(r.valueRequired=!0),"string"==typeof t.defaultValue&&(r.defaultValue=t.defaultValue),"number"==typeof t.valueMinLength&&(r.valueMinLength=t.valueMinLength),"number"==typeof t.valueMaxLength&&(r.valueMaxLength=t.valueMaxLength),"number"==typeof t.minValue&&(r.minValue=t.minValue),"number"==typeof t.maxValue&&(r.maxValue=t.maxValue),"number"==typeof t.stepValue&&(r.stepValue=t.stepValue),"string"==typeof t.valuePattern&&(r.valuePattern=t.valuePattern),!0===t.multipleValues&&(r.multipleValues=!0),!0===t.readonlyValue&&(r.readonlyValue=!0);const i=x(t,"nxis:valueOption",w+"valueOption");if(i.length>0){const e=i.filter(e=>e&&"object"==typeof e&&"string"==typeof e.value).map(e=>{const t=e;return{"@type":"nxis:ValueOption",value:t.value,name:"string"==typeof t.name?t.name:void 0}});e.length>0&&(r["nxis:valueOption"]=e)}return r}function T(e){switch(e){case"string":case"date":case"file":default:return"string";case"number":return"number";case"integer":return"integer";case"boolean":return"boolean"}}function L(e){const t={type:T(e["nxis:valueType"])},n=function(e){if("date"===e)return"date"}(e["nxis:valueType"]);if(n&&(t.format=n),e.description&&(t.description=e.description),void 0!==e.defaultValue&&(t.default=function(e,t){switch(t){case"number":case"integer":{const t=Number(e);return isNaN(t)?e:t}case"boolean":return"true"===e||"1"===e;default:return e}}(e.defaultValue,e["nxis:valueType"])),void 0!==e.valueMinLength&&(t.minLength=e.valueMinLength),void 0!==e.valueMaxLength&&(t.maxLength=e.valueMaxLength),e.valuePattern){const n=e.valuePattern,o=n.startsWith("^")&&n.endsWith("$")?n:`^${n}$`;t.pattern=o}void 0!==e.minValue&&(t.minimum=e.minValue),void 0!==e.maxValue&&(t.maximum=e.maxValue),void 0!==e.stepValue&&(t.multipleOf=e.stepValue),e.readonlyValue&&(t.readOnly=!0);const o=e["nxis:valueOption"];if(o&&o.length>0){t.enum=o.map(e=>e.value);if(o.some(e=>void 0!==e.name&&e.name!==e.value)){const e=o.filter(e=>e.name&&e.name!==e.value).map(e=>`${e.value} = "${e.name}"`).join(", "),n=t.description?`${t.description}. `:"";t.description=`${n}Values: ${e}`}}return t}function O(e){const t=Array.from(document.querySelectorAll("form"));if(0===t.length)return null;const n=function(e){return e.input?e.input.map(e=>e.valueName):[]}(e);let o;if(o=!e.target.urlTemplate?t:function(e,t,n){const o=function(e){const t=e.replace(/\{[^}]*\}/g,"");try{return new URL(t,window.location.href)}catch(e){return null}}(t);return e.filter(e=>{if((e.method||"GET").toUpperCase()!==n.toUpperCase())return!1;if(!o)return!0;const t=function(e){const t=e.getAttribute("action");if(!t||"#"===t||t.startsWith("#")||""===t)return window.location.href.split("#")[0].split("?")[0];if(t.startsWith("javascript:"))return window.location.href.split("#")[0].split("?")[0];try{return new URL(t,window.location.href).href}catch(e){return t}}(e);return function(e,t){let n;try{n=new URL(e,window.location.href)}catch(e){return!1}if(n.origin!==t.origin)return!1;const o=n.pathname.replace(/\/+$/,""),r=t.pathname.replace(/\/+$/,"");return""===r||(o===r||o.startsWith(r+"/")||r.startsWith(o+"/"))}(t,o)})}(t,e.target.urlTemplate,e.target.httpMethod),0===o.length&&(o=t),1===o.length)return o[0];const r=o.map(e=>({form:e,controlNames:S(e)})).filter(({controlNames:e})=>n.every(t=>e.has(t)));return 0===r.length?(console.warn(`[nxis/webmcp] No field-set match for action "${e.name}". Expected fields: [${n.join(", ")}]. Falling back to first candidate.`),o[0]):1===r.length?r[0].form:(r.sort((e,t)=>e.controlNames.size-t.controlNames.size),r[0].controlNames.size===r[1].controlNames.size?(console.warn(`[nxis/webmcp] Ambiguous form match for action "${e.name}". ${r.length} forms have identical field sets. Returning null.`),null):r[0].form)}function S(e){const t=new Set,n=e.elements;for(let e=0;e<n.length;e++){const o=n[e];o.name&&t.add(o.name)}return t}function k(e,t){var n;return(null!==(n=e.input)&&void 0!==n?n:[]).map(e=>function(e,t){var n;const o=t=>({valueName:e.valueName,pvs:e,strategy:t}),r=function(e,t){const n=[],o=e.elements;for(let e=0;e<o.length;e++){const r=o[e];r.name===t&&n.push(r)}return n}(t,e.valueName),i=null!==(n=r[0])&&void 0!==n?n:null;if(!i)return o({kind:"skip",reason:`No control found with name="${e.valueName}"`});if(i instanceof HTMLInputElement&&"radio"===i.type){const t=r.filter(e=>e instanceof HTMLInputElement&&"radio"===e.type&&!e.disabled);return 0===t.length?o({kind:"skip",reason:`Radio group "${e.valueName}" has no enabled options`}):o({kind:"radio",name:e.valueName,elements:t})}if(i instanceof HTMLInputElement&&"checkbox"===i.type){const t=r.filter(e=>e instanceof HTMLInputElement&&"checkbox"===e.type&&!e.disabled);return 0===t.length?o({kind:"skip",reason:`Checkbox "${e.valueName}" is disabled`}):t.length>1?o({kind:"checkboxGroup",name:e.valueName,elements:t}):o({kind:"checked",element:t[0]})}if(!0===i.readOnly||null!==i.getAttribute("readonly"))return o({kind:"skip",reason:`Control "${e.valueName}" is readonly in the DOM`});if(i.disabled)return o({kind:"skip",reason:`Control "${e.valueName}" is disabled`});if(i instanceof HTMLSelectElement){const e=[];for(let t=0;t<i.options.length;t++){const n=i.options[t];n.value&&e.push(n.value)}return o(i.multiple?{kind:"multiSelect",element:i,allowedValues:e}:{kind:"select",element:i,allowedValues:e})}if(i instanceof HTMLTextAreaElement)return o({kind:"value",element:i});if(i instanceof HTMLInputElement)return o({kind:"value",element:i});return o({kind:"skip",reason:`Unsupported control type for "${e.valueName}"`})}(e,t))}async function j(e,t){var n,o;const r=function(e){const t=e.target["nxis:pageUrl"];if(!t)return null;try{const n=new URL(t),o=new URL(window.location.href);if(n.origin!==o.origin||n.pathname!==o.pathname)return`Action "${e.name}" was extracted from ${n.origin}${n.pathname} but the current page is ${o.origin}${o.pathname}. The form may have moved or changed.`}catch(e){return null}return null}(e),i=O(e);if(!i){const t=`Form not found for action "${e.name}". The page may have changed since the tool was registered.`,n={success:!1,submitted:!1,message:r?`Warning: ${r} ${t}`:t,filled:[],skipped:[]};return r&&(n.stale=!0),JSON.stringify(n)}const a=k(e,i),s=null!==(n=e.input)&&void 0!==n?n:[],l=[],u=[];let c=!1;for(const e of s)!e.valueRequired||void 0!==t[e.valueName]&&null!==t[e.valueName]||(c=!0,u.push({field:e.valueName,reason:`Required field '${e.valueName}' was not provided. Expected type: ${e["nxis:valueType"]}.`}));for(const e of a){const n=t[e.valueName];if(null==n){u.some(t=>t.field===e.valueName)||u.push({field:e.valueName,reason:"No value provided (optional field)."});continue}const o=I(e,n);!1===o.ok?u.push({field:e.valueName,reason:o.reason}):l.push(e.valueName)}await new Promise(e=>setTimeout(e,0));for(const e of a)if("value"===e.strategy.kind&&l.includes(e.valueName)){const n=e.strategy.element,r=M(null!==(o=t[e.valueName])&&void 0!==o?o:"");n.value!==r&&(u.push({field:e.valueName,reason:`Value was set to "${r}" but the DOM now shows "${n.value}". A framework may have reverted it.`}),l.splice(l.indexOf(e.valueName),1))}const f=l.length>0&&!c;let d=!1;f&&(d=function(e){try{return"function"==typeof e.requestSubmit?e.requestSubmit():e.submit(),!0}catch(e){return console.warn("[nxis/webmcp] Form submission failed:",e),!1}}(i));const p={success:f,submitted:d,message:C(l,u,d,r),filled:l,skipped:u};return r&&(p.stale=!0),JSON.stringify(p)}function C(e,t,n,o){const r=[];if(o&&r.push(`Warning: ${o}`),0===e.length&&0===t.length)return r.push("No fields were filled. Verify field names match the input schema."),r.join(" ");if(e.length>0&&r.push(`Filled ${e.length} field(s): ${e.join(", ")}.`),t.length>0){const e=t.map(e=>`${e.field} (${e.reason})`).join("; ");r.push(`Skipped ${t.length}: ${e}`)}return n?r.push("Form submitted."):e.length>0&&r.push("Form was NOT submitted due to missing required fields or fill errors."),r.join(" ")}function I(e,t){const{strategy:n}=e;switch(n.kind){case"skip":return{ok:!1,reason:n.reason};case"value":return function(e,t){var n;const o=null===(n=Object.getOwnPropertyDescriptor(e instanceof HTMLTextAreaElement?HTMLTextAreaElement.prototype:HTMLInputElement.prototype,"value"))||void 0===n?void 0:n.set;o?o.call(e,t):e.value=t;return e.dispatchEvent(new Event("input",{bubbles:!0})),e.dispatchEvent(new Event("change",{bubbles:!0})),{ok:!0}}(n.element,M(t));case"checked":return function(e,t){return e.checked=function(e){if("boolean"==typeof e)return e;if("number"==typeof e)return 0!==e;if("string"==typeof e){const t=e.trim().toLowerCase();return""!==t&&"false"!==t&&"0"!==t&&"off"!==t&&"no"!==t}return Boolean(e)}(t),e.dispatchEvent(new Event("change",{bubbles:!0})),{ok:!0}}(n.element,t);case"radio":return function(e,t){var n,o;const r=String(t),i=null!==(o=null!==(n=e.find(e=>e.value===r))&&void 0!==n?n:e.find(e=>e.value.toLowerCase()===r.toLowerCase()))&&void 0!==o?o:e.find(e=>function(e){var t;if(e.labels&&e.labels.length>0)return(null!==(t=e.labels[0].textContent)&&void 0!==t?t:"").trim();return e.value}(e).toLowerCase()===r.toLowerCase());if(!i){const t=e.map(e=>e.value).join(", ");return{ok:!1,reason:`Value "${r}" is not an allowed option for radio "${e[0].name}". Allowed: [${t}].`}}return i.checked=!0,i.dispatchEvent(new Event("input",{bubbles:!0})),i.dispatchEvent(new Event("change",{bubbles:!0})),{ok:!0}}(n.elements,t);case"checkboxGroup":return function(e,t){const n=(Array.isArray(t)?t:[t]).map(e=>String(e)),o=n.map(e=>e.toLowerCase());let r=!1;for(const t of e){const e=n.includes(t.value)||o.includes(t.value.toLowerCase());t.checked=e,e&&(r=!0),t.dispatchEvent(new Event("change",{bubbles:!0}))}if(!r){const t=e.map(e=>e.value).join(", ");return{ok:!1,reason:`None of [${n.join(", ")}] match checkbox group "${e[0].name}". Allowed: [${t}].`}}return{ok:!0}}(n.elements,t);case"select":return function(e,t,n){const o=R(e,t,n);if(null===o)return{ok:!1,reason:`Value "${t}" is not an allowed option for "${e.name}". Allowed: [${n.join(", ")}].`};return e.value=o,e.dispatchEvent(new Event("input",{bubbles:!0})),e.dispatchEvent(new Event("change",{bubbles:!0})),{ok:!0}}(n.element,String(t),n.allowedValues);case"multiSelect":return function(e,t,n){const o=(Array.isArray(t)?t:[t]).map(e=>String(e)),r=new Set,i=[];for(const t of o){const o=R(e,t,n);null===o?i.push(t):r.add(o)}if(0===r.size)return{ok:!1,reason:`None of [${o.join(", ")}] are allowed options for "${e.name}". Allowed: [${n.join(", ")}].`};for(const t of Array.from(e.options))t.selected=r.has(t.value);if(e.dispatchEvent(new Event("input",{bubbles:!0})),e.dispatchEvent(new Event("change",{bubbles:!0})),i.length>0)return{ok:!1,reason:`Selected [${Array.from(r).join(", ")}] but [${i.join(", ")}] are not allowed options for "${e.name}". Allowed: [${n.join(", ")}].`};return{ok:!0}}(n.element,t,n.allowedValues);default:return{ok:!1,reason:`Unsupported fill strategy for field "${e.valueName}".`}}}function M(e){return Array.isArray(e)?e.map(e=>String(e)).join(","):String(e)}function R(e,t,n){if(n.includes(t))return t;const o=n.find(e=>e.toLowerCase()===t.toLowerCase());if(o)return o;for(const n of Array.from(e.options))if(n.text.toLowerCase()===t.toLowerCase())return n.value;return null}function _(e){var t;const n=!1!==(null==e?void 0:e.observe),o=null!==(t=null==e?void 0:e.doc)&&void 0!==t?t:"undefined"!=typeof document?document:void 0;if(!o)return console.warn("[nxis/webmcp] No document available (not running in a browser). Tools will not be registered."),()=>{};!function(e={}){const{force:t=!1,installNavigatorAlias:n=!0}=e,o="undefined"!=typeof document&&document.modelContext||"undefined"!=typeof navigator&&navigator.modelContext||void 0;if(o&&!t)return o;const r=new g;"undefined"!=typeof document&&y(document,r),n&&"undefined"!=typeof navigator&&y(navigator,r)}();const r=function(e){var t,n;const o=null!==(n=null===(t=e.defaultView)||void 0===t?void 0:t.navigator)&&void 0!==n?n:"undefined"!=typeof navigator?navigator:void 0;return e.modelContext||(null==o?void 0:o.modelContext)||null}(o);if(!r)return console.warn("[nxis/webmcp] WebMCP API not available — document.modelContext is undefined. Tools will not be registered."),()=>{};const i=new AbortController;(null==e?void 0:e.signal)&&(e.signal.aborted?i.abort():e.signal.addEventListener("abort",()=>i.abort()));const a={ids:new Set,names:new Set};U(o,r,i,a);let s=null;return n&&(s=function(e,t,n,o){const r=new MutationObserver(r=>{(function(e){for(const t of e)for(const e of Array.from(t.addedNodes)){if(P(e))return!0;if(e instanceof Element&&e.querySelector('script[type="application/ld+json"]'))return!0}return!1})(r)&&U(e,t,n,o)});return n.signal.addEventListener("abort",()=>r.disconnect()),r.observe(e.documentElement,{childList:!0,subtree:!0}),r}(o,r,i,a)),()=>{i.abort(),s&&(s.disconnect(),s=null)}}function U(e,t,n,o){const r=function(e=("undefined"!=typeof document?document:void 0)){const t=[];if(!e)return t;const n=e.querySelectorAll('script[type="application/ld+json"]');for(const e of Array.from(n)){const n=e.textContent;if(!n)continue;let o;try{o=JSON.parse(n)}catch(e){continue}const r=A(o);for(const e of r){const n=N(e);n&&t.push(n)}}return t}(e);let i=0;for(const e of r)if(!o.ids.has(e["@id"]))try{V(e,t,n,o),o.ids.add(e["@id"]),i++}catch(t){console.warn(`[nxis/webmcp] Failed to register tool for action "${e.name}":`,t)}i>0&&console.info(`[nxis/webmcp] Registered ${i} tool(s) (${o.ids.size} total) from ${r.length} action(s).`)}function V(e,t,n,o){const r=function(e){var t;const n={},o=[],r=null!==(t=e.input)&&void 0!==t?t:[];for(const e of r){const t=L(e);if(e.multipleValues){const o={type:"array",items:t};void 0!==e.valueMinLength&&(o.minItems=e.valueMinLength,delete t.minLength),void 0!==e.valueMaxLength&&(o.maxItems=e.valueMaxLength,delete t.maxLength),e.description&&(o.description=e.description,delete t.description),n[e.valueName]=o}else n[e.valueName]=t;e.valueRequired&&o.push(e.valueName)}const i={type:"object",properties:n};return o.length>0&&(i.required=o),i}(e),i=function(e,t){if(!t.has(e))return e;for(let n=2;;n++){const o=`_${n}`,r=e.slice(0,64-o.length)+o;if(!t.has(r))return r}}(function(e){return e.toLowerCase().replace(/[\s-]+/g,"_").replace(/[^a-z0-9_]/g,"_").replace(/_+/g,"_").replace(/^_|_$/g,"").slice(0,64)||"unnamed_action"}(e.name),o.names);t.registerTool({name:i,description:e.description,inputSchema:r,annotations:{readOnlyHint:!1,untrustedContentHint:!0},execute:async t=>j(e,t)},{signal:n.signal}),o.names.add(i)}function P(e){return e instanceof HTMLScriptElement&&"application/ld+json"===e.type}function D(t){t instanceof e?console.warn(JSON.parse(t.message).message):console.error(`Unable to retrieve structured data for ${window.location.href}`)}!function(){var t,o;const r=window.__NXIS_CONFIG__,i=null!==(o=null===(t=null==r?void 0:r.webmcp)||void 0===t?void 0:t.mode)&&void 0!==o?o:"browser";if("ssr"===i)return void _();const a=process.env.NXIS_API_HOST,s=new Headers,l=new URL(`${a}/v1/nxis:inject`);l.search=function(e){var t,n;const o=new URLSearchParams;return(null===(t=e.organization_id)||void 0===t?void 0:t.value)&&o.append("organization_id",e.organization_id.value),(null===(n=e.site_id)||void 0===n?void 0:n.value)&&o.append("site_id",e.site_id.value),o.append("uri",e.uri.href),e.referrer&&o.append("referrer",e.referrer),o}({organization_id:{value:null==r?void 0:r.org_id},site_id:{value:null==r?void 0:r.site_id},uri:new URL(window.location.href),referrer:document.referrer}).toString(),(null==r?void 0:r.sub_id)&&s.set("X-NXIS-SUBSCRIPTION",r.sub_id),s.set("Content-Type","application/json"),s.set("User-Agent",navigator.userAgent),fetch(`${l}`,{headers:s}).then(t=>async function(t,o){const r=null!=o?o:"unknown";if(200===t.status){const n=await t.json();if((null==n?void 0:n.data)&&Object.keys(n.data).length>0)return n;throw new e(JSON.stringify({message:`No Nxis resource found for ${r}`}))}const i=await async function(e){try{return await e.json()}catch(e){return null}}(t);if(void 0!==(null==i?void 0:i.code)&&(null==i?void 0:i.message))throw n.fromResponse(t.status,i);switch(t.status){case 500:throw new n(13,500,"An internal server error occurred, please contact technical support.");case 502:throw new n(13,502,"An internal server error occurred, please contact technical support.");case 503:throw new n(8,503,"Service is temporarily unavailable, please try again later.");default:throw new n(13,t.status,`Unexpected response status: ${t.status}`)}}(t,window.location.href)).then(e=>{!function(e){if(e.data&&Object.keys(e.data).length>0){const t=document.createComment("Nxis.ai"),n=document.createComment("End Nxis.ai"),o=document.createElement("script");o.setAttribute("type","application/ld+json"),o.textContent=JSON.stringify(e.data),document.head.appendChild(o),o.before(t),o.after(n)}}(e),"browser"===i&&_()}).catch(D)}();
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire-shape types matching the backend JsonLdEmitter output.
|
|
3
|
+
*
|
|
4
|
+
* These types describe the JSON-LD that NxisAI's Action Specialist emits
|
|
5
|
+
* into `<script type="application/ld+json">` blocks. They are the single
|
|
6
|
+
* source of truth for the frontend consumer — any change here is a
|
|
7
|
+
* versioned contract change.
|
|
8
|
+
*
|
|
9
|
+
* @see JsonLdEmitter.scala in nxis-autofill-runner
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
/** The closed set of inferred value types emitted by the backend. */
|
|
13
|
+
export type NxisValueType = "string" | "number" | "integer" | "boolean" | "date" | "file";
|
|
14
|
+
/** A single option value for select/radio fields. */
|
|
15
|
+
export interface NxisValueOption {
|
|
16
|
+
"@type": "nxis:ValueOption";
|
|
17
|
+
value: string;
|
|
18
|
+
/** Human-readable label. Absent when label === value. */
|
|
19
|
+
name?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Mirrors schema.org PropertyValueSpecification + nxis extensions.
|
|
23
|
+
*
|
|
24
|
+
* Every field is an inferred descriptor — the consumer verifies
|
|
25
|
+
* against the live DOM control.
|
|
26
|
+
*/
|
|
27
|
+
export interface NxisPropertyValueSpecification {
|
|
28
|
+
"@type": "PropertyValueSpecification";
|
|
29
|
+
/** REQUIRED — property key, maps to form control `name`. */
|
|
30
|
+
valueName: string;
|
|
31
|
+
/** REQUIRED — inferred from HTML input[type]; consumer verifies against DOM. */
|
|
32
|
+
"nxis:valueType": NxisValueType;
|
|
33
|
+
/** Human-readable description (from label or placeholder). */
|
|
34
|
+
description?: string;
|
|
35
|
+
/** Inferred from HTML `required` attribute. */
|
|
36
|
+
valueRequired?: boolean;
|
|
37
|
+
/** Inferred from HTML `value` attribute. */
|
|
38
|
+
defaultValue?: string;
|
|
39
|
+
/** Inferred from HTML `minlength`. */
|
|
40
|
+
valueMinLength?: number;
|
|
41
|
+
/** Inferred from HTML `maxlength`. */
|
|
42
|
+
valueMaxLength?: number;
|
|
43
|
+
/** Inferred from HTML `min`. */
|
|
44
|
+
minValue?: number;
|
|
45
|
+
/** Inferred from HTML `max`. */
|
|
46
|
+
maxValue?: number;
|
|
47
|
+
/** Inferred from HTML `step`. */
|
|
48
|
+
stepValue?: number;
|
|
49
|
+
/** Inferred from HTML `pattern` — emitted UNANCHORED; consumer anchors with ^…$. */
|
|
50
|
+
valuePattern?: string;
|
|
51
|
+
/** Inferred from HTML `multiple`. */
|
|
52
|
+
multipleValues?: boolean;
|
|
53
|
+
/** Inferred from HTML `readonly`. */
|
|
54
|
+
readonlyValue?: boolean;
|
|
55
|
+
/** REQUIRED for select/radio — actual option values from DOM. */
|
|
56
|
+
"nxis:valueOption"?: NxisValueOption[];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Mirrors schema.org EntryPoint + nxis extensions.
|
|
60
|
+
*
|
|
61
|
+
* The backend's `SubmitTarget` discriminator is expressed as:
|
|
62
|
+
* - `urlTemplate` present → Url target (real server endpoint)
|
|
63
|
+
* - `nxis:targetKind` present → endpointless (SelfPage or ClientSide)
|
|
64
|
+
*
|
|
65
|
+
* These are mutually exclusive in the emitted JSON-LD.
|
|
66
|
+
*/
|
|
67
|
+
export interface NxisEntryPoint {
|
|
68
|
+
"@type": "EntryPoint";
|
|
69
|
+
/** Form method (GET or POST), uppercase. */
|
|
70
|
+
httpMethod: string;
|
|
71
|
+
/** Form enctype (e.g., "application/x-www-form-urlencoded"). */
|
|
72
|
+
encodingType?: string;
|
|
73
|
+
/** Real, resolvable server endpoint. May include RFC 6570 template vars for GET. */
|
|
74
|
+
urlTemplate?: string;
|
|
75
|
+
/** Discriminator for endpointless forms. */
|
|
76
|
+
"nxis:targetKind"?: "selfPage" | "clientSide";
|
|
77
|
+
/** Page URL for staleness checking. */
|
|
78
|
+
"nxis:pageUrl"?: string;
|
|
79
|
+
/** Evidence that a JS submit/click listener exists on the form. */
|
|
80
|
+
"nxis:jsHandled"?: boolean;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* A schema.org Action node as emitted by the backend JsonLdEmitter.
|
|
84
|
+
*
|
|
85
|
+
* Found in `<script type="application/ld+json">` blocks, either standalone
|
|
86
|
+
* or nested within a `@graph` array alongside other schema.org types.
|
|
87
|
+
*/
|
|
88
|
+
export interface NxisAction {
|
|
89
|
+
"@context": unknown;
|
|
90
|
+
/** Action or a subtype (e.g., "SearchAction", "CommunicateAction"). */
|
|
91
|
+
"@type": string;
|
|
92
|
+
/** Provenance/identity URN. NOT a DOM locator. */
|
|
93
|
+
"@id": string;
|
|
94
|
+
/** Human-readable action name (becomes the tool name). */
|
|
95
|
+
name: string;
|
|
96
|
+
/** Human-readable action description (becomes the tool description). */
|
|
97
|
+
description: string;
|
|
98
|
+
/** EntryPoint describing how to invoke the action. */
|
|
99
|
+
target: NxisEntryPoint;
|
|
100
|
+
/** Inferred field descriptors (may be empty for actions with no inputs). */
|
|
101
|
+
input?: NxisPropertyValueSpecification[];
|
|
102
|
+
/** Crawl timestamp — consumer uses to assess staleness. */
|
|
103
|
+
"nxis:extractedAt"?: string;
|
|
104
|
+
}
|
|
105
|
+
/** The set of Action subtypes the backend classifier emits. */
|
|
106
|
+
export declare const ACTION_SUBTYPES: readonly ["AskAction", "SearchAction", "SubscribeAction", "RegisterAction", "BuyAction", "OrderAction", "AddAction", "ScheduleAction", "ReserveAction", "RsvpAction", "ApplyAction", "QuoteAction", "DownloadAction", "ReviewAction", "CommentAction", "TrackAction", "CommunicateAction", "InteractAction"];
|
|
107
|
+
export type ActionSubtype = typeof ACTION_SUBTYPES[number];
|
|
108
|
+
/** Check if a string is a known Action subtype. */
|
|
109
|
+
export declare function isActionType(type: string): type is ActionSubtype;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execute — fills a located form with the provided arguments and submits it.
|
|
3
|
+
*
|
|
4
|
+
* Dispatches `input` and `change` events to trigger framework reactivity,
|
|
5
|
+
* then submits the form via `requestSubmit()` (respects validation and
|
|
6
|
+
* submit event handlers).
|
|
7
|
+
*
|
|
8
|
+
* Returns a structured JSON result with a `success` flag and actionable
|
|
9
|
+
* `message` so the agent can self-correct on failure.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
import { NxisAction } from './contract';
|
|
14
|
+
/**
|
|
15
|
+
* Execute a form fill action.
|
|
16
|
+
*
|
|
17
|
+
* Locates the form, reconciles fields, fills values, dispatches events,
|
|
18
|
+
* waits for framework reactivity to settle, then verifies values persisted.
|
|
19
|
+
*
|
|
20
|
+
* @param action - The NxisAction describing the form.
|
|
21
|
+
* @param args - The argument values to fill, keyed by valueName.
|
|
22
|
+
* @returns A JSON string describing the fill result.
|
|
23
|
+
*/
|
|
24
|
+
export declare function executeAction(action: NxisAction, args: Record<string, unknown>): Promise<string>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
*
|
|
4
|
+
* `nxis/webmcp` — WebMCP tool registration for NxisAI structured data.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* This subpath export reads Action + EntryPoint JSON-LD injected by the
|
|
8
|
+
* NxisAI backend and registers each Action as a WebMCP tool via
|
|
9
|
+
* `document.modelContext.registerTool`. Each tool's `execute` locates the
|
|
10
|
+
* real `<form>` on the live page and fills it.
|
|
11
|
+
*
|
|
12
|
+
* **Browser-only.** This module requires `document.modelContext` (Chrome 149+).
|
|
13
|
+
* It gracefully no-ops in environments where WebMCP is unavailable.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { registerActions } from 'nxis/webmcp';
|
|
18
|
+
*
|
|
19
|
+
* // Auto-register all Action tools found in the page's JSON-LD
|
|
20
|
+
* const cleanup = registerActions();
|
|
21
|
+
*
|
|
22
|
+
* // Later, to unregister all tools:
|
|
23
|
+
* cleanup();
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* // Opt-out of MutationObserver re-scan for SPAs
|
|
29
|
+
* const cleanup = registerActions({ observe: false });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export { registerActions } from './register';
|
|
33
|
+
export type { RegisterOptions } from './register';
|
|
34
|
+
export type { NxisAction, NxisEntryPoint, NxisPropertyValueSpecification, NxisValueOption, NxisValueType, ActionSubtype, } from './contract';
|
|
35
|
+
export { isActionType, ACTION_SUBTYPES } from './contract';
|
|
36
|
+
export { readActions } from './parse';
|
|
37
|
+
export { buildInputSchema } from './schema';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form locator — heuristically locates the real `<form>` element
|
|
3
|
+
* corresponding to a NxisAction.
|
|
4
|
+
*
|
|
5
|
+
* Three-step cascade:
|
|
6
|
+
* 1. Target-based candidate collection (URL match or all forms for endpointless)
|
|
7
|
+
* 2. Field-set containment disambiguation (superset of valueName set)
|
|
8
|
+
* 3. Endpointless degenerate case (smallest superset wins)
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
import { NxisAction } from './contract';
|
|
13
|
+
/**
|
|
14
|
+
* Locate the form element on the live page that corresponds to the given Action.
|
|
15
|
+
*
|
|
16
|
+
* @param action - The parsed NxisAction to locate a form for.
|
|
17
|
+
* @returns The matching HTMLFormElement, or null if no unambiguous match found.
|
|
18
|
+
*/
|
|
19
|
+
export declare function locateForm(action: NxisAction): HTMLFormElement | null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-LD parser — reads Action specs from the page's structured data.
|
|
3
|
+
*
|
|
4
|
+
* Selects all `<script type="application/ld+json">` blocks, parses them,
|
|
5
|
+
* flattens `@graph` containers, and collects Action nodes.
|
|
6
|
+
*
|
|
7
|
+
* Total parsing: every step fails soft — malformed blocks are skipped,
|
|
8
|
+
* never fatal. A page may have dozens of JSON-LD blocks; we only collect
|
|
9
|
+
* the ones that are valid Action nodes.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
import { NxisAction } from './contract';
|
|
14
|
+
/**
|
|
15
|
+
* Read all Action specs from JSON-LD blocks in the document.
|
|
16
|
+
*
|
|
17
|
+
* @param doc - The document to scan. Defaults to `document`.
|
|
18
|
+
* @returns Array of parsed NxisAction objects. Never throws.
|
|
19
|
+
*/
|
|
20
|
+
export declare function readActions(doc?: Document | undefined): NxisAction[];
|