diff --git a/.github/workflows/cf_prerelease.yml b/.github/workflows/cf_prerelease.yml new file mode 100644 index 0000000..0aed978 --- /dev/null +++ b/.github/workflows/cf_prerelease.yml @@ -0,0 +1,24 @@ +name: "Playwright MCP for Cloudflare - Pre-Release" +permissions: + contents: read + +on: + push: + pull_request: + +jobs: + test_smoke: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20.x' + registry-url: 'https://registry.npmjs.org' + + - name: Generate Pre-Release + run: | + npm ci + cd cloudflare + npm run build + npx pkg-pr-new publish diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c28167..b695863 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,10 +11,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Use Node.js 18 + - name: Use Node.js 20 uses: actions/setup-node@v4 with: - node-version: '18' + node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci @@ -28,15 +28,14 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-15, windows-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - name: Use Node.js 18 + - name: Use Node.js 20 uses: actions/setup-node@v4 with: - # https://github.com/microsoft/playwright-mcp/issues/344 - node-version: '18.19' + node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci @@ -55,10 +54,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Use Node.js 18 + - name: Use Node.js 20 uses: actions/setup-node@v4 with: - node-version: '18' + node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci @@ -83,3 +82,42 @@ jobs: npm run test -- --project=chromium-docker env: MCP_IN_DOCKER: 1 + + test_extension: + strategy: + fail-fast: false + runs-on: macos-latest + defaults: + run: + working-directory: ./extension + steps: + - uses: actions/checkout@v4 + - name: Use Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: '20' # crypto.randomUUID(); stalls in v18.20.8 + cache: 'npm' + - name: Install dependencies + run: npm ci + - name: Build extension + run: npm run build + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: extension + path: ./extension/dist + retention-days: 7 + - name: Install and build MCP server + run: | + cd .. + npm ci + npm run build + npx playwright install chromium + - name: Run tests + run: | + if [[ "$(uname)" == "Linux" ]]; then + xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test + else + npm run test + fi + shell: bash diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 0000000..3288368 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,44 @@ +name: "Copilot Setup Steps" + +# Automatically run the setup steps when they are changed to allow for easy validation, and +# allow manual testing through the repository's "Actions" tab +on: + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + pull_request: + paths: + - .github/workflows/copilot-setup-steps.yml + +jobs: + # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. + copilot-setup-steps: + runs-on: ubuntu-latest + + # Set the permissions to the lowest permissions possible needed for your steps. + # Copilot will be given its own token for its operations. + permissions: + # If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete. + contents: read + + # You can define any steps you want, and they will run before the agent starts. + # If you do not check out your code, Copilot will do this for you. + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "18.19" + cache: "npm" + + - name: Install JavaScript dependencies + run: npm ci + + - name: Playwright install + run: npx playwright install --with-deps + + - name: Build + run: npm run build \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f581c2c..ed23287 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -68,3 +68,31 @@ jobs: for tag in $(echo ${{ steps.build-push.outputs.metadata['image.name'] }} | tr ',' '\n'); do attach_eol_manifest $tag done + + package-extension: + runs-on: ubuntu-latest + permissions: + contents: write # Needed to upload release assets + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + - name: Install extension dependencies + working-directory: ./extension + run: npm ci + - name: Build extension + working-directory: ./extension + run: npm run build + - name: Package extension + working-directory: ./extension + run: | + cd dist + zip -r ../playwright-mcp-extension-${{ github.event.release.tag_name }}.zip . + cd .. + - name: Upload extension to release + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + gh release upload ${{github.event.release.tag_name}} ./extension/playwright-mcp-extension-${{ github.event.release.tag_name }}.zip diff --git a/.gitignore b/.gitignore index 1089cef..f54eaa7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ lib/ +dist/ node_modules/ test-results/ playwright-report/ .vscode/mcp.json - .idea .DS_Store +.env +sessions/ diff --git a/README.md b/README.md index b18e4ad..ac4e0a9 100644 --- a/README.md +++ b/README.md @@ -128,15 +128,7 @@ X Y coordinate space, based on the provided screenshot. -Interactions - - - -- **browser_snapshot** - - Title: Page snapshot - - Description: Capture accessibility snapshot of the current page, this is better than screenshot - - Parameters: None - - Read-only: **true** +Core automation @@ -147,81 +139,64 @@ X Y coordinate space, based on the provided screenshot. - `element` (string): Human-readable element description used to obtain permission to interact with the element - `ref` (string): Exact target element reference from the page snapshot - `doubleClick` (boolean, optional): Whether to perform a double click instead of a single click + - `button` (string, optional): Button to click, defaults to left - Read-only: **false** -- **browser_drag** - - Title: Drag mouse - - Description: Perform drag and drop between two elements - - Parameters: - - `startElement` (string): Human-readable source element description used to obtain the permission to interact with the element - - `startRef` (string): Exact source element reference from the page snapshot - - `endElement` (string): Human-readable target element description used to obtain the permission to interact with the element - - `endRef` (string): Exact target element reference from the page snapshot - - Read-only: **false** +- **browser_close** + - Title: Close browser + - Description: Close the page + - Parameters: None + - Read-only: **true** -- **browser_hover** - - Title: Hover mouse - - Description: Hover over element on page - - Parameters: - - `element` (string): Human-readable element description used to obtain permission to interact with the element - - `ref` (string): Exact target element reference from the page snapshot +- **browser_console_messages** + - Title: Get console messages + - Description: Returns all console messages + - Parameters: None - Read-only: **true** -- **browser_type** - - Title: Type text - - Description: Type text into editable element +- **browser_drag** + - Title: Drag mouse + - Description: Perform drag and drop between two elements - Parameters: - - `element` (string): Human-readable element description used to obtain permission to interact with the element - - `ref` (string): Exact target element reference from the page snapshot - - `text` (string): Text to type into the element - - `submit` (boolean, optional): Whether to submit entered text (press Enter after) - - `slowly` (boolean, optional): Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once. + - `startElement` (string): Human-readable source element description used to obtain the permission to interact with the element + - `startRef` (string): Exact source element reference from the page snapshot + - `endElement` (string): Human-readable target element description used to obtain the permission to interact with the element + - `endRef` (string): Exact target element reference from the page snapshot - Read-only: **false** -- **browser_select_option** - - Title: Select option - - Description: Select an option in a dropdown +- **browser_evaluate** + - Title: Evaluate JavaScript + - Description: Evaluate JavaScript expression on page or element - Parameters: - - `element` (string): Human-readable element description used to obtain permission to interact with the element - - `ref` (string): Exact target element reference from the page snapshot - - `values` (array): Array of values to select in the dropdown. This can be a single value or multiple values. + - `function` (string): () => { /* code */ } or (element) => { /* code */ } when element is provided + - `element` (string, optional): Human-readable element description used to obtain permission to interact with the element + - `ref` (string, optional): Exact target element reference from the page snapshot - Read-only: **false** -- **browser_press_key** - - Title: Press a key - - Description: Press a key on the keyboard +- **browser_file_upload** + - Title: Upload files + - Description: Upload one or multiple files - Parameters: - - `key` (string): Name of the key to press or a character to generate, such as `ArrowLeft` or `a` + - `paths` (array): The absolute paths to the files to upload. Can be a single file or multiple files. - Read-only: **false** -- **browser_wait_for** - - Title: Wait for - - Description: Wait for text to appear or disappear or a specified time to pass - - Parameters: - - `time` (number, optional): The time to wait in seconds - - `text` (string, optional): The text to wait for - - `textGone` (string, optional): The text to wait for to disappear - - Read-only: **true** - - - -- **browser_file_upload** - - Title: Upload files - - Description: Upload one or multiple files +- **browser_fill_form** + - Title: Fill form + - Description: Fill multiple form fields - Parameters: - - `paths` (array): The absolute paths to the files to upload. Can be a single file or multiple files. + - `fields` (array): Fields to fill in - Read-only: **false** @@ -234,10 +209,15 @@ X Y coordinate space, based on the provided screenshot. - `promptText` (string, optional): The text of the prompt in case of a prompt dialog. - Read-only: **false** - + - -Navigation +- **browser_hover** + - Title: Hover mouse + - Description: Hover over element on page + - Parameters: + - `element` (string): Human-readable element description used to obtain permission to interact with the element + - `ref` (string): Exact target element reference from the page snapshot + - Read-only: **true** @@ -258,40 +238,6 @@ X Y coordinate space, based on the provided screenshot. -- **browser_navigate_forward** - - Title: Go forward - - Description: Go forward to the next page - - Parameters: None - - Read-only: **true** - - - - -Resources - - - -- **browser_take_screenshot** - - Title: Take a screenshot - - Description: Take a screenshot of the current page. You can't perform actions based on the screenshot, use browser_snapshot for actions. - - Parameters: - - `raw` (boolean, optional): Whether to return without compression (in PNG format). Default is false, which returns a JPEG image. - - `filename` (string, optional): File name to save the screenshot to. Defaults to `page-{timestamp}.{png|jpeg}` if not specified. - - `element` (string, optional): Human-readable element description used to obtain permission to screenshot the element. If not provided, the screenshot will be taken of viewport. If element is provided, ref must be provided too. - - `ref` (string, optional): Exact target element reference from the page snapshot. If not provided, the screenshot will be taken of viewport. If ref is provided, element must be provided too. - - Read-only: **true** - - - -- **browser_pdf_save** - - Title: Save as PDF - - Description: Save page as PDF - - Parameters: - - `filename` (string, optional): File name to save the pdf to. Defaults to `page-{timestamp}.pdf` if not specified. - - Read-only: **true** - - - - **browser_network_requests** - Title: List network requests - Description: Returns all network requests since loading the page @@ -300,35 +246,15 @@ X Y coordinate space, based on the provided screenshot. -- **browser_console_messages** - - Title: Get console messages - - Description: Returns all console messages - - Parameters: None - - Read-only: **true** - - - - -Utilities - - - -- **browser_install** - - Title: Install the browser specified in the config - - Description: Install the browser specified in the config. Call this if you get an error about the browser not being installed. - - Parameters: None +- **browser_press_key** + - Title: Press a key + - Description: Press a key on the keyboard + - Parameters: + - `key` (string): Name of the key to press or a character to generate, such as `ArrowLeft` or `a` - Read-only: **false** -- **browser_close** - - Title: Close browser - - Description: Close the page - - Parameters: None - - Read-only: **true** - - - - **browser_resize** - Title: Resize browser window - Description: Resize the browser window @@ -337,91 +263,100 @@ X Y coordinate space, based on the provided screenshot. - `height` (number): Height of the browser window - Read-only: **true** - + - -Tabs +- **browser_select_option** + - Title: Select option + - Description: Select an option in a dropdown + - Parameters: + - `element` (string): Human-readable element description used to obtain permission to interact with the element + - `ref` (string): Exact target element reference from the page snapshot + - `values` (array): Array of values to select in the dropdown. This can be a single value or multiple values. + - Read-only: **false** -- **browser_tab_list** - - Title: List tabs - - Description: List browser tabs +- **browser_snapshot** + - Title: Page snapshot + - Description: Capture accessibility snapshot of the current page, this is better than screenshot - Parameters: None - Read-only: **true** -- **browser_tab_new** - - Title: Open a new tab - - Description: Open a new tab +- **browser_take_screenshot** + - Title: Take a screenshot + - Description: Take a screenshot of the current page. You can't perform actions based on the screenshot, use browser_snapshot for actions. - Parameters: - - `url` (string, optional): The URL to navigate to in the new tab. If not provided, the new tab will be blank. + - `type` (string, optional): Image format for the screenshot. Default is png. + - `filename` (string, optional): File name to save the screenshot to. Defaults to `page-{timestamp}.{png|jpeg}` if not specified. + - `element` (string, optional): Human-readable element description used to obtain permission to screenshot the element. If not provided, the screenshot will be taken of viewport. If element is provided, ref must be provided too. + - `ref` (string, optional): Exact target element reference from the page snapshot. If not provided, the screenshot will be taken of viewport. If ref is provided, element must be provided too. + - `fullPage` (boolean, optional): When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Cannot be used with element screenshots. - Read-only: **true** -- **browser_tab_select** - - Title: Select a tab - - Description: Select a tab by index +- **browser_type** + - Title: Type text + - Description: Type text into editable element - Parameters: - - `index` (number): The index of the tab to select - - Read-only: **true** + - `element` (string): Human-readable element description used to obtain permission to interact with the element + - `ref` (string): Exact target element reference from the page snapshot + - `text` (string): Text to type into the element + - `submit` (boolean, optional): Whether to submit entered text (press Enter after) + - `slowly` (boolean, optional): Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once. + - Read-only: **false** -- **browser_tab_close** - - Title: Close a tab - - Description: Close a tab +- **browser_wait_for** + - Title: Wait for + - Description: Wait for text to appear or disappear or a specified time to pass - Parameters: - - `index` (number, optional): The index of the tab to close. Closes current tab if not provided. - - Read-only: **false** + - `time` (number, optional): The time to wait in seconds + - `text` (string, optional): The text to wait for + - `textGone` (string, optional): The text to wait for to disappear + - Read-only: **true** -Testing +Tab management -- **browser_generate_playwright_test** - - Title: Generate a Playwright test - - Description: Generate a Playwright test for given scenario +- **browser_tabs** + - Title: Manage tabs + - Description: List, create, close, or select a browser tab. - Parameters: - - `name` (string): The name of the test - - `description` (string): The description of the test - - `steps` (array): The steps of the test - - Read-only: **true** + - `action` (string): Operation to perform + - `index` (number, optional): Tab index, used for close/select. If omitted for close, current tab is closed. + - Read-only: **false** -Vision mode +Browser installation -- **browser_screen_capture** - - Title: Take a screenshot - - Description: Take a screenshot of the current page +- **browser_install** + - Title: Install the browser specified in the config + - Description: Install the browser specified in the config. Call this if you get an error about the browser not being installed. - Parameters: None - - Read-only: **true** + - Read-only: **false** - + -- **browser_screen_move_mouse** - - Title: Move mouse - - Description: Move mouse to a given position - - Parameters: - - `element` (string): Human-readable element description used to obtain permission to interact with the element - - `x` (number): X coordinate - - `y` (number): Y coordinate - - Read-only: **true** + +Coordinate-based (opt-in via --caps=vision) -- **browser_screen_click** +- **browser_mouse_click_xy** - Title: Click - - Description: Click left mouse button + - Description: Click left mouse button at a given position - Parameters: - `element` (string): Human-readable element description used to obtain permission to interact with the element - `x` (number): X coordinate @@ -430,9 +365,9 @@ X Y coordinate space, based on the provided screenshot. -- **browser_screen_drag** +- **browser_mouse_drag_xy** - Title: Drag mouse - - Description: Drag left mouse button + - Description: Drag left mouse button to a given position - Parameters: - `element` (string): Human-readable element description used to obtain permission to interact with the element - `startX` (number): Start X coordinate @@ -443,52 +378,28 @@ X Y coordinate space, based on the provided screenshot. -- **browser_screen_type** - - Title: Type text - - Description: Type text - - Parameters: - - `text` (string): Text to type into the element - - `submit` (boolean, optional): Whether to submit entered text (press Enter after) - - Read-only: **false** - - - -- **browser_press_key** - - Title: Press a key - - Description: Press a key on the keyboard - - Parameters: - - `key` (string): Name of the key to press or a character to generate, such as `ArrowLeft` or `a` - - Read-only: **false** - - - -- **browser_wait_for** - - Title: Wait for - - Description: Wait for text to appear or disappear or a specified time to pass +- **browser_mouse_move_xy** + - Title: Move mouse + - Description: Move mouse to a given position - Parameters: - - `time` (number, optional): The time to wait in seconds - - `text` (string, optional): The text to wait for - - `textGone` (string, optional): The text to wait for to disappear + - `element` (string): Human-readable element description used to obtain permission to interact with the element + - `x` (number): X coordinate + - `y` (number): Y coordinate - Read-only: **true** - + -- **browser_file_upload** - - Title: Upload files - - Description: Upload one or multiple files - - Parameters: - - `paths` (array): The absolute paths to the files to upload. Can be a single file or multiple files. - - Read-only: **false** + +PDF generation (opt-in via --caps=pdf) -- **browser_handle_dialog** - - Title: Handle a dialog - - Description: Handle a dialog +- **browser_pdf_save** + - Title: Save as PDF + - Description: Save page as PDF - Parameters: - - `accept` (boolean): Whether to accept the dialog. - - `promptText` (string, optional): The text of the prompt in case of a prompt dialog. - - Read-only: **false** + - `filename` (string, optional): File name to save the pdf to. Defaults to `page-{timestamp}.pdf` if not specified. + - Read-only: **true** diff --git a/cloudflare/.npmignore b/cloudflare/.npmignore index 16ce17d..975cf6b 100644 --- a/cloudflare/.npmignore +++ b/cloudflare/.npmignore @@ -7,3 +7,4 @@ # Include playwright core and test entry points !index.d.ts +!experimental.d.ts diff --git a/cloudflare/example/package-lock.json b/cloudflare/example/package-lock.json index 0bd5784..854eeec 100644 --- a/cloudflare/example/package-lock.json +++ b/cloudflare/example/package-lock.json @@ -9,17 +9,41 @@ "version": "0.0.1-next", "license": "Apache-2.0", "dependencies": { - "@cloudflare/playwright-mcp": "^0.0.4" + "@cloudflare/playwright-mcp": "file:..", + "ai": "^4.3.19", + "workers-ai-provider": "^0.7.5" }, "devDependencies": { "@types/node": "^22.14.1", "typescript": "^5.8.2", - "wrangler": "^4.13.0" + "wrangler": "^4.34.0" }, "engines": { "node": ">=18" } }, + "..": { + "name": "@cloudflare/playwright-mcp", + "version": "0.0.1-next", + "license": "Apache-2.0", + "dependencies": { + "@cloudflare/playwright": "file:../../playwright/packages/playwright-cloudflare", + "@modelcontextprotocol/sdk": "^1.16.0", + "agents": "^0.0.113", + "yaml": "^2.8.0", + "zod-to-json-schema": "^3.24.6" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20250725.0", + "vite": "^7.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "ai": "^4.3.19" + } + }, "node_modules/@ai-sdk/provider": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.3.tgz", @@ -32,69 +56,108 @@ "node": ">=18" } }, - "node_modules/@cloudflare/kv-asset-handler": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.0.tgz", - "integrity": "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==", - "dev": true, - "license": "MIT OR Apache-2.0", + "node_modules/@ai-sdk/provider-utils": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz", + "integrity": "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==", + "license": "Apache-2.0", "dependencies": { - "mime": "^3.0.0" + "@ai-sdk/provider": "1.1.3", + "nanoid": "^3.3.8", + "secure-json-parse": "^2.7.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.23.8" } }, - "node_modules/@cloudflare/playwright": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/@cloudflare/playwright/-/playwright-0.0.10.tgz", - "integrity": "sha512-phlOzpO5FLgXFbx20C2GyQonffpe+qdL/MSMOf7ryc3+WjEsRrr6zqVLVJJq9ZW4CEypee1e/wcUQ473srC2dA==", - "license": "Apache-2.0" + "node_modules/@ai-sdk/provider-utils/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } }, - "node_modules/@cloudflare/playwright-mcp": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@cloudflare/playwright-mcp/-/playwright-mcp-0.0.4.tgz", - "integrity": "sha512-ykmRf2oSIpHty2iOhFrWfpFI8lzVSbtUzANEQaOIxuH7T1ZN/C64xj3mwrRnB4w5z6V1o/9X+ZEAPywI1siKNg==", + "node_modules/@ai-sdk/react": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.12.tgz", + "integrity": "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==", "license": "Apache-2.0", "dependencies": { - "@cloudflare/playwright": "^0.0.10", - "@modelcontextprotocol/sdk": "^1.13.3", - "agents": "^0.0.101", - "yaml": "^2.7.1", - "zod-to-json-schema": "^3.24.5" + "@ai-sdk/provider-utils": "2.2.8", + "@ai-sdk/ui-utils": "1.2.11", + "swr": "^2.2.5", + "throttleit": "2.1.0" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } } }, - "node_modules/@cloudflare/playwright-mcp/node_modules/zod": { - "version": "3.24.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", - "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", - "license": "MIT", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" + "node_modules/@ai-sdk/ui-utils": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz", + "integrity": "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.23.8" } }, - "node_modules/@cloudflare/playwright-mcp/node_modules/zod-to-json-schema": { - "version": "3.24.5", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", - "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.0.tgz", + "integrity": "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "mime": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" } }, + "node_modules/@cloudflare/playwright-mcp": { + "resolved": "..", + "link": true + }, "node_modules/@cloudflare/unenv-preset": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.3.1.tgz", - "integrity": "sha512-Xq57Qd+ADpt6hibcVBO0uLG9zzRgyRhfCUgBT9s+g3+3Ivg5zDyVgLFy40ES1VdNcu8rPNSivm9A+kGP5IVaPg==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.7.2.tgz", + "integrity": "sha512-JY7Uf8GhWcbOMDZX8ke2czp9f9TijvJN4CpRBs3+WYN9U7jHpj3XaV+HHm78iHkAwTm/JeBHqyQNhq/PizynRA==", "dev": true, "license": "MIT OR Apache-2.0", "peerDependencies": { - "unenv": "2.0.0-rc.15", - "workerd": "^1.20250320.0" + "unenv": "2.0.0-rc.20", + "workerd": "^1.20250828.1" }, "peerDependenciesMeta": { "workerd": { @@ -103,9 +166,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20250422.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250422.0.tgz", - "integrity": "sha512-2FWl8TLpC4Knuyw8GmNgUSoJCNJNNGJ7Xv90j2n8FiXR5Clp9jSpm2ovK8RP9P751yX1/iIp8e7QufR/XDB6ow==", + "version": "1.20250902.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250902.0.tgz", + "integrity": "sha512-mwC/YEtDUGfnjXdbW5Lya+bgODrpJ5RxxqpaTjtMJycqnjR0RZgVpOqISwGfBHIhseykU3ahPugM5t91XkBKTg==", "cpu": [ "x64" ], @@ -120,9 +183,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20250422.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250422.0.tgz", - "integrity": "sha512-GY3W74ivqxsYldacEbMtcSbG7LsS9hPo5UybKIw4RO9GzP7UC5WGnPfuI4PE2SnJOnw7nwSrBLuhGRPe/QQHkQ==", + "version": "1.20250902.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250902.0.tgz", + "integrity": "sha512-5Wr6a5/ixoXuMPOvbprN8k9HhAHDBh8f7H5V4DN/Xb4ORoGkI9AbC5QPpYV0wa3Ncf+CRSGobdmZNyO24hRccA==", "cpu": [ "arm64" ], @@ -137,9 +200,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20250422.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250422.0.tgz", - "integrity": "sha512-mtNkEygKtlRq9pMRlm9J4nX4uVHU1AtJ3mSkdNwPwhisTpo989O5Zd0SH9CYwAk8+NmlZsXELpODUVQxQ7FJgw==", + "version": "1.20250902.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250902.0.tgz", + "integrity": "sha512-1yJGt56VQBuG01nrhkRGoa1FGz7xQwJTrgewxt/MRRtigZTf84qJQiPQxyM7PQWCLREKa+JS7G8HFqvOwK7kZA==", "cpu": [ "x64" ], @@ -154,9 +217,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20250422.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250422.0.tgz", - "integrity": "sha512-ILlW4/kAoFJvSryrr/QJsiHBdMTf/fjUrIM0hxeuQue8zIEvAVqM1tzpUh8bPJT6AQEbk5ziwkfucA939Z6Tnw==", + "version": "1.20250902.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250902.0.tgz", + "integrity": "sha512-ArDodWzfo0BVqMQGUgaOGV5Mzf8wEMUX8TJonExpGbYavoVXVDbp2rTLFRJg1vkFGpmw1teCtSoOjSDisFZQMg==", "cpu": [ "arm64" ], @@ -171,9 +234,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20250422.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250422.0.tgz", - "integrity": "sha512-O2f6f7oxU/oaWX/3/5d/9qvzNSKsw72RsQFjpew2va7KwnnUciI2LnbYR6KYOqRGYrEoiMJxpWPQaYaFVj8t1w==", + "version": "1.20250902.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250902.0.tgz", + "integrity": "sha512-DT/o8ZSkmze1YGI7vgVt4ST+VYGb3tNChiFnOM9Z8YOejqKqbVvATB4gi/xMSnNR9CsKFqH4hHWDDtz+wf4uZg==", "cpu": [ "x64" ], @@ -188,10 +251,12 @@ } }, "node_modules/@cloudflare/workers-types": { - "version": "4.20250508.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20250508.0.tgz", - "integrity": "sha512-Gr7NLsHy5BFXbWVMMO+1mf/DwxT30tNw5LGhC86S+CXErM2a2eJ0HJHqgAs0Y8Lt/XEUSrH9QrUFDvJWNhE4Rg==", + "version": "4.20250906.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20250906.0.tgz", + "integrity": "sha512-CMRTupQpAdNZJrxRGaM2JzxmpWOnzgxcyTGmjAOcosRfi1ZsNUTAZ0kj1dzY+4bPDIdFwvvJL3t91DEpqitOJg==", + "dev": true, "license": "MIT OR Apache-2.0", + "optional": true, "peer": true }, "node_modules/@cspotcode/source-map-support": { @@ -208,9 +273,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", - "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", + "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", "dev": true, "license": "MIT", "optional": true, @@ -219,9 +284,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", - "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", "cpu": [ "ppc64" ], @@ -236,9 +301,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", - "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", "cpu": [ "arm" ], @@ -253,9 +318,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", - "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", "cpu": [ "arm64" ], @@ -270,9 +335,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", - "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", "cpu": [ "x64" ], @@ -287,9 +352,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", - "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", "cpu": [ "arm64" ], @@ -304,9 +369,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", - "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", "cpu": [ "x64" ], @@ -321,9 +386,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", - "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", "cpu": [ "arm64" ], @@ -338,9 +403,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", - "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", "cpu": [ "x64" ], @@ -355,9 +420,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", - "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", "cpu": [ "arm" ], @@ -372,9 +437,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", - "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", "cpu": [ "arm64" ], @@ -389,9 +454,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", - "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", "cpu": [ "ia32" ], @@ -406,9 +471,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", - "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", "cpu": [ "loong64" ], @@ -423,9 +488,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", - "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", "cpu": [ "mips64el" ], @@ -440,9 +505,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", - "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", "cpu": [ "ppc64" ], @@ -457,9 +522,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", - "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", "cpu": [ "riscv64" ], @@ -474,9 +539,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", - "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", "cpu": [ "s390x" ], @@ -491,9 +556,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", - "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", "cpu": [ "x64" ], @@ -508,9 +573,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", - "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", "cpu": [ "arm64" ], @@ -525,9 +590,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", - "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", "cpu": [ "x64" ], @@ -542,9 +607,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", - "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", "cpu": [ "arm64" ], @@ -559,9 +624,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", - "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", "cpu": [ "x64" ], @@ -576,9 +641,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", - "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", "cpu": [ "x64" ], @@ -593,9 +658,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", - "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", "cpu": [ "arm64" ], @@ -610,9 +675,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", - "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", "cpu": [ "ia32" ], @@ -627,9 +692,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", - "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", "cpu": [ "x64" ], @@ -643,16 +708,6 @@ "node": ">=18" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", @@ -1044,9 +1099,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, @@ -1061,56 +1116,64 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.15.0.tgz", - "integrity": "sha512-67hnl/ROKdb03Vuu0YOr+baKTvf1/5YBHBm9KnZdjdAh8hjt4FRCPD5ucwxGB237sBpzlqQsLy1PFu7z/ekZ9Q==", - "license": "MIT", - "dependencies": { - "ajv": "^6.12.6", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" - }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": ">=8.0.0" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/zod": { - "version": "3.25.75", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.75.tgz", - "integrity": "sha512-OhpzAmVzabPOL6C3A3gpAifqr9MqihV/Msx3gor2b2kviCgcb+HM9SEOpMWwwNp9MRunWnhtAKUoo0AHhjyPPg==", + "node_modules/@poppinss/colors": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.5.tgz", + "integrity": "sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw==", + "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" + "dependencies": { + "kleur": "^4.1.5" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" + "node_modules/@poppinss/dumper": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.4.tgz", + "integrity": "sha512-iG0TIdqv8xJ3Lt9O8DrPRxw1MRLjNpoqiSGU03P/wNLP/s0ra0udPJ1J2Tx5M0J3H/cVyEgpbn8xUKRY9j59kQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" } }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "license": "Apache-2.0", + "node_modules/@poppinss/exception": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.2.tgz", + "integrity": "sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.2.tgz", + "integrity": "sha512-d9xRovfKNz1SKieM0qJdO+PQonjnnIfSNWfHYnBSJ9hkjm0ZPw6HlxscDXYstp3z+7V2GOFHc+J0CYrYTjqCJw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, + "node_modules/@speed-highlight/core": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.7.tgz", + "integrity": "sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/@types/diff-match-patch": { "version": "1.0.36", "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", @@ -1127,19 +1190,6 @@ "undici-types": "~6.21.0" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -1163,69 +1213,18 @@ "node": ">=0.4.0" } }, - "node_modules/agents": { - "version": "0.0.101", - "resolved": "https://registry.npmjs.org/agents/-/agents-0.0.101.tgz", - "integrity": "sha512-dMsVNX4dwW83ZgL/l3DVJqPZZpEN5yVDM6g0Irs1JlWnuTp0slYU8IKyQcduTSdMqX2y25Kh1tlOIdkBt18Xng==", - "license": "MIT", - "dependencies": { - "@modelcontextprotocol/sdk": "^1.13.3", - "ai": "^4.3.16", - "cron-schedule": "^5.0.4", - "nanoid": "^5.1.5", - "partyserver": "^0.0.72", - "partysocket": "1.1.4", - "zod": "^3.25.67" - }, - "peerDependencies": { - "react": "*" - } - }, - "node_modules/agents/node_modules/@ai-sdk/provider-utils": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz", - "integrity": "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==", + "node_modules/ai": { + "version": "4.3.19", + "resolved": "https://registry.npmjs.org/ai/-/ai-4.3.19.tgz", + "integrity": "sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "1.1.3", - "nanoid": "^3.3.8", - "secure-json-parse": "^2.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.23.8" - } - }, - "node_modules/agents/node_modules/@ai-sdk/provider-utils/node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/agents/node_modules/@ai-sdk/react": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.12.tgz", - "integrity": "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==", - "license": "Apache-2.0", - "dependencies": { "@ai-sdk/provider-utils": "2.2.8", + "@ai-sdk/react": "1.2.12", "@ai-sdk/ui-utils": "1.2.11", - "swr": "^2.2.5", - "throttleit": "2.1.0" + "@opentelemetry/api": "1.9.0", + "jsondiffpatch": "0.6.0" }, "engines": { "node": ">=18" @@ -1235,188 +1234,42 @@ "zod": "^3.23.8" }, "peerDependenciesMeta": { - "zod": { + "react": { "optional": true } } }, - "node_modules/agents/node_modules/@ai-sdk/ui-utils": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz", - "integrity": "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "1.1.3", - "@ai-sdk/provider-utils": "2.2.8", - "zod-to-json-schema": "^3.24.1" - }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "license": "MIT", "engines": { - "node": ">=18" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, - "peerDependencies": { - "zod": "^3.23.8" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/agents/node_modules/ai": { - "version": "4.3.17", - "resolved": "https://registry.npmjs.org/ai/-/ai-4.3.17.tgz", - "integrity": "sha512-uWqIQ94Nb1GTYtYElGHegJMOzv3r2mCKNFlKrqkft9xrfvIahTI5OdcnD5U9612RFGuUNGmSDTO1/YRNFXobaQ==", - "license": "Apache-2.0", + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dev": true, + "license": "MIT", "dependencies": { - "@ai-sdk/provider": "1.1.3", - "@ai-sdk/provider-utils": "2.2.8", - "@ai-sdk/react": "1.2.12", - "@ai-sdk/ui-utils": "1.2.11", - "@opentelemetry/api": "1.9.0", - "jsondiffpatch": "0.6.0" + "color-convert": "^2.0.1", + "color-string": "^1.9.0" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "zod": "^3.23.8" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - } - } - }, - "node_modules/agents/node_modules/zod": { - "version": "3.25.75", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.75.tgz", - "integrity": "sha512-OhpzAmVzabPOL6C3A3gpAifqr9MqihV/Msx3gor2b2kviCgcb+HM9SEOpMWwwNp9MRunWnhtAKUoo0AHhjyPPg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/agents/node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/as-table": { - "version": "1.0.55", - "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", - "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "printable-characters": "^1.0.42" - } - }, - "node_modules/blake3-wasm": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", - "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", - "dev": true, - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" + "node": ">=12.5.0" } }, "node_modules/color-convert": { @@ -1425,7 +1278,6 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1438,8 +1290,7 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/color-string": { "version": "1.9.1", @@ -1447,109 +1298,19 @@ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, - "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cron-schedule": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cron-schedule/-/cron-schedule-5.0.4.tgz", - "integrity": "sha512-nH0a49E/kSVk6BeFgKZy4uUsy6D2A16p120h5bYD9ILBhQu7o2sJFH+WI4R731TSBQ0dB1Ik7inB/dRAB4C8QQ==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", - "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=18" } }, "node_modules/defu": { @@ -1559,15 +1320,6 @@ "dev": true, "license": "MIT" }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -1578,12 +1330,11 @@ } }, "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", "dev": true, "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=8" } @@ -1594,69 +1345,20 @@ "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", "license": "Apache-2.0" }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, "node_modules/esbuild": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", - "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1667,73 +1369,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.2", - "@esbuild/android-arm": "0.25.2", - "@esbuild/android-arm64": "0.25.2", - "@esbuild/android-x64": "0.25.2", - "@esbuild/darwin-arm64": "0.25.2", - "@esbuild/darwin-x64": "0.25.2", - "@esbuild/freebsd-arm64": "0.25.2", - "@esbuild/freebsd-x64": "0.25.2", - "@esbuild/linux-arm": "0.25.2", - "@esbuild/linux-arm64": "0.25.2", - "@esbuild/linux-ia32": "0.25.2", - "@esbuild/linux-loong64": "0.25.2", - "@esbuild/linux-mips64el": "0.25.2", - "@esbuild/linux-ppc64": "0.25.2", - "@esbuild/linux-riscv64": "0.25.2", - "@esbuild/linux-s390x": "0.25.2", - "@esbuild/linux-x64": "0.25.2", - "@esbuild/netbsd-arm64": "0.25.2", - "@esbuild/netbsd-x64": "0.25.2", - "@esbuild/openbsd-arm64": "0.25.2", - "@esbuild/openbsd-x64": "0.25.2", - "@esbuild/sunos-x64": "0.25.2", - "@esbuild/win32-arm64": "0.25.2", - "@esbuild/win32-ia32": "0.25.2", - "@esbuild/win32-x64": "0.25.2" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-target-polyfill": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/event-target-polyfill/-/event-target-polyfill-0.0.4.tgz", - "integrity": "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==", - "license": "MIT" - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.3.tgz", - "integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==", - "license": "MIT", - "engines": { - "node": ">=20.0.0" + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" } }, "node_modules/exit-hook": { @@ -1749,117 +1409,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, "node_modules/exsolve": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.5.tgz", - "integrity": "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", "dev": true, "license": "MIT" }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "license": "MIT" - }, - "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1875,63 +1431,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-source": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", - "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", - "dev": true, - "license": "Unlicense", - "dependencies": { - "data-uri-to-buffer": "^2.0.0", - "source-map": "^0.6.1" - } - }, "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -1939,126 +1438,19 @@ "dev": true, "license": "BSD-2-Clause" }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", "license": "(AFL-2.1 OR BSD-3-Clause)" }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, "node_modules/jsondiffpatch": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz", @@ -2076,34 +1468,14 @@ "node": "^18.0.0 || >=20.0.0" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/mime": { @@ -2119,31 +1491,10 @@ "node": ">=10.0.0" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/miniflare": { - "version": "4.20250422.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250422.0.tgz", - "integrity": "sha512-3frXK9EZEWQkHMDyppeIbUKwd7OQkNOm2gBtQQzjQ4gtzQmh+yxkyJiiylf+fGbz86djQTLKKQdQ1FC4yM3AMg==", + "version": "4.20250902.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250902.0.tgz", + "integrity": "sha512-QHjI17yVDxDXsjDvX6GNRySx2uYsQJyiZ2MRBAsA0CFpAI2BcHd4oz0FIjbqgpZK+4Fhm7OKht/AfBNCd234Zg==", "dev": true, "license": "MIT", "dependencies": { @@ -2151,83 +1502,30 @@ "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", - "glob-to-regexp": "0.4.1", - "stoppable": "1.1.0", - "undici": "^5.28.5", - "workerd": "1.20250422.0", - "ws": "8.18.0", - "youch": "3.3.4", - "zod": "3.22.3" - }, - "bin": { - "miniflare": "bootstrap.js" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/mustache": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "dev": true, - "license": "MIT", - "bin": { - "mustache": "bin/mustache" - } - }, - "node_modules/nanoid": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", + "glob-to-regexp": "0.4.1", + "sharp": "^0.33.5", + "stoppable": "1.1.0", + "undici": "^7.10.0", + "workerd": "1.20250902.0", + "ws": "8.18.0", + "youch": "4.1.0-beta.10", + "zod": "3.22.3" + }, "bin": { - "nanoid": "bin/nanoid.js" + "miniflare": "bootstrap.js" }, "engines": { - "node": "^18 || >=20" - } - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "node": ">=18.0.0" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "node_modules/miniflare/node_modules/zod": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", + "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" - }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/ohash": { @@ -2237,66 +1535,6 @@ "dev": true, "license": "MIT" }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/partyserver": { - "version": "0.0.72", - "resolved": "https://registry.npmjs.org/partyserver/-/partyserver-0.0.72.tgz", - "integrity": "sha512-mYkCQ6Q4KBIy4lFFuA6upmvNeD/FC+CQVTd4V3DYU6nsitKVI3NXxBrNNvmIxJLSwk3JQzYcEOPBkebB7ITVpQ==", - "license": "ISC", - "dependencies": { - "nanoid": "^5.1.5" - }, - "peerDependencies": { - "@cloudflare/workers-types": "^4.20240729.0" - } - }, - "node_modules/partysocket": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/partysocket/-/partysocket-1.1.4.tgz", - "integrity": "sha512-jXP7PFj2h5/v4UjDS8P7MZy6NJUQ7sspiFyxL4uc/+oKOL+KdtXzHnTV8INPGxBrLTXgalyG3kd12Qm7WrYc3A==", - "license": "ISC", - "dependencies": { - "event-target-polyfill": "^0.0.4" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/path-to-regexp": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", @@ -2311,144 +1549,16 @@ "dev": true, "license": "MIT" }, - "node_modules/pkce-challenge": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/printable-characters": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", - "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", - "dev": true, - "license": "Unlicense" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "license": "MIT", "peer": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/router/node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, "node_modules/secure-json-parse": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", @@ -2456,12 +1566,11 @@ "license": "BSD-3-Clause" }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", - "optional": true, "bin": { "semver": "bin/semver.js" }, @@ -2469,49 +1578,6 @@ "node": ">=10" } }, - "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, "node_modules/sharp": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", @@ -2519,7 +1585,6 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "optional": true, "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", @@ -2553,140 +1618,16 @@ "@img/sharp-win32-x64": "0.33.5" } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "is-arrayish": "^0.3.1" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stacktracey": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", - "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", - "dev": true, - "license": "Unlicense", - "dependencies": { - "as-table": "^1.0.36", - "get-source": "^2.0.12" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/stoppable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", @@ -2698,10 +1639,23 @@ "npm": ">=6" } }, + "node_modules/supports-color": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.0.tgz", + "integrity": "sha512-5eG9FQjEjDbAlI5+kdpdyPIBMRH4GfTVDGREVupaZHmVoppknhM29b/S9BkQz7cathp85BVgRi/As3Siln7e0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/swr": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.4.tgz", - "integrity": "sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz", + "integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==", "license": "MIT", "dependencies": { "dequal": "^2.0.3", @@ -2723,15 +1677,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -2740,20 +1685,6 @@ "license": "0BSD", "optional": true }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -2776,16 +1707,13 @@ "license": "MIT" }, "node_modules/undici": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", - "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.15.0.tgz", + "integrity": "sha512-7oZJCPvvMvTd0OlqWsIxTuItTpJBpU1tcbVl24FMn3xt3+VSunwUasmfPJRE57oNO1KsZ4PgA1xTdAX4hq8NyQ==", "dev": true, "license": "MIT", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, "engines": { - "node": ">=14.0" + "node": ">=20.18.1" } }, "node_modules/undici-types": { @@ -2796,35 +1724,17 @@ "license": "MIT" }, "node_modules/unenv": { - "version": "2.0.0-rc.15", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.15.tgz", - "integrity": "sha512-J/rEIZU8w6FOfLNz/hNKsnY+fFHWnu9MH4yRbSZF3xbbGHovcetXPs7sD+9p8L6CeNC//I9bhRYAOsBt2u7/OA==", + "version": "2.0.0-rc.20", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.20.tgz", + "integrity": "sha512-8tn4tAl9vD5nWoggAAPz28vf0FY8+pQAayhU94qD+ZkIbVKCBAH/E1MWEEmhb9Whn5EgouYVfBJB20RsTLRDdg==", "dev": true, "license": "MIT", "dependencies": { "defu": "^6.1.4", - "exsolve": "^1.0.4", + "exsolve": "^1.0.7", "ohash": "^2.0.11", "pathe": "^2.0.3", - "ufo": "^1.5.4" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" + "ufo": "^1.6.1" } }, "node_modules/use-sync-external-store": { @@ -2836,34 +1746,10 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/workerd": { - "version": "1.20250422.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250422.0.tgz", - "integrity": "sha512-q3ws6MIa9GJQqq1Q52qoD7vCx1203fjKNPmtRV1vvplrsfYphjr5pOAnZGUODFB1BnsDWypr71Luy7OonT0vug==", + "version": "1.20250902.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250902.0.tgz", + "integrity": "sha512-rM+8ARYoy9gWJNPW89ERWyjbp7+m1hu6PFbehiP8FW9Hm5kNVo71lXFrkCP2HSsTP1OLfIU/IwanYOijJ0mQDw==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -2874,28 +1760,38 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20250422.0", - "@cloudflare/workerd-darwin-arm64": "1.20250422.0", - "@cloudflare/workerd-linux-64": "1.20250422.0", - "@cloudflare/workerd-linux-arm64": "1.20250422.0", - "@cloudflare/workerd-windows-64": "1.20250422.0" + "@cloudflare/workerd-darwin-64": "1.20250902.0", + "@cloudflare/workerd-darwin-arm64": "1.20250902.0", + "@cloudflare/workerd-linux-64": "1.20250902.0", + "@cloudflare/workerd-linux-arm64": "1.20250902.0", + "@cloudflare/workerd-windows-64": "1.20250902.0" + } + }, + "node_modules/workers-ai-provider": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/workers-ai-provider/-/workers-ai-provider-0.7.5.tgz", + "integrity": "sha512-dhCwgc3D65oDDTpH3k8Gf0Ek7KItzvaQidn2N5L5cqLo3WG8GM/4+Nr4rU56o8O3oZRsloB1gUCHYaRv2j7Y0A==", + "license": "MIT", + "dependencies": { + "@ai-sdk/provider": "^1.1.3", + "@ai-sdk/provider-utils": "^2.2.8" } }, "node_modules/wrangler": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.13.0.tgz", - "integrity": "sha512-CVRNL0unLmzhVeUkW+9neZHFITSo7UDROz8VYxi8YhitV9Rr1xMojS1cGjQTaQX8F3nAEsTRJXTwwTZ0JoJm6g==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.34.0.tgz", + "integrity": "sha512-iU+T8klWX6M/oN9y2PG8HrekoHwlBs/7wNMouyRToCJGn5EFtVl98a1fxxPCgkuUNZ2sKLrCyx/TlhgilIlqpQ==", "dev": true, "license": "MIT OR Apache-2.0", "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", - "@cloudflare/unenv-preset": "2.3.1", + "@cloudflare/unenv-preset": "2.7.2", "blake3-wasm": "2.1.5", - "esbuild": "0.25.2", - "miniflare": "4.20250422.0", + "esbuild": "0.25.4", + "miniflare": "4.20250902.0", "path-to-regexp": "6.3.0", - "unenv": "2.0.0-rc.15", - "workerd": "1.20250422.0" + "unenv": "2.0.0-rc.20", + "workerd": "1.20250902.0" }, "bin": { "wrangler": "bin/wrangler.js", @@ -2905,11 +1801,10 @@ "node": ">=18.0.0" }, "optionalDependencies": { - "fsevents": "~2.3.2", - "sharp": "^0.33.5" + "fsevents": "~2.3.2" }, "peerDependencies": { - "@cloudflare/workers-types": "^4.20250422.0" + "@cloudflare/workers-types": "^4.20250902.0" }, "peerDependenciesMeta": { "@cloudflare/workers-types": { @@ -2917,12 +1812,6 @@ } } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", @@ -2945,39 +1834,49 @@ } } }, - "node_modules/yaml": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", - "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" + "node_modules/youch": { + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" } }, - "node_modules/youch": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.4.tgz", - "integrity": "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==", + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", "dev": true, "license": "MIT", "dependencies": { - "cookie": "^0.7.1", - "mustache": "^4.2.0", - "stacktracey": "^2.1.8" + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" } }, "node_modules/zod": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", - "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", - "dev": true, + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } } } } diff --git a/cloudflare/example/package.json b/cloudflare/example/package.json index bcc74ff..b5b52b2 100644 --- a/cloudflare/example/package.json +++ b/cloudflare/example/package.json @@ -16,11 +16,13 @@ "deploy": "wrangler deploy" }, "dependencies": { - "@cloudflare/playwright-mcp": "^0.0.4" + "@cloudflare/playwright-mcp": "file:..", + "ai": "^4.3.19", + "workers-ai-provider": "^0.7.5" }, "devDependencies": { "@types/node": "^22.14.1", "typescript": "^5.8.2", - "wrangler": "^4.13.0" + "wrangler": "^4.34.0" } } diff --git a/cloudflare/example/src/index.ts b/cloudflare/example/src/index.ts index 194a169..879e83d 100644 --- a/cloudflare/example/src/index.ts +++ b/cloudflare/example/src/index.ts @@ -1,19 +1,52 @@ import { env } from 'cloudflare:workers'; import { createMcpAgent } from '@cloudflare/playwright-mcp'; +import { createToolsProvider } from '@cloudflare/playwright-mcp/experimental'; +import { generateText } from 'ai'; +import { createWorkersAI } from 'workers-ai-provider'; +import { launch } from '@cloudflare/playwright'; export const PlaywrightMCP = createMcpAgent(env.BROWSER); +async function handleAi(env: Env, searchParams: URLSearchParams) { + const workersai = createWorkersAI({ binding: env.AI }); + await using browser = await launch(env.BROWSER); + await using browserContext = await browser.newContext(); + const toolsProvider = await createToolsProvider(browserContext); + const tools = toolsProvider.tools(); + const prompt = searchParams.get('q') || 'navigate to https://news.ycombinator.com/ and list the title of top 5 stories'; + const content = await generateText({ + // @ts-expect-error + model: workersai(searchParams.get('model') ?? '@cf/meta/llama-4-scout-17b-16e-instruct'), + tools, + maxSteps: 5, + maxRetries: 3, + prompt, + experimental_activeTools: ['browser_navigate', 'browser_snapshot', 'browser_click'], + }); + return new Response([ + `You asked: ${prompt}`, + '', + content.text + ].join('\n'), { + headers: { + 'Content-Type': 'text/plain', + }, + }); +} + export default { - fetch(request: Request, env: Env, ctx: ExecutionContext) { - const { pathname } = new URL(request.url); + async fetch(request: Request, env: Env, ctx: ExecutionContext) { + const { pathname, searchParams } = new URL(request.url); switch (pathname) { + case '/ai': + return await handleAi(env, searchParams); case '/sse': case '/sse/message': - return PlaywrightMCP.serveSSE('/sse').fetch(request, env, ctx); + return await PlaywrightMCP.serveSSE('/sse').fetch(request, env, ctx); case '/mcp': - return PlaywrightMCP.serve('/mcp').fetch(request, env, ctx); + return await PlaywrightMCP.serve('/mcp').fetch(request, env, ctx); default: return new Response('Not Found', { status: 404 }); } diff --git a/cloudflare/example/worker-configuration.d.ts b/cloudflare/example/worker-configuration.d.ts index 749c3a0..d3492cc 100644 --- a/cloudflare/example/worker-configuration.d.ts +++ b/cloudflare/example/worker-configuration.d.ts @@ -1,10 +1,11 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: 5fe77681648184bc7e79ddf3dddd8309) -// Runtime types generated with workerd@1.20250422.0 2025-03-10 nodejs_compat +// Generated by Wrangler by running `wrangler types` (hash: 9c645409ef4f2c3e533afa2673f4cea6) +// Runtime types generated with workerd@1.20250902.0 2025-03-10 nodejs_compat declare namespace Cloudflare { interface Env { MCP_OBJECT: DurableObjectNamespace; BROWSER: Fetcher; + AI: Ai; } } interface Env extends Cloudflare.Env {} @@ -89,7 +90,7 @@ interface Console { clear(): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/count_static) */ count(label?: string): void; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/countreset_static) */ + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/countReset_static) */ countReset(label?: string): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/debug_static) */ debug(...data: any[]): void; @@ -101,9 +102,9 @@ interface Console { error(...data: any[]): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/group_static) */ group(...data: any[]): void; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupcollapsed_static) */ + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupCollapsed_static) */ groupCollapsed(...data: any[]): void; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupend_static) */ + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupEnd_static) */ groupEnd(): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/info_static) */ info(...data: any[]): void; @@ -113,9 +114,9 @@ interface Console { table(tabularData?: any, properties?: string[]): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/time_static) */ time(label?: string): void; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeend_static) */ + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeEnd_static) */ timeEnd(label?: string): void; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timelog_static) */ + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeLog_static) */ timeLog(label?: string, ...data: any[]): void; timeStamp(label?: string): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/trace_static) */ @@ -290,25 +291,25 @@ declare function dispatchEvent(event: WorkerGlobalScopeEventMap[keyof WorkerGlob declare function btoa(data: string): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/atob) */ declare function atob(data: string): string; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/setTimeout) */ +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */ declare function setTimeout(callback: (...args: any[]) => void, msDelay?: number): number; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/setTimeout) */ +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */ declare function setTimeout(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/clearTimeout) */ +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearTimeout) */ declare function clearTimeout(timeoutId: number | null): void; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/setInterval) */ +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */ declare function setInterval(callback: (...args: any[]) => void, msDelay?: number): number; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/setInterval) */ +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */ declare function setInterval(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/clearInterval) */ +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearInterval) */ declare function clearInterval(timeoutId: number | null): void; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/queueMicrotask) */ +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/queueMicrotask) */ declare function queueMicrotask(task: Function): void; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/structuredClone) */ +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/structuredClone) */ declare function structuredClone(value: T, options?: StructuredSerializeOptions): T; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/reportError) */ +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/reportError) */ declare function reportError(error: any): void; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/fetch) */ +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch) */ declare function fetch(input: RequestInfo | URL, init?: RequestInit): Promise; declare const self: ServiceWorkerGlobalScope; /** @@ -347,7 +348,7 @@ interface ExecutionContext { type ExportedHandlerFetchHandler = (request: Request>, env: Env, ctx: ExecutionContext) => Response | Promise; type ExportedHandlerTailHandler = (events: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise; type ExportedHandlerTraceHandler = (traces: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise; -type ExportedHandlerTailStreamHandler = (event: TailStream.TailEvent, env: Env, ctx: ExecutionContext) => TailStream.TailEventHandlerType | Promise; +type ExportedHandlerTailStreamHandler = (event: TailStream.TailEvent, env: Env, ctx: ExecutionContext) => TailStream.TailEventHandlerType | Promise; type ExportedHandlerScheduledHandler = (controller: ScheduledController, env: Env, ctx: ExecutionContext) => void | Promise; type ExportedHandlerQueueHandler = (batch: MessageBatch, env: Env, ctx: ExecutionContext) => void | Promise; type ExportedHandlerTestHandler = (controller: TestController, env: Env, ctx: ExecutionContext) => void | Promise; @@ -416,9 +417,10 @@ interface DurableObjectNamespace; + getByName(name: string, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub; jurisdiction(jurisdiction: DurableObjectJurisdiction): DurableObjectNamespace; } -type DurableObjectJurisdiction = "eu" | "fedramp"; +type DurableObjectJurisdiction = "eu" | "fedramp" | "fedramp-high"; interface DurableObjectNamespaceNewUniqueIdOptions { jurisdiction?: DurableObjectJurisdiction; } @@ -787,6 +789,7 @@ declare class Blob { slice(start?: number, end?: number, type?: string): Blob; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/arrayBuffer) */ arrayBuffer(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/bytes) */ bytes(): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */ text(): Promise; @@ -1081,10 +1084,15 @@ interface TextEncoderEncodeIntoResult { */ declare class ErrorEvent extends Event { constructor(type: string, init?: ErrorEventErrorEventInit); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/filename) */ get filename(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/message) */ get message(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/lineno) */ get lineno(): number; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/colno) */ get colno(): number; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/error) */ get error(): any; } interface ErrorEventErrorEventInit { @@ -1094,6 +1102,47 @@ interface ErrorEventErrorEventInit { colno?: number; error?: any; } +/** + * A message received by a target object. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent) + */ +declare class MessageEvent extends Event { + constructor(type: string, initializer: MessageEventInit); + /** + * Returns the data of the message. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/data) + */ + readonly data: any; + /** + * Returns the origin of the message, for server-sent events and cross-document messaging. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/origin) + */ + readonly origin: string | null; + /** + * Returns the last event ID string, for server-sent events. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/lastEventId) + */ + readonly lastEventId: string; + /** + * Returns the WindowProxy of the source window, for cross-document messaging, and the MessagePort being attached, in the connect event fired at SharedWorkerGlobalScope objects. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/source) + */ + readonly source: MessagePort | null; + /** + * Returns the MessagePort array sent with the message, for cross-document messaging and channel messaging. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/ports) + */ + readonly ports: MessagePort[]; +} +interface MessageEventInit { + data: ArrayBuffer | string; +} /** * Provides a way to easily construct a set of key/value pairs representing form fields and their values, which can then be easily sent using the XMLHttpRequest.send() method. It uses the same format a form would use if the encoding type were set to "multipart/form-data". * @@ -1258,6 +1307,7 @@ declare abstract class Body { get bodyUsed(): boolean; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer) */ arrayBuffer(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bytes) */ bytes(): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/text) */ text(): Promise; @@ -1369,7 +1419,11 @@ interface Request> e * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/integrity) */ integrity: string; - /* Returns a boolean indicating whether or not request can outlive the global in which it was created. */ + /** + * Returns a boolean indicating whether or not request can outlive the global in which it was created. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/keepalive) + */ keepalive: boolean; /** * Returns the cache mode associated with request, which is a string indicating how the request will interact with the browser's cache when fetching. @@ -1397,7 +1451,7 @@ interface RequestInit { signal?: (AbortSignal | null); encodeResponseBody?: "automatic" | "manual"; } -type Service = Fetcher; +type Service Rpc.WorkerEntrypointBranded) | Rpc.WorkerEntrypointBranded | ExportedHandler | undefined = undefined> = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded ? Fetcher> : T extends Rpc.WorkerEntrypointBranded ? Fetcher : T extends Exclude ? never : Fetcher; type Fetcher = (T extends Rpc.EntrypointBranded ? Rpc.Provider : unknown) & { fetch(input: RequestInfo | URL, init?: RequestInit): Promise; connect(address: SocketAddress | string, options?: SocketOptions): Socket; @@ -1569,6 +1623,7 @@ interface R2ObjectBody extends R2Object { get body(): ReadableStream; get bodyUsed(): boolean; arrayBuffer(): Promise; + bytes(): Promise; text(): Promise; json(): Promise; blob(): Promise; @@ -2269,23 +2324,6 @@ interface CloseEventInit { reason?: string; wasClean?: boolean; } -/** - * A message received by a target object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent) - */ -declare class MessageEvent extends Event { - constructor(type: string, initializer: MessageEventInit); - /** - * Returns the data of the message. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/data) - */ - readonly data: ArrayBuffer | string; -} -interface MessageEventInit { - data: ArrayBuffer | string; -} type WebSocketEventMap = { close: CloseEvent; message: MessageEvent; @@ -2473,6 +2511,38 @@ interface ContainerStartupOptions { enableInternet: boolean; env?: Record; } +/** + * This Channel Messaging API interface represents one of the two ports of a MessageChannel, allowing messages to be sent from one port and listening out for them arriving at the other. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort) + */ +interface MessagePort extends EventTarget { + /** + * Posts a message through the channel. Objects listed in transfer are transferred, not just cloned, meaning that they are no longer usable on the sending side. + * + * Throws a "DataCloneError" DOMException if transfer contains duplicate objects or port, or if message could not be cloned. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/postMessage) + */ + postMessage(data?: any, options?: (any[] | MessagePortPostMessageOptions)): void; + /** + * Disconnects the port, so that it is no longer active. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/close) + */ + close(): void; + /** + * Begins dispatching messages received on the port. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/start) + */ + start(): void; + get onmessage(): any | null; + set onmessage(value: any | null); +} +interface MessagePortPostMessageOptions { + transfer?: any[]; +} type AiImageClassificationInput = { image: number[]; }; @@ -2663,7 +2733,7 @@ type AiTextGenerationOutput = { name: string; arguments: unknown; }[]; -} | ReadableStream; +}; declare abstract class BaseAiTextGeneration { inputs: AiTextGenerationInput; postProcessedOutputs: AiTextGenerationOutput; @@ -2709,6 +2779,45 @@ declare abstract class BaseAiTranslation { inputs: AiTranslationInput; postProcessedOutputs: AiTranslationOutput; } +type Ai_Cf_Baai_Bge_Base_En_V1_5_Input = { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; +} | { + /** + * Batch of the embeddings requests to run using async-queue + */ + requests: { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; + }[]; +}; +type Ai_Cf_Baai_Bge_Base_En_V1_5_Output = { + shape?: number[]; + /** + * Embeddings of the requested text values + */ + data?: number[][]; + /** + * The pooling method used in the embedding process. + */ + pooling?: "mean" | "cls"; +} | AsyncResponse; +interface AsyncResponse { + /** + * The async request id that can be used to obtain the results. + */ + request_id?: string; +} +declare abstract class Base_Ai_Cf_Baai_Bge_Base_En_V1_5 { + inputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Input; + postProcessedOutputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Output; +} type Ai_Cf_Openai_Whisper_Input = string | { /** * An array of integers that represent the audio data constrained to 8-bit unsigned integer values @@ -2738,6 +2847,114 @@ declare abstract class Base_Ai_Cf_Openai_Whisper { inputs: Ai_Cf_Openai_Whisper_Input; postProcessedOutputs: Ai_Cf_Openai_Whisper_Output; } +type Ai_Cf_Meta_M2M100_1_2B_Input = { + /** + * The text to be translated + */ + text: string; + /** + * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified + */ + source_lang?: string; + /** + * The language code to translate the text into (e.g., 'es' for Spanish) + */ + target_lang: string; +} | { + /** + * Batch of the embeddings requests to run using async-queue + */ + requests: { + /** + * The text to be translated + */ + text: string; + /** + * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified + */ + source_lang?: string; + /** + * The language code to translate the text into (e.g., 'es' for Spanish) + */ + target_lang: string; + }[]; +}; +type Ai_Cf_Meta_M2M100_1_2B_Output = { + /** + * The translated text in the target language + */ + translated_text?: string; +} | AsyncResponse; +declare abstract class Base_Ai_Cf_Meta_M2M100_1_2B { + inputs: Ai_Cf_Meta_M2M100_1_2B_Input; + postProcessedOutputs: Ai_Cf_Meta_M2M100_1_2B_Output; +} +type Ai_Cf_Baai_Bge_Small_En_V1_5_Input = { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; +} | { + /** + * Batch of the embeddings requests to run using async-queue + */ + requests: { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; + }[]; +}; +type Ai_Cf_Baai_Bge_Small_En_V1_5_Output = { + shape?: number[]; + /** + * Embeddings of the requested text values + */ + data?: number[][]; + /** + * The pooling method used in the embedding process. + */ + pooling?: "mean" | "cls"; +} | AsyncResponse; +declare abstract class Base_Ai_Cf_Baai_Bge_Small_En_V1_5 { + inputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Input; + postProcessedOutputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Output; +} +type Ai_Cf_Baai_Bge_Large_En_V1_5_Input = { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; +} | { + /** + * Batch of the embeddings requests to run using async-queue + */ + requests: { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; + }[]; +}; +type Ai_Cf_Baai_Bge_Large_En_V1_5_Output = { + shape?: number[]; + /** + * Embeddings of the requested text values + */ + data?: number[][]; + /** + * The pooling method used in the embedding process. + */ + pooling?: "mean" | "cls"; +} | AsyncResponse; +declare abstract class Base_Ai_Cf_Baai_Bge_Large_En_V1_5 { + inputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Input; + postProcessedOutputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Output; +} type Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input = string | { /** * The input text prompt for the model to generate a response. @@ -2829,7 +3046,7 @@ interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input { /** * Preprocess the audio with a voice activity detection model. */ - vad_filter?: string; + vad_filter?: boolean; /** * A text prompt to help provide context to the model on the contents of the audio. */ @@ -2919,7 +3136,12 @@ declare abstract class Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo { inputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input; postProcessedOutputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output; } -type Ai_Cf_Baai_Bge_M3_Input = BGEM3InputQueryAndContexts | BGEM3InputEmbedding; +type Ai_Cf_Baai_Bge_M3_Input = BGEM3InputQueryAndContexts | BGEM3InputEmbedding | { + /** + * Batch of the embeddings requests to run using async-queue + */ + requests: (BGEM3InputQueryAndContexts1 | BGEM3InputEmbedding1)[]; +}; interface BGEM3InputQueryAndContexts { /** * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts @@ -2946,7 +3168,33 @@ interface BGEM3InputEmbedding { */ truncate_inputs?: boolean; } -type Ai_Cf_Baai_Bge_M3_Output = BGEM3OuputQuery | BGEM3OutputEmbeddingForContexts | BGEM3OuputEmbedding; +interface BGEM3InputQueryAndContexts1 { + /** + * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts + */ + query?: string; + /** + * List of provided contexts. Note that the index in this array is important, as the response will refer to it. + */ + contexts: { + /** + * One of the provided context content + */ + text?: string; + }[]; + /** + * When provided with too long context should the model error out or truncate the context to fit? + */ + truncate_inputs?: boolean; +} +interface BGEM3InputEmbedding1 { + text: string | string[]; + /** + * When provided with too long context should the model error out or truncate the context to fit? + */ + truncate_inputs?: boolean; +} +type Ai_Cf_Baai_Bge_M3_Output = BGEM3OuputQuery | BGEM3OutputEmbeddingForContexts | BGEM3OuputEmbedding | AsyncResponse; interface BGEM3OuputQuery { response?: { /** @@ -2994,21 +3242,1426 @@ interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input { } interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output { /** - * The generated image in Base64 format. + * The generated image in Base64 format. + */ + image?: string; +} +declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell { + inputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input; + postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output; +} +type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input = Prompt | Messages; +interface Prompt { + /** + * The input text prompt for the model to generate a response. + */ + prompt: string; + image?: number[] | (string & NonNullable); + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; + /** + * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. + */ + lora?: string; +} +interface Messages { + /** + * An array of message objects representing the conversation history. + */ + messages: { + /** + * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). + */ + role?: string; + /** + * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001 + */ + tool_call_id?: string; + content?: string | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }[] | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }; + }[]; + image?: number[] | (string & NonNullable); + functions?: { + name: string; + code: string; + }[]; + /** + * A list of tools available for the assistant to use. + */ + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; + /** + * If true, the response will be streamed back incrementally. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output = { + /** + * The generated text response from the model + */ + response?: string; + /** + * An array of tool calls requests made during the response generation + */ + tool_calls?: { + /** + * The arguments passed to be passed to the tool call request + */ + arguments?: object; + /** + * The name of the tool to be called + */ + name?: string; + }[]; +}; +declare abstract class Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct { + inputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input; + postProcessedOutputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output; +} +type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input = Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt | Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages | AsyncBatch; +interface Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt { + /** + * The input text prompt for the model to generate a response. + */ + prompt: string; + /** + * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. + */ + lora?: string; + response_format?: JSONMode; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +interface JSONMode { + type?: "json_object" | "json_schema"; + json_schema?: unknown; +} +interface Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages { + /** + * An array of message objects representing the conversation history. + */ + messages: { + /** + * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). + */ + role: string; + /** + * The content of the message as a string. + */ + content: string; + }[]; + functions?: { + name: string; + code: string; + }[]; + /** + * A list of tools available for the assistant to use. + */ + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; + response_format?: JSONMode; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +interface AsyncBatch { + requests?: { + /** + * User-supplied reference. This field will be present in the response as well it can be used to reference the request and response. It's NOT validated to be unique. + */ + external_reference?: string; + /** + * Prompt for the text generation model + */ + prompt?: string; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; + response_format?: JSONMode; + }[]; +} +type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output = { + /** + * The generated text response from the model + */ + response: string; + /** + * Usage statistics for the inference request + */ + usage?: { + /** + * Total number of tokens in input + */ + prompt_tokens?: number; + /** + * Total number of tokens in output + */ + completion_tokens?: number; + /** + * Total number of input and output tokens + */ + total_tokens?: number; + }; + /** + * An array of tool calls requests made during the response generation + */ + tool_calls?: { + /** + * The arguments passed to be passed to the tool call request + */ + arguments?: object; + /** + * The name of the tool to be called + */ + name?: string; + }[]; +} | AsyncResponse; +declare abstract class Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast { + inputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input; + postProcessedOutputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output; +} +interface Ai_Cf_Meta_Llama_Guard_3_8B_Input { + /** + * An array of message objects representing the conversation history. + */ + messages: { + /** + * The role of the message sender must alternate between 'user' and 'assistant'. + */ + role: "user" | "assistant"; + /** + * The content of the message as a string. + */ + content: string; + }[]; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Dictate the output format of the generated response. + */ + response_format?: { + /** + * Set to json_object to process and output generated text as JSON. + */ + type?: string; + }; +} +interface Ai_Cf_Meta_Llama_Guard_3_8B_Output { + response?: string | { + /** + * Whether the conversation is safe or not. + */ + safe?: boolean; + /** + * A list of what hazard categories predicted for the conversation, if the conversation is deemed unsafe. + */ + categories?: string[]; + }; + /** + * Usage statistics for the inference request + */ + usage?: { + /** + * Total number of tokens in input + */ + prompt_tokens?: number; + /** + * Total number of tokens in output + */ + completion_tokens?: number; + /** + * Total number of input and output tokens + */ + total_tokens?: number; + }; +} +declare abstract class Base_Ai_Cf_Meta_Llama_Guard_3_8B { + inputs: Ai_Cf_Meta_Llama_Guard_3_8B_Input; + postProcessedOutputs: Ai_Cf_Meta_Llama_Guard_3_8B_Output; +} +interface Ai_Cf_Baai_Bge_Reranker_Base_Input { + /** + * A query you wish to perform against the provided contexts. + */ + query: string; + /** + * Number of returned results starting with the best score. + */ + top_k?: number; + /** + * List of provided contexts. Note that the index in this array is important, as the response will refer to it. + */ + contexts: { + /** + * One of the provided context content + */ + text?: string; + }[]; +} +interface Ai_Cf_Baai_Bge_Reranker_Base_Output { + response?: { + /** + * Index of the context in the request + */ + id?: number; + /** + * Score of the context under the index. + */ + score?: number; + }[]; +} +declare abstract class Base_Ai_Cf_Baai_Bge_Reranker_Base { + inputs: Ai_Cf_Baai_Bge_Reranker_Base_Input; + postProcessedOutputs: Ai_Cf_Baai_Bge_Reranker_Base_Output; +} +type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input = Qwen2_5_Coder_32B_Instruct_Prompt | Qwen2_5_Coder_32B_Instruct_Messages; +interface Qwen2_5_Coder_32B_Instruct_Prompt { + /** + * The input text prompt for the model to generate a response. + */ + prompt: string; + /** + * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. + */ + lora?: string; + response_format?: JSONMode; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +interface Qwen2_5_Coder_32B_Instruct_Messages { + /** + * An array of message objects representing the conversation history. + */ + messages: { + /** + * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). + */ + role: string; + /** + * The content of the message as a string. + */ + content: string; + }[]; + functions?: { + name: string; + code: string; + }[]; + /** + * A list of tools available for the assistant to use. + */ + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; + response_format?: JSONMode; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output = { + /** + * The generated text response from the model + */ + response: string; + /** + * Usage statistics for the inference request + */ + usage?: { + /** + * Total number of tokens in input + */ + prompt_tokens?: number; + /** + * Total number of tokens in output + */ + completion_tokens?: number; + /** + * Total number of input and output tokens + */ + total_tokens?: number; + }; + /** + * An array of tool calls requests made during the response generation + */ + tool_calls?: { + /** + * The arguments passed to be passed to the tool call request + */ + arguments?: object; + /** + * The name of the tool to be called + */ + name?: string; + }[]; +}; +declare abstract class Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct { + inputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input; + postProcessedOutputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output; +} +type Ai_Cf_Qwen_Qwq_32B_Input = Qwen_Qwq_32B_Prompt | Qwen_Qwq_32B_Messages; +interface Qwen_Qwq_32B_Prompt { + /** + * The input text prompt for the model to generate a response. + */ + prompt: string; + /** + * JSON schema that should be fulfilled for the response. + */ + guided_json?: object; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +interface Qwen_Qwq_32B_Messages { + /** + * An array of message objects representing the conversation history. + */ + messages: { + /** + * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). + */ + role?: string; + /** + * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001 + */ + tool_call_id?: string; + content?: string | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }[] | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }; + }[]; + functions?: { + name: string; + code: string; + }[]; + /** + * A list of tools available for the assistant to use. + */ + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; + /** + * JSON schema that should be fufilled for the response. + */ + guided_json?: object; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +type Ai_Cf_Qwen_Qwq_32B_Output = { + /** + * The generated text response from the model + */ + response: string; + /** + * Usage statistics for the inference request + */ + usage?: { + /** + * Total number of tokens in input + */ + prompt_tokens?: number; + /** + * Total number of tokens in output + */ + completion_tokens?: number; + /** + * Total number of input and output tokens + */ + total_tokens?: number; + }; + /** + * An array of tool calls requests made during the response generation + */ + tool_calls?: { + /** + * The arguments passed to be passed to the tool call request + */ + arguments?: object; + /** + * The name of the tool to be called + */ + name?: string; + }[]; +}; +declare abstract class Base_Ai_Cf_Qwen_Qwq_32B { + inputs: Ai_Cf_Qwen_Qwq_32B_Input; + postProcessedOutputs: Ai_Cf_Qwen_Qwq_32B_Output; +} +type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input = Mistral_Small_3_1_24B_Instruct_Prompt | Mistral_Small_3_1_24B_Instruct_Messages; +interface Mistral_Small_3_1_24B_Instruct_Prompt { + /** + * The input text prompt for the model to generate a response. + */ + prompt: string; + /** + * JSON schema that should be fulfilled for the response. + */ + guided_json?: object; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +interface Mistral_Small_3_1_24B_Instruct_Messages { + /** + * An array of message objects representing the conversation history. + */ + messages: { + /** + * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). + */ + role?: string; + /** + * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001 + */ + tool_call_id?: string; + content?: string | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }[] | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }; + }[]; + functions?: { + name: string; + code: string; + }[]; + /** + * A list of tools available for the assistant to use. + */ + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; + /** + * JSON schema that should be fufilled for the response. + */ + guided_json?: object; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output = { + /** + * The generated text response from the model + */ + response: string; + /** + * Usage statistics for the inference request + */ + usage?: { + /** + * Total number of tokens in input + */ + prompt_tokens?: number; + /** + * Total number of tokens in output + */ + completion_tokens?: number; + /** + * Total number of input and output tokens + */ + total_tokens?: number; + }; + /** + * An array of tool calls requests made during the response generation */ - image?: string; -} -declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell { - inputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input; - postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output; + tool_calls?: { + /** + * The arguments passed to be passed to the tool call request + */ + arguments?: object; + /** + * The name of the tool to be called + */ + name?: string; + }[]; +}; +declare abstract class Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct { + inputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input; + postProcessedOutputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output; } -type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input = Prompt | Messages; -interface Prompt { +type Ai_Cf_Google_Gemma_3_12B_It_Input = Google_Gemma_3_12B_It_Prompt | Google_Gemma_3_12B_It_Messages; +interface Google_Gemma_3_12B_It_Prompt { /** * The input text prompt for the model to generate a response. */ prompt: string; - image?: number[] | (string & NonNullable); + /** + * JSON schema that should be fufilled for the response. + */ + guided_json?: object; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ @@ -3049,12 +4702,8 @@ interface Prompt { * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; - /** - * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. - */ - lora?: string; } -interface Messages { +interface Google_Gemma_3_12B_It_Messages { /** * An array of message objects representing the conversation history. */ @@ -3062,13 +4711,33 @@ interface Messages { /** * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ - role: string; - /** - * The content of the message as a string. - */ - content: string; + role?: string; + content?: string | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }[] | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }; }[]; - image?: number[] | string; functions?: { name: string; code: string; @@ -3161,7 +4830,15 @@ interface Messages { }; })[]; /** - * If true, the response will be streamed back incrementally. + * JSON schema that should be fufilled for the response. + */ + guided_json?: object; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. */ stream?: boolean; /** @@ -3173,7 +4850,7 @@ interface Messages { */ temperature?: number; /** - * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** @@ -3197,72 +4874,11 @@ interface Messages { */ presence_penalty?: number; } -type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output = { +type Ai_Cf_Google_Gemma_3_12B_It_Output = { /** * The generated text response from the model */ - response?: string; - /** - * An array of tool calls requests made during the response generation - */ - tool_calls?: { - /** - * The arguments passed to be passed to the tool call request - */ - arguments?: object; - /** - * The name of the tool to be called - */ - name?: string; - }[]; -} | ReadableStream; -declare abstract class Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct { - inputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input; - postProcessedOutputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output; -} -interface Ai_Cf_Meta_Llama_Guard_3_8B_Input { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender must alternate between 'user' and 'assistant'. - */ - role: "user" | "assistant"; - /** - * The content of the message as a string. - */ - content: string; - }[]; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Dictate the output format of the generated response. - */ - response_format?: { - /** - * Set to json_object to process and output generated text as JSON. - */ - type?: string; - }; -} -interface Ai_Cf_Meta_Llama_Guard_3_8B_Output { - response?: string | { - /** - * Whether the conversation is safe or not. - */ - safe?: boolean; - /** - * A list of what hazard categories predicted for the conversation, if the conversation is deemed unsafe. - */ - categories?: string[]; - }; + response: string; /** * Usage statistics for the inference request */ @@ -3280,44 +4896,23 @@ interface Ai_Cf_Meta_Llama_Guard_3_8B_Output { */ total_tokens?: number; }; -} -declare abstract class Base_Ai_Cf_Meta_Llama_Guard_3_8B { - inputs: Ai_Cf_Meta_Llama_Guard_3_8B_Input; - postProcessedOutputs: Ai_Cf_Meta_Llama_Guard_3_8B_Output; -} -interface Ai_Cf_Baai_Bge_Reranker_Base_Input { - /** - * A query you wish to perform against the provided contexts. - */ - /** - * Number of returned results starting with the best score. - */ - top_k?: number; /** - * List of provided contexts. Note that the index in this array is important, as the response will refer to it. + * An array of tool calls requests made during the response generation */ - contexts: { - /** - * One of the provided context content - */ - text?: string; - }[]; -} -interface Ai_Cf_Baai_Bge_Reranker_Base_Output { - response?: { + tool_calls?: { /** - * Index of the context in the request + * The arguments passed to be passed to the tool call request */ - id?: number; + arguments?: object; /** - * Score of the context under the index. + * The name of the tool to be called */ - score?: number; + name?: string; }[]; -} -declare abstract class Base_Ai_Cf_Baai_Bge_Reranker_Base { - inputs: Ai_Cf_Baai_Bge_Reranker_Base_Input; - postProcessedOutputs: Ai_Cf_Baai_Bge_Reranker_Base_Output; +}; +declare abstract class Base_Ai_Cf_Google_Gemma_3_12B_It { + inputs: Ai_Cf_Google_Gemma_3_12B_It_Input; + postProcessedOutputs: Ai_Cf_Google_Gemma_3_12B_It_Output; } type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input = Ai_Cf_Meta_Llama_4_Prompt | Ai_Cf_Meta_Llama_4_Messages; interface Ai_Cf_Meta_Llama_4_Prompt { @@ -3329,6 +4924,7 @@ interface Ai_Cf_Meta_Llama_4_Prompt { * JSON schema that should be fulfilled for the response. */ guided_json?: object; + response_format?: JSONMode; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ @@ -3380,7 +4976,7 @@ interface Ai_Cf_Meta_Llama_4_Messages { */ role?: string; /** - * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001 + * The tool call id. If you don't know what to put here you can fall back to 000000001 */ tool_call_id?: string; content?: string | { @@ -3500,6 +5096,7 @@ interface Ai_Cf_Meta_Llama_4_Messages { }; }; })[]; + response_format?: JSONMode; /** * JSON schema that should be fufilled for the response. */ @@ -3572,15 +5169,28 @@ type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output = { */ tool_calls?: { /** - * The arguments passed to be passed to the tool call request + * The tool call id. */ - arguments?: object; + id?: string; /** - * The name of the tool to be called + * Specifies the type of tool (e.g., 'function'). */ - name?: string; + type?: string; + /** + * Details of the function tool. + */ + function?: { + /** + * The name of the tool to be called + */ + name?: string; + /** + * The arguments passed to be passed to the tool call request + */ + arguments?: object; + }; }[]; -} | string; +}; declare abstract class Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct { inputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input; postProcessedOutputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output; @@ -3593,9 +5203,6 @@ interface AiModels { "@cf/lykon/dreamshaper-8-lcm": BaseAiTextToImage; "@cf/bytedance/stable-diffusion-xl-lightning": BaseAiTextToImage; "@cf/myshell-ai/melotts": BaseAiTextToSpeech; - "@cf/baai/bge-base-en-v1.5": BaseAiTextEmbeddings; - "@cf/baai/bge-small-en-v1.5": BaseAiTextEmbeddings; - "@cf/baai/bge-large-en-v1.5": BaseAiTextEmbeddings; "@cf/microsoft/resnet-50": BaseAiImageClassification; "@cf/facebook/detr-resnet-50": BaseAiObjectDetection; "@cf/meta/llama-2-7b-chat-int8": BaseAiTextGeneration; @@ -3637,23 +5244,35 @@ interface AiModels { "@cf/meta/llama-3.1-8b-instruct-awq": BaseAiTextGeneration; "@cf/meta/llama-3.2-3b-instruct": BaseAiTextGeneration; "@cf/meta/llama-3.2-1b-instruct": BaseAiTextGeneration; - "@cf/meta/llama-3.3-70b-instruct-fp8-fast": BaseAiTextGeneration; "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b": BaseAiTextGeneration; - "@cf/meta/m2m100-1.2b": BaseAiTranslation; "@cf/facebook/bart-large-cnn": BaseAiSummarization; "@cf/llava-hf/llava-1.5-7b-hf": BaseAiImageToText; + "@cf/baai/bge-base-en-v1.5": Base_Ai_Cf_Baai_Bge_Base_En_V1_5; "@cf/openai/whisper": Base_Ai_Cf_Openai_Whisper; + "@cf/meta/m2m100-1.2b": Base_Ai_Cf_Meta_M2M100_1_2B; + "@cf/baai/bge-small-en-v1.5": Base_Ai_Cf_Baai_Bge_Small_En_V1_5; + "@cf/baai/bge-large-en-v1.5": Base_Ai_Cf_Baai_Bge_Large_En_V1_5; "@cf/unum/uform-gen2-qwen-500m": Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M; "@cf/openai/whisper-tiny-en": Base_Ai_Cf_Openai_Whisper_Tiny_En; "@cf/openai/whisper-large-v3-turbo": Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo; "@cf/baai/bge-m3": Base_Ai_Cf_Baai_Bge_M3; "@cf/black-forest-labs/flux-1-schnell": Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell; "@cf/meta/llama-3.2-11b-vision-instruct": Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct; + "@cf/meta/llama-3.3-70b-instruct-fp8-fast": Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast; "@cf/meta/llama-guard-3-8b": Base_Ai_Cf_Meta_Llama_Guard_3_8B; "@cf/baai/bge-reranker-base": Base_Ai_Cf_Baai_Bge_Reranker_Base; + "@cf/qwen/qwen2.5-coder-32b-instruct": Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct; + "@cf/qwen/qwq-32b": Base_Ai_Cf_Qwen_Qwq_32B; + "@cf/mistralai/mistral-small-3.1-24b-instruct": Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct; + "@cf/google/gemma-3-12b-it": Base_Ai_Cf_Google_Gemma_3_12B_It; "@cf/meta/llama-4-scout-17b-16e-instruct": Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct; } type AiOptions = { + /** + * Send requests as an asynchronous batch job, only works for supported models + * https://developers.cloudflare.com/workers-ai/features/batch-api + */ + queueRequest?: boolean; gateway?: GatewayOptions; returnRawResponse?: boolean; prefix?: string; @@ -3699,10 +5318,12 @@ type AiModelListType = Record; declare abstract class Ai { aiGatewayLogId: string | null; gateway(gatewayId: string): AiGateway; - autorag(autoragId: string): AutoRAG; - run(model: Name, inputs: AiModelList[Name]["inputs"], options?: Options): Promise(model: Name, inputs: InputOptions, options?: Options): Promise; + } ? Response : InputOptions extends { + stream: true; + } ? ReadableStream : AiModelList[Name]["postProcessedOutputs"]>; models(params?: AiModelsSearchParams): Promise; toMarkdown(files: { name: string; @@ -3735,6 +5356,12 @@ type GatewayOptions = { requestTimeoutMs?: number; retries?: GatewayRetries; }; +type UniversalGatewayOptions = Exclude & { + /** + ** @deprecated + */ + id?: string; +}; type AiGatewayPatchLog = { score?: number | null; feedback?: -1 | 1 | null; @@ -3803,7 +5430,7 @@ declare abstract class AiGateway { patchLog(logId: string, data: AiGatewayPatchLog): Promise; getLog(logId: string): Promise; run(data: AIGatewayUniversalRequest | AIGatewayUniversalRequest[], options?: { - gateway?: GatewayOptions; + gateway?: UniversalGatewayOptions; extraHeaders?: object; }): Promise; getUrl(provider?: AIGatewayProviders | string): Promise; // eslint-disable-line @@ -3814,8 +5441,20 @@ interface AutoRAGNotFoundError extends Error { } interface AutoRAGUnauthorizedError extends Error { } +interface AutoRAGNameNotSetError extends Error { +} +type ComparisonFilter = { + key: string; + type: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte'; + value: string | number | boolean; +}; +type CompoundFilter = { + type: 'and' | 'or'; + filters: ComparisonFilter[]; +}; type AutoRagSearchRequest = { query: string; + filters?: CompoundFilter | ComparisonFilter; max_num_results?: number; ranking_options?: { ranker?: string; @@ -3825,6 +5464,7 @@ type AutoRagSearchRequest = { }; type AutoRagAiSearchRequest = AutoRagSearchRequest & { stream?: boolean; + system_prompt?: string; }; type AutoRagAiSearchRequestStreaming = Omit & { stream: true; @@ -3845,10 +5485,20 @@ type AutoRagSearchResponse = { has_more: boolean; next_page: string | null; }; +type AutoRagListResponse = { + id: string; + enable: boolean; + type: string; + source: string; + vectorize_name: string; + paused: boolean; + status: string; +}[]; type AutoRagAiSearchResponse = AutoRagSearchResponse & { response: string; }; declare abstract class AutoRAG { + list(): Promise; search(params: AutoRagSearchRequest): Promise; aiSearch(params: AutoRagAiSearchRequestStreaming): Promise; aiSearch(params: AutoRagAiSearchRequest): Promise; @@ -3890,6 +5540,12 @@ interface BasicImageTransformations { * breaks aspect ratio */ fit?: "scale-down" | "contain" | "cover" | "crop" | "pad" | "squeeze"; + /** + * Image segmentation using artificial intelligence models. Sets pixels not + * within selected segment area to transparent e.g "foreground" sets every + * background pixel as transparent. + */ + segment?: "foreground"; /** * When cropping with fit: "cover", this defines the side or point that should * be left uncropped. The value is either a string @@ -3902,7 +5558,7 @@ interface BasicImageTransformations { * preserve as much as possible around a point at 20% of the height of the * source image. */ - gravity?: 'left' | 'right' | 'top' | 'bottom' | 'center' | 'auto' | 'entropy' | BasicImageTransformationsGravityCoordinates; + gravity?: 'face' | 'left' | 'right' | 'top' | 'bottom' | 'center' | 'auto' | 'entropy' | BasicImageTransformationsGravityCoordinates; /** * Background color to add underneath the image. Applies only to images with * transparency (such as PNG). Accepts any CSS color (#RRGGBB, rgba(…), @@ -4189,13 +5845,13 @@ interface IncomingRequestCfPropertiesBase extends Record { * * @example 395747 */ - asn: number; + asn?: number; /** * The organization which owns the ASN of the incoming request. * * @example "Google Cloud" */ - asOrganization: string; + asOrganization?: string; /** * The original value of the `Accept-Encoding` header if Cloudflare modified it. * @@ -4319,7 +5975,7 @@ interface IncomingRequestCfPropertiesCloudflareForSaaSEnterprise { * This field is only present if you have Cloudflare for SaaS enabled on your account * and you have followed the [required steps to enable it]((https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/custom-metadata/)). */ - hostMetadata: HostMetadata; + hostMetadata?: HostMetadata; } interface IncomingRequestCfPropertiesCloudflareAccessOrApiShield { /** @@ -4745,6 +6401,22 @@ declare module "cloudflare:email" { }; export { _EmailMessage as EmailMessage }; } +/** + * Hello World binding to serve as an explanatory example. DO NOT USE + */ +interface HelloWorldBinding { + /** + * Retrieve the current stored value + */ + get(): Promise<{ + value: string; + ms?: number; + }>; + /** + * Set a new stored value + */ + set(value: string): Promise; +} interface Hyperdrive { /** * Connect directly to Hyperdrive as if it's your database, returning a TCP socket. @@ -4822,7 +6494,8 @@ type ImageTransform = { fit?: 'scale-down' | 'contain' | 'pad' | 'squeeze' | 'cover' | 'crop'; flip?: 'h' | 'v' | 'hv'; gamma?: number; - gravity?: 'left' | 'right' | 'top' | 'bottom' | 'center' | 'auto' | 'entropy' | { + segment?: 'foreground'; + gravity?: 'face' | 'left' | 'right' | 'top' | 'bottom' | 'center' | 'auto' | 'entropy' | { x?: number; y?: number; mode: 'remainder' | 'box-center'; @@ -4830,7 +6503,7 @@ type ImageTransform = { rotate?: 0 | 90 | 180 | 270; saturation?: number; sharpen?: number; - trim?: "border" | { + trim?: 'border' | { top?: number; bottom?: number; left?: number; @@ -4852,6 +6525,9 @@ type ImageDrawOptions = { bottom?: number; right?: number; }; +type ImageInputOptions = { + encoding?: 'base64'; +}; type ImageOutputOptions = { format: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp' | 'image/avif' | 'rgb' | 'rgba'; quality?: number; @@ -4863,13 +6539,13 @@ interface ImagesBinding { * @throws {@link ImagesError} with code 9412 if input is not an image * @param stream The image bytes */ - info(stream: ReadableStream): Promise; + info(stream: ReadableStream, options?: ImageInputOptions): Promise; /** * Begin applying a series of transformations to an image * @param stream The image bytes * @returns A transform handle */ - input(stream: ReadableStream): ImageTransformer; + input(stream: ReadableStream, options?: ImageInputOptions): ImageTransformer; } interface ImageTransformer { /** @@ -4892,6 +6568,9 @@ interface ImageTransformer { */ output(options: ImageOutputOptions): Promise; } +type ImageTransformationOutputOptions = { + encoding?: 'base64'; +}; interface ImageTransformationResult { /** * The image as a response, ready to store in cache or return to users @@ -4904,7 +6583,7 @@ interface ImageTransformationResult { /** * The bytes of the response */ - image(): ReadableStream; + image(options?: ImageTransformationOutputOptions): ReadableStream; } interface ImagesError extends Error { readonly code: number; @@ -5129,6 +6808,19 @@ declare namespace Cloudflare { interface Env { } } +declare module 'cloudflare:node' { + export interface DefaultHandler { + fetch?(request: Request): Response | Promise; + tail?(events: TraceItem[]): void | Promise; + trace?(traces: TraceItem[]): void | Promise; + scheduled?(controller: ScheduledController): void | Promise; + queue?(batch: MessageBatch): void | Promise; + test?(controller: TestController): void | Promise; + } + export function httpServerHandler(options: { + port: number; + }, handlers?: Omit): DefaultHandler; +} declare module 'cloudflare:workers' { export type RpcStub = Rpc.Stub; export const RpcStub: { @@ -5165,6 +6857,7 @@ declare module 'cloudflare:workers' { export type WorkflowSleepDuration = `${number} ${WorkflowDurationLabel}${'s' | ''}` | number; export type WorkflowDelayDuration = WorkflowSleepDuration; export type WorkflowTimeoutDuration = WorkflowSleepDuration; + export type WorkflowRetentionDuration = WorkflowSleepDuration; export type WorkflowBackoff = 'constant' | 'linear' | 'exponential'; export type WorkflowStepConfig = { retries?: { @@ -5201,6 +6894,7 @@ declare module 'cloudflare:workers' { constructor(ctx: ExecutionContext, env: Env); run(event: Readonly>, step: WorkflowStep): Promise; } + export function waitUntil(promise: Promise): void; export const env: Cloudflare.Env; } interface SecretsStoreSecret { @@ -5223,7 +6917,7 @@ declare namespace TailStream { readonly type: "fetch"; readonly method: string; readonly url: string; - readonly cfJson: string; + readonly cfJson?: object; readonly headers: Header[]; } interface JsRpcEventInfo { @@ -5269,10 +6963,6 @@ declare namespace TailStream { readonly type: "hibernatableWebSocket"; readonly info: HibernatableWebSocketEventInfoClose | HibernatableWebSocketEventInfoError | HibernatableWebSocketEventInfoMessage; } - interface Resume { - readonly type: "resume"; - readonly attachment?: any; - } interface CustomEventInfo { readonly type: "custom"; } @@ -5293,13 +6983,15 @@ declare namespace TailStream { } interface Onset { readonly type: "onset"; + readonly attributes: Attribute[]; readonly dispatchNamespace?: string; readonly entrypoint?: string; + readonly executionModel: string; readonly scriptName?: string; readonly scriptTags?: string[]; readonly scriptVersion?: ScriptVersion; readonly trigger?: Trigger; - readonly info: FetchEventInfo | JsRpcEventInfo | ScheduledEventInfo | AlarmEventInfo | QueueEventInfo | EmailEventInfo | TraceEventInfo | HibernatableWebSocketEventInfo | Resume | CustomEventInfo; + readonly info: FetchEventInfo | JsRpcEventInfo | ScheduledEventInfo | AlarmEventInfo | QueueEventInfo | EmailEventInfo | TraceEventInfo | HibernatableWebSocketEventInfo | CustomEventInfo; } interface Outcome { readonly type: "outcome"; @@ -5307,13 +6999,10 @@ declare namespace TailStream { readonly cpuTime: number; readonly wallTime: number; } - interface Hibernate { - readonly type: "hibernate"; - } interface SpanOpen { readonly type: "spanOpen"; - readonly op?: string; - readonly info?: FetchEventInfo | JsRpcEventInfo | Attribute[]; + readonly name: string; + readonly info?: FetchEventInfo | JsRpcEventInfo | Attributes; } interface SpanClose { readonly type: "spanClose"; @@ -5333,36 +7022,39 @@ declare namespace TailStream { interface Log { readonly type: "log"; readonly level: "debug" | "error" | "info" | "log" | "warn"; - readonly message: string; + readonly message: object; } interface Return { readonly type: "return"; - readonly info?: FetchResponseInfo | Attribute[]; - } - interface Link { - readonly type: "link"; - readonly label?: string; - readonly traceId: string; - readonly invocationId: string; - readonly spanId: string; + readonly info?: FetchResponseInfo; } interface Attribute { - readonly type: "attribute"; readonly name: string; - readonly value: string | string[] | boolean | boolean[] | number | number[]; + readonly value: string | string[] | boolean | boolean[] | number | number[] | bigint | bigint[]; } - type Mark = DiagnosticChannelEvent | Exception | Log | Return | Link | Attribute[]; - interface TailEvent { - readonly traceId: string; + interface Attributes { + readonly type: "attributes"; + readonly info: Attribute[]; + } + type EventType = Onset | Outcome | SpanOpen | SpanClose | DiagnosticChannelEvent | Exception | Log | Return | Attributes; + interface TailEvent { readonly invocationId: string; readonly spanId: string; readonly timestamp: Date; readonly sequence: number; - readonly event: Onset | Outcome | Hibernate | SpanOpen | SpanClose | Mark; + readonly event: Event; } - type TailEventHandler = (event: TailEvent) => void | Promise; - type TailEventHandlerName = "onset" | "outcome" | "hibernate" | "spanOpen" | "spanClose" | "diagnosticChannel" | "exception" | "log" | "return" | "link" | "attribute"; - type TailEventHandlerObject = Record; + type TailEventHandler = (event: TailEvent) => void | Promise; + type TailEventHandlerObject = { + outcome?: TailEventHandler; + spanOpen?: TailEventHandler; + spanClose?: TailEventHandler; + diagnosticChannel?: TailEventHandler; + exception?: TailEventHandler; + log?: TailEventHandler; + return?: TailEventHandler; + attributes?: TailEventHandler; + }; type TailEventHandlerType = TailEventHandler | TailEventHandlerObject; } // Copyright (c) 2022-2023 Cloudflare, Inc. @@ -5675,6 +7367,9 @@ declare abstract class Workflow { */ public createBatch(batch: WorkflowInstanceCreateOptions[]): Promise; } +type WorkflowDurationLabel = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year'; +type WorkflowSleepDuration = `${number} ${WorkflowDurationLabel}${'s' | ''}` | number; +type WorkflowRetentionDuration = WorkflowSleepDuration; interface WorkflowInstanceCreateOptions { /** * An id for your Workflow instance. Must be unique within the Workflow. @@ -5684,6 +7379,14 @@ interface WorkflowInstanceCreateOptions { * The event payload the Workflow instance is triggered with */ params?: PARAMS; + /** + * The retention policy for Workflow instance. + * Defaults to the maximum retention period available for the owner's account. + */ + retention?: { + successRetention?: WorkflowRetentionDuration; + errorRetention?: WorkflowRetentionDuration; + }; } type InstanceStatus = { status: 'queued' // means that instance is waiting to be started (see concurrency limits) diff --git a/cloudflare/example/wrangler.toml b/cloudflare/example/wrangler.toml index e8f3734..13b6777 100644 --- a/cloudflare/example/wrangler.toml +++ b/cloudflare/example/wrangler.toml @@ -6,6 +6,9 @@ compatibility_flags = ["nodejs_compat"] [browser] binding = "BROWSER" +[ai] +binding = "AI" + [[migrations]] tag = "v1" new_sqlite_classes = ["PlaywrightMCP"] diff --git a/cloudflare/experimental.d.ts b/cloudflare/experimental.d.ts new file mode 100644 index 0000000..da8a15c --- /dev/null +++ b/cloudflare/experimental.d.ts @@ -0,0 +1,23 @@ +import { Browser, BrowserContext, BrowserEndpoint } from '@cloudflare/playwright'; +import { ToolSet } from 'ai'; +import { ToolCapability } from './index.js'; + +/** + * ToolsProvider interface for providing Playwright MCP tools. + */ +export interface ToolsProvider { + /** + * Returns a ToolSet containing all available tools mapped from the context + * Each tool includes parameters, description, and execute function + */ + tools(): ToolSet; + + /** + * Closes the underlying browser context and cleans up resources + */ + close(): Promise; + + [Symbol.asyncDispose](): Promise; +} + +export declare function createToolsProvider(endpoint: BrowserEndpoint | Browser | BrowserContext, options?: { capabilities?: ToolCapability[] }): Promise; diff --git a/cloudflare/index.d.ts b/cloudflare/index.d.ts index 4f0a4ef..112c0e3 100644 --- a/cloudflare/index.d.ts +++ b/cloudflare/index.d.ts @@ -19,23 +19,16 @@ import { env } from 'cloudflare:workers'; import { McpAgent } from 'agents/mcp'; import { BrowserEndpoint } from '@cloudflare/playwright'; -type ToolCapability = 'core' | 'tabs' | 'pdf' | 'history' | 'wait' | 'files'; +export type ToolCapability = 'core' | 'core-tabs' | 'core-install' | 'vision' | 'pdf'; type Options = { - /** - * Enable vision capabilities (e.g., visual automation or OCR). - */ - vision?: boolean; - /** - * List of enabled tool capabilities. Possible values: - * - 'core': Core browser automation features. - * - 'tabs': Tab management features. - * - 'pdf': PDF generation and manipulation. - * - 'history': Browser history access. - * - 'wait': Wait and timing utilities. - * - 'files': File upload/download support. - */ - capabilities?: ToolCapability[]; + /** + * List of enabled tool capabilities. Possible values: + * - 'core': Core browser automation features. + * - 'pdf': PDF generation and manipulation. + * - 'vision': Coordinate-based interactions. + */ + capabilities?: ToolCapability[]; }; export declare function createMcpAgent(endpoint: BrowserEndpoint, options?: Options): typeof McpAgent; export {}; diff --git a/cloudflare/package-lock.json b/cloudflare/package-lock.json index 82d1cab..b42f860 100644 --- a/cloudflare/package-lock.json +++ b/cloudflare/package-lock.json @@ -9,18 +9,22 @@ "version": "0.0.1-next", "license": "Apache-2.0", "dependencies": { - "@cloudflare/playwright": "^0.0.11", - "@modelcontextprotocol/sdk": "^1.17.0", - "agents": "^0.0.109", + "@cloudflare/playwright": "https://pkg.pr.new/cloudflare/playwright/@cloudflare/playwright@71", + "@modelcontextprotocol/sdk": "^1.16.0", + "agents": "^0.0.113", "yaml": "^2.8.0", "zod-to-json-schema": "^3.24.6" }, "devDependencies": { "@cloudflare/workers-types": "^4.20250725.0", + "pkg-pr-new": "^0.0.59", "vite": "^7.0.6" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "ai": "^4.3.19" } }, "../../agents": { @@ -72,6 +76,45 @@ "zod": "^3.24.3" } }, + "node_modules/@actions/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", + "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/exec": "^1.1.1", + "@actions/http-client": "^2.0.1" + } + }, + "node_modules/@actions/exec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/io": "^1.0.1" + } + }, + "node_modules/@actions/http-client": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", + "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "node_modules/@actions/io": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@ai-sdk/provider": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.3.tgz", @@ -182,9 +225,9 @@ } }, "node_modules/@cloudflare/playwright": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/@cloudflare/playwright/-/playwright-0.0.11.tgz", - "integrity": "sha512-5meAHlST5K3MKYA7TyL6Ns1XQhYB615qYZxDSn0UnSX14MFgRe+pqlq482dXbgMBappnvvsjZl+Ea3/FQcdbvQ==", + "version": "0.0.1-next", + "resolved": "https://pkg.pr.new/cloudflare/playwright/@cloudflare/playwright@71", + "integrity": "sha512-/U3sBV6qnz1Zw+V6GkNQQme3DWU8s05T3EuqKbpOHL0B++l+fm7kh7bmzNwXpHuIjB4ds4TphM6vPdnNSTnMIA==", "license": "Apache-2.0" }, "node_modules/@cloudflare/workers-types": { @@ -618,10 +661,36 @@ "node": ">=18" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@jsdevtools/ez-spawn": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@jsdevtools/ez-spawn/-/ez-spawn-3.0.4.tgz", + "integrity": "sha512-f5DRIOZf7wxogefH03RjMPMdBF7ADTWUMoOs9kaJo06EfwF+aFhMZMDZxHg/Xe12hptN9xoZjGso2fdjapBRIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-me-maybe": "^1.0.1", + "cross-spawn": "^7.0.3", + "string-argv": "^0.3.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.0.tgz", - "integrity": "sha512-qFfbWFA7r1Sd8D697L7GkTd36yqDuTkvz0KfOGkgXR8EUhQn3/EDNIR/qUdQNMT8IjmasBvHWuXeisxtXTQT2g==", + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.5.tgz", + "integrity": "sha512-QakrKIGniGuRVfWBdMsDea/dx1PNE739QJ7gCM41s9q+qaCYTHCdsIBXQVVXry3mfWAiaM9kT22Hyz53Uw8mfg==", "license": "MIT", "dependencies": { "ajv": "^6.12.6", @@ -641,6 +710,288 @@ "node": ">=18" } }, + "node_modules/@octokit/action": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/action/-/action-6.1.0.tgz", + "integrity": "sha512-lo+nHx8kAV86bxvOVOI3vFjX3gXPd/L7guAUbvs3pUvnR2KC+R7yjBkA1uACt4gYhs4LcWP3AXSGQzsbeN2XXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-action": "^4.0.0", + "@octokit/core": "^5.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/plugin-rest-endpoint-methods": "^10.0.0", + "@octokit/types": "^12.0.0", + "undici": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/action/node_modules/undici": { + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/@octokit/auth-action": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-action/-/auth-action-4.1.0.tgz", + "integrity": "sha512-m+3t7K46IYyMk7Bl6/lF4Rv09GqDZjYmNg8IWycJ2Fa3YE3DE7vQcV6G2hUPmR9NDqenefNJwVtlisMjzymPiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/types": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-action/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/auth-action/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", + "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.4.1", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/core/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", + "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/endpoint/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", + "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^8.4.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/graphql/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz", + "integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", + "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/request": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", + "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", + "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/request-error/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/request/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/request/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, "node_modules/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", @@ -956,19 +1307,32 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agents": { - "version": "0.0.109", - "resolved": "https://registry.npmjs.org/agents/-/agents-0.0.109.tgz", - "integrity": "sha512-QExGnLWyQZ0JzYOv3QAKM2LM8jmnGi+WhCASQhsCtARoGk1xfo0ho0aSjXUmPbcL0OnTsqZm3W/Lu6fygCTQgA==", + "version": "0.0.113", + "resolved": "https://registry.npmjs.org/agents/-/agents-0.0.113.tgz", + "integrity": "sha512-PnUjSwFGYMcOWTcewo4NJZqN8gU2u1LFD17OhOzc83LZHa01P2o/qjLOVT338jWPzkPalXTj0RO19s0Lgyl6Ig==", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.13.3", - "ai": "^4.3.16", + "@modelcontextprotocol/sdk": "^1.17.2", + "ai": "^4.3.19", "cron-schedule": "^5.0.4", "mimetext": "^3.0.27", "nanoid": "^5.1.5", "partyserver": "^0.0.72", - "partysocket": "1.1.4", + "partysocket": "1.1.5", "zod": "^3.25.67" }, "peerDependencies": { @@ -976,9 +1340,9 @@ } }, "node_modules/ai": { - "version": "4.3.16", - "resolved": "https://registry.npmjs.org/ai/-/ai-4.3.16.tgz", - "integrity": "sha512-KUDwlThJ5tr2Vw0A1ZkbDKNME3wzWhuVfAOwIvFUzl1TPVDFAXDFTXio3p+jaKneB+dKNCvFFlolYmmgHttG1g==", + "version": "4.3.19", + "resolved": "https://registry.npmjs.org/ai/-/ai-4.3.19.tgz", + "integrity": "sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "1.1.3", @@ -1017,6 +1381,13 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/body-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", @@ -1075,10 +1446,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "dev": true, + "license": "MIT" + }, "node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -1087,6 +1465,13 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", @@ -1190,6 +1575,16 @@ } } }, + "node_modules/decode-uri-component": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.4.1.tgz", + "integrity": "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1199,6 +1594,13 @@ "node": ">= 0.8" } }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true, + "license": "ISC" + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -1440,6 +1842,19 @@ } } }, + "node_modules/filter-obj": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-5.1.0.tgz", + "integrity": "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/finalhandler": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", @@ -1600,6 +2015,16 @@ "node": ">=0.10.0" } }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -1621,6 +2046,19 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/isbinaryfile": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.6.tgz", + "integrity": "sha512-I+NmIfBHUl+r2wcDd6JwE9yWje/PIVY/R5/CmV8dXLZd5K+L9X2klAOwfAHNnondLXkbHyTAleQAWonpTJBTtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1750,6 +2188,19 @@ "node": ">= 0.6" } }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1847,10 +2298,10 @@ } }, "node_modules/partysocket": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/partysocket/-/partysocket-1.1.4.tgz", - "integrity": "sha512-jXP7PFj2h5/v4UjDS8P7MZy6NJUQ7sspiFyxL4uc/+oKOL+KdtXzHnTV8INPGxBrLTXgalyG3kd12Qm7WrYc3A==", - "license": "ISC", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/partysocket/-/partysocket-1.1.5.tgz", + "integrity": "sha512-8uw9foq9bij4sKLCtTSHvyqMrMTQ5FJjrHc7BjoM2s95Vu7xYCN63ABpI7OZHC7ZMP5xaom/A+SsoFPXmTV6ZQ==", + "license": "MIT", "dependencies": { "event-target-polyfill": "^0.0.4" } @@ -1873,6 +2324,13 @@ "node": ">=16" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1902,6 +2360,38 @@ "node": ">=16.20.0" } }, + "node_modules/pkg-pr-new": { + "version": "0.0.59", + "resolved": "https://registry.npmjs.org/pkg-pr-new/-/pkg-pr-new-0.0.59.tgz", + "integrity": "sha512-dnSddkZUs5Qz6JhMmHTyYZuscwYRw8XbANazk0uObR89XS2N0AC0gtKMqWxYhgG14BT29kUHGy8aAOYFL1wfeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/core": "^1.11.1", + "@jsdevtools/ez-spawn": "^3.0.4", + "@octokit/action": "^6.1.0", + "ignore": "^5.3.1", + "isbinaryfile": "^5.0.2", + "pkg-types": "^1.1.1", + "query-registry": "^3.0.1", + "tinyglobby": "^0.2.9" + }, + "bin": { + "pkg-pr-new": "bin/cli.js" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -1987,6 +2477,55 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/query-registry": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/query-registry/-/query-registry-3.0.1.tgz", + "integrity": "sha512-M9RxRITi2mHMVPU5zysNjctUT8bAPx6ltEXo/ir9+qmiM47Y7f0Ir3+OxUO5OjYAWdicBQRew7RtHtqUXydqlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "query-string": "^9.0.0", + "quick-lru": "^7.0.0", + "url-join": "^5.0.0", + "validate-npm-package-name": "^5.0.1", + "zod": "^3.23.8", + "zod-package-json": "^1.0.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/query-string": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.3.0.tgz", + "integrity": "sha512-IQHOQ9aauHAApwAaUYifpEyLHv6fpVGVkMOnwPzcDScLjbLj8tLsILn6unSW79NafOw1llh8oK7Gd0VwmXBFmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.4.1", + "filter-obj": "^5.1.0", + "split-on-first": "^3.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/quick-lru": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-7.1.0.tgz", + "integrity": "sha512-Pzd/4IFnTb8E+I1P5rbLQoqpUHcXKg48qTYKi4EANg+sTPwGFEMOcYGiiZz6xuQcOMZP7MPsrdAPx+16Q8qahg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -2012,9 +2551,9 @@ } }, "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "license": "MIT", "peer": true, "engines": { @@ -2255,6 +2794,19 @@ "node": ">=0.10.0" } }, + "node_modules/split-on-first": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-3.0.0.tgz", + "integrity": "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -2264,10 +2816,20 @@ "node": ">= 0.8" } }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/swr": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.4.tgz", - "integrity": "sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz", + "integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==", "license": "MIT", "dependencies": { "dequal": "^2.0.3", @@ -2315,6 +2877,26 @@ "node": ">=0.6" } }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -2329,6 +2911,33 @@ "node": ">= 0.6" } }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "dev": true, + "license": "ISC" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2347,6 +2956,16 @@ "punycode": "^2.1.0" } }, + "node_modules/url-join": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", + "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/use-sync-external-store": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", @@ -2356,6 +2975,16 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2482,6 +3111,19 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zod-package-json": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zod-package-json/-/zod-package-json-1.2.0.tgz", + "integrity": "sha512-tamtgPM3MkP+obfO2dLr/G+nYoYkpJKmuHdYEy6IXRKfLybruoJ5NUj0lM0LxwOpC9PpoGLbll1ecoeyj43Wsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "zod": "^3.25.64" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/zod-to-json-schema": { "version": "3.24.6", "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", diff --git a/cloudflare/package.json b/cloudflare/package.json index b7710ba..e315cb1 100644 --- a/cloudflare/package.json +++ b/cloudflare/package.json @@ -19,20 +19,26 @@ "./package.json": "./package.json", ".": { "types": "./index.d.ts", - "import": "./lib/esm/index.js", - "require": "./lib/cjs/index.js", - "default": "./lib/esm/index.js" + "default": "./lib/index.js" + }, + "./experimental": { + "types": "./experimental.d.ts", + "default": "./lib/experimental.js" } }, "dependencies": { - "@cloudflare/playwright": "^0.0.11", - "@modelcontextprotocol/sdk": "^1.17.0", - "agents": "^0.0.109", + "@cloudflare/playwright": "https://pkg.pr.new/cloudflare/playwright/@cloudflare/playwright@71", + "@modelcontextprotocol/sdk": "^1.16.0", + "agents": "^0.0.113", "yaml": "^2.8.0", "zod-to-json-schema": "^3.24.6" }, "devDependencies": { "@cloudflare/workers-types": "^4.20250725.0", + "pkg-pr-new": "^0.0.59", "vite": "^7.0.6" + }, + "peerDependencies": { + "ai": "^4.3.19" } } diff --git a/cloudflare/src/experimental.ts b/cloudflare/src/experimental.ts new file mode 100644 index 0000000..5b24715 --- /dev/null +++ b/cloudflare/src/experimental.ts @@ -0,0 +1,123 @@ +import { Browser, BrowserContext, BrowserEndpoint, endpointURLString } from '@cloudflare/playwright'; +import type { ToolCapability } from '../../config'; +import { Context } from '../../src/context.js'; +import { FullConfig, resolveConfig } from '../../src/config.js'; +import type { BrowserContextFactory, ClientInfo } from '../../src/browserContextFactory.js'; +import type { Tool, ToolSet } from 'ai'; +import { ToolsProvider } from '../experimental.js'; +import { filteredTools } from '../../src/tools'; +import { Response } from '../../src/response'; +import { BrapiContextFactory } from '.'; + +class ToolsProviderImpl implements ToolsProvider { + private _context: Context; + private _tools: ToolSet; + + constructor(context: Context) { + this._context = context; + } + + tools(): ToolSet { + if (!this._tools) { + this._tools = Object.fromEntries( + this._context.tools.map(tool => [ + tool.schema.name, + { + parameters: tool.schema.inputSchema, + description: tool.schema.description, + execute: async (rawArguments: any) => { + const parsedArguments = tool.schema.inputSchema.parse(rawArguments || {}); + const context = this._context; + const response = new Response(context, tool.schema.name, parsedArguments); + context.setRunningTool(tool.schema.name); + try { + await tool.handle(context, parsedArguments, response); + await response.finish(); + context.sessionLog?.logResponse(response); + } catch (error: any) { + response.addError(String(error)); + } finally { + context.setRunningTool(undefined); + } + return response.serialize(); + }, + } satisfies Tool, + ]) + ); + } + return this._tools; + } + + async close() { + await this._context.dispose(); + } + + async [Symbol.asyncDispose]() { + await this.close(); + } +} + +function isBrowser(browser: any): browser is Browser { + return ( + typeof browser.newPage === 'function' && + typeof browser.newContext === 'function' && + // Fetcher may match the previous ones + typeof browser[Symbol.asyncDispose] === 'function' + ); +} + +function isBrowserContext(browserContext: any): browserContext is BrowserContext { + return ( + typeof browserContext.newPage === 'function' && + typeof browserContext.newContext === 'undefined' && + // Fetcher may match the previous ones + typeof browserContext[Symbol.asyncDispose] === 'function' + ); +} + +export async function createToolsProvider(endpoint: BrowserEndpoint | Browser | BrowserContext, options?: { capabilities?: ToolCapability[], clientInfo: ClientInfo }): Promise { + let config: FullConfig; + let browserContextFactory: BrowserContextFactory; + + if (isBrowser(endpoint)) { + config = await resolveConfig({}); + browserContextFactory = { + createContext: async () => { + const browserContext = await endpoint.newContext(); + return { browserContext, close: () => browserContext.close() }; + }, + } as unknown as BrowserContextFactory; + } else if (isBrowserContext(endpoint)) { + config = await resolveConfig({}); + const browserContext = endpoint; + browserContextFactory = { + createContext: async () => { + return { browserContext, close: () => {} }; + }, + } as unknown as BrowserContextFactory; + } else { + const cdpEndpoint = typeof endpoint === 'string' + ? endpoint + : endpoint instanceof URL + ? endpoint.toString() + : endpointURLString(endpoint); + config = await resolveConfig({ + browser: { + cdpEndpoint, + }, + }); + browserContextFactory = new BrapiContextFactory(config); + } + + const tools = filteredTools(config); + const clientInfo = options?.clientInfo ?? { name: 'unknown', version: 'unknown' }; + + const context = new Context({ + tools, + config, + browserContextFactory, + clientInfo, + sessionLog: undefined, + }); + return new ToolsProviderImpl(context); +} diff --git a/cloudflare/src/index.ts b/cloudflare/src/index.ts index 985afc2..278ed56 100644 --- a/cloudflare/src/index.ts +++ b/cloudflare/src/index.ts @@ -1,19 +1,74 @@ import { McpAgent } from 'agents/mcp'; import { env } from 'cloudflare:workers'; -import type { BrowserEndpoint } from '@cloudflare/playwright'; +import type { Browser, BrowserContext, BrowserEndpoint } from '@cloudflare/playwright'; -import { endpointURLString } from '@cloudflare/playwright'; -import { createConnection } from '../../src/index.js'; -import { ToolCapability } from '../../config.js'; +import playwright, { endpointURLString } from '@cloudflare/playwright'; +import type { Config, ToolCapability } from '../../config.js'; import type { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import type { BrowserContextFactory, ClientInfo } from '../../src/browserContextFactory.js'; +import { FullConfig, resolveConfig } from '../../src/config.js'; +import { BrowserServerBackend } from '../../src/browserServerBackend.js'; +import { packageJSON } from './package.js'; +import { createServer } from '../../src/mcp/server.js'; type Options = { - vision?: boolean; + clientInfo?: ClientInfo; capabilities?: ToolCapability[]; }; +export async function createConnection(userConfig: Config = {}, factory: BrowserContextFactory): Promise { + const config = await resolveConfig(userConfig); + return createServer('Playwright', packageJSON.version, new BrowserServerBackend(config, factory), false); +} + +export class BrapiContextFactory implements BrowserContextFactory { + readonly config: FullConfig; + protected _browserPromise: Promise | undefined; + + constructor(config: FullConfig) { + this.config = config; + } + + protected async _obtainBrowser(): Promise { + if (this._browserPromise) + return this._browserPromise; + this._browserPromise = this._doObtainBrowser(); + void this._browserPromise.then(browser => { + browser.on('disconnected', () => { + this._browserPromise = undefined; + }); + }).catch(() => { + this._browserPromise = undefined; + }); + return this._browserPromise; + } + + async createContext() { + const browser = await this._obtainBrowser(); + const browserContext = await this._doCreateContext(browser); + return { browserContext: browserContext as any, close: () => this._closeBrowserContext(browserContext, browser) }; + } + + protected async _doObtainBrowser(): Promise { + return playwright.chromium.connectOverCDP(this.config.browser.cdpEndpoint!); + } + + protected async _doCreateContext(browser: Browser): Promise { + return this.config.browser.isolated ? await browser.newContext() : browser.contexts()[0]; + } + + private async _closeBrowserContext(browserContext: BrowserContext, browser: Browser) { + if (browser.contexts().length === 1) + this._browserPromise = undefined; + await browserContext.close().catch(() => {}); + if (browser.contexts().length === 0) { + await browser.close().catch(() => {}); + } + } +} + export function createMcpAgent(endpoint: BrowserEndpoint, options?: Options): typeof McpAgent { const cdpEndpoint = typeof endpoint === 'string' ? endpoint @@ -21,16 +76,26 @@ export function createMcpAgent(endpoint: BrowserEndpoint, options?: Options): ty ? endpoint.toString() : endpointURLString(endpoint); - const connection = createConnection({ - capabilities: ['core', 'tabs', 'pdf', 'history', 'wait', 'files', 'testing'], + const contextFactory = new BrapiContextFactory({ + capabilities: ['core', 'core-tabs', 'core-install', 'vision', 'pdf'], browser: { + browserName: 'chromium', cdpEndpoint, + launchOptions: {}, + contextOptions: {}, + }, + network: { + allowedOrigins: undefined, + blockedOrigins: undefined, }, + server: {}, + saveTrace: false, ...options, }); + const serverPromise = createConnection({}, contextFactory); return class PlaywrightMcpAgent extends McpAgent { - server = connection.then(server => server.server as unknown as Server); + server = serverPromise as Promise; async init() { // do nothing diff --git a/cloudflare/vite.config.ts b/cloudflare/vite.config.ts index bf7622d..dafdd55 100644 --- a/cloudflare/vite.config.ts +++ b/cloudflare/vite.config.ts @@ -31,6 +31,9 @@ export default defineConfig({ 'util': 'node:util', 'zlib': 'node:zlib', + // imported from src/tools/utils.ts + 'playwright-core/lib/utils': path.resolve(__dirname, './node_modules/@cloudflare/playwright/lib/packages/playwright-core/src/utils/isomorphic/locatorGenerators.js'), + 'playwright-core': '@cloudflare/playwright', 'playwright': '@cloudflare/playwright/test', 'node:fs': '@cloudflare/playwright/fs', @@ -47,6 +50,7 @@ export default defineConfig({ name: '@cloudflare/playwright', entry: [ path.resolve(__dirname, './src/index.ts'), + path.resolve(__dirname, './src/experimental.ts'), ], }, // prevents __defProp, __defNormalProp, __publicField in compiled code @@ -55,20 +59,11 @@ export default defineConfig({ output: [ { format: 'es', - dir: 'lib/esm', - preserveModules: true, - preserveModulesRoot: 'src', - entryFileNames: '[name].js', - chunkFileNames: '[name].js', - }, - { - format: 'cjs', - dir: 'lib/cjs', + dir: 'lib', preserveModules: true, preserveModulesRoot: 'src', entryFileNames: '[name].js', chunkFileNames: '[name].js', - exports: 'named', }, ], external: [ diff --git a/config.d.ts b/config.d.ts index a935918..d63b061 100644 --- a/config.d.ts +++ b/config.d.ts @@ -16,18 +16,13 @@ import type * as playwright from 'playwright'; -export type ToolCapability = 'core' | 'tabs' | 'pdf' | 'history' | 'wait' | 'files' | 'install' | 'testing'; +export type ToolCapability = 'core' | 'core-tabs' | 'core-install' | 'vision' | 'pdf'; export type Config = { /** * The browser to use. */ browser?: { - /** - * Use browser agent (experimental). - */ - browserAgent?: string; - /** * The type of browser to use. */ @@ -85,19 +80,15 @@ export type Config = { /** * List of enabled tool capabilities. Possible values: * - 'core': Core browser automation features. - * - 'tabs': Tab management features. * - 'pdf': PDF generation and manipulation. - * - 'history': Browser history access. - * - 'wait': Wait and timing utilities. - * - 'files': File upload/download support. - * - 'install': Browser installation utilities. + * - 'vision': Coordinate-based interactions. */ capabilities?: ToolCapability[]; /** - * Run server that uses screenshots (Aria snapshots are used by default). + * Whether to save the Playwright session into the output directory. */ - vision?: boolean; + saveSession?: boolean; /** * Whether to save the Playwright trace of the session into the output directory. @@ -124,5 +115,5 @@ export type Config = { /** * Whether to send image responses to the client. Can be "allow", "omit", or "auto". Defaults to "auto", which sends images if the client can display them. */ - imageResponses?: 'allow' | 'omit' | 'auto'; + imageResponses?: 'allow' | 'omit'; }; diff --git a/eslint.config.mjs b/eslint.config.mjs index 239bf5e..c95971c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -192,6 +192,31 @@ const languageOptions = { } }; +const importOrderRules = { + "import/order": [ + 2, + { + groups: [ + "builtin", + "external", + "internal", + ["parent", "sibling"], + "index", + "type", + ], + }, + ], + "import/consistent-type-specifier-style": [2, "prefer-top-level"], +}; + +const noFloatingPromisesRules = { + "@typescript-eslint/no-floating-promises": "error", +}; + +const noBooleanCompareRules = { + "@typescript-eslint/no-unnecessary-boolean-literal-compare": 2, +}; + export default [ { ignores: ["**/*.js"], @@ -200,7 +225,12 @@ export default [ files: ["**/*.ts", "**/*.tsx"], plugins, languageOptions, - rules: baseRules, + rules: { + ...baseRules, + ...importOrderRules, + ...noFloatingPromisesRules, + ...noBooleanCompareRules, + }, }, { files: ['cloudflare/**/*'], @@ -208,4 +238,7 @@ export default [ 'notice/notice': 'off', }, }, + { + ignores: ["cloudflare/example/**/*"], + }, ]; diff --git a/extension/README.md b/extension/README.md new file mode 100644 index 0000000..6421798 --- /dev/null +++ b/extension/README.md @@ -0,0 +1,48 @@ +# Playwright MCP Chrome Extension + +## Introduction + +The Playwright MCP Chrome Extension allows you to connect to pages in your existing browser and leverage the state of your default user profile. This means the AI assistant can interact with websites where you're already logged in, using your existing cookies, sessions, and browser state, providing a seamless experience without requiring separate authentication or setup. + +## Prerequisites + +- Chrome/Edge/Chromium browser + +## Installation Steps + +### Download the Extension + +Download the latest Chrome extension from GitHub: +- **Download link**: https://github.com/microsoft/playwright-mcp/releases + +### Load Chrome Extension + +1. Open Chrome and navigate to `chrome://extensions/` +2. Enable "Developer mode" (toggle in the top right corner) +3. Click "Load unpacked" and select the extension directory + +### Configure Playwright MCP server + +Configure Playwright MCP server to connect to the browser using the extension by passing the `--extension` option when running the MCP server: + +```json +{ + "mcpServers": { + "playwright-extension": { + "command": "npx", + "args": [ + "@playwright/mcp@latest", + "--extension" + ] + } + } +} +``` + +## Usage + +### Browser Tab Selection + +When the LLM interacts with the browser for the first time, it will load a page where you can select which browser tab the LLM will connect to. This allows you to control which specific page the AI assistant will interact with during the session. + + diff --git a/extension/icons/icon-128.png b/extension/icons/icon-128.png new file mode 100644 index 0000000..c4bc8b0 Binary files /dev/null and b/extension/icons/icon-128.png differ diff --git a/extension/icons/icon-16.png b/extension/icons/icon-16.png new file mode 100644 index 0000000..0bab712 Binary files /dev/null and b/extension/icons/icon-16.png differ diff --git a/extension/icons/icon-32.png b/extension/icons/icon-32.png new file mode 100644 index 0000000..1f9a8cc Binary files /dev/null and b/extension/icons/icon-32.png differ diff --git a/extension/icons/icon-48.png b/extension/icons/icon-48.png new file mode 100644 index 0000000..ac23ef0 Binary files /dev/null and b/extension/icons/icon-48.png differ diff --git a/extension/manifest.json b/extension/manifest.json new file mode 100644 index 0000000..5b26fd3 --- /dev/null +++ b/extension/manifest.json @@ -0,0 +1,35 @@ +{ + "manifest_version": 3, + "name": "Playwright MCP Bridge", + "version": "0.0.35", + "description": "Share browser tabs with Playwright MCP server", + "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9nMS2b0WCohjVHPGb8D9qAdkbIngDqoAjTeSccHJijgcONejge+OJxOQOMLu7b0ovt1c9BiEJa5JcpM+EHFVGL1vluBxK71zmBy1m2f9vZF3HG0LSCp7YRkum9rAIEthDwbkxx6XTvpmAY5rjFa/NON6b9Hlbo+8peUSkoOK7HTwYnnI36asZ9eUTiveIf+DMPLojW2UX33vDWG2UKvMVDewzclb4+uLxAYshY7Mx8we/b44xu+Anb/EBLKjOPk9Yh541xJ5Ozc8EiP/5yxOp9c/lRiYUHaRW+4r0HKZyFt0eZ52ti2iM4Nfk7jRXR7an3JPsUIf5deC/1cVM/+1ZQIDAQAB", + "permissions": [ + "debugger", + "activeTab", + "tabs", + "storage" + ], + "host_permissions": [ + "" + ], + "background": { + "service_worker": "lib/background.js", + "type": "module" + }, + "action": { + "default_title": "Playwright MCP Bridge", + "default_icon": { + "16": "icons/icon-16.png", + "32": "icons/icon-32.png", + "48": "icons/icon-48.png", + "128": "icons/icon-128.png" + } + }, + "icons": { + "16": "icons/icon-16.png", + "32": "icons/icon-32.png", + "48": "icons/icon-48.png", + "128": "icons/icon-128.png" + } +} diff --git a/extension/package-lock.json b/extension/package-lock.json new file mode 100644 index 0000000..c1b89b4 --- /dev/null +++ b/extension/package-lock.json @@ -0,0 +1,1884 @@ +{ + "name": "@playwright/mcp-extension", + "version": "0.0.35", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@playwright/mcp-extension", + "version": "0.0.35", + "license": "Apache-2.0", + "devDependencies": { + "@types/chrome": "^0.0.315", + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "typescript": "^5.8.2", + "vite": "^5.0.0", + "vite-plugin-static-copy": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", + "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.28.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.1.tgz", + "integrity": "sha512-oENme6QxtLCqjChRUUo3S6X8hjCXnWmJWnedD7VbGML5GUtaOtAyx+fEEXnBXVf0CBZApMQU0Idwi0FmyxzQhw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.1.tgz", + "integrity": "sha512-OikvNT3qYTl9+4qQ9Bpn6+XHM+ogtFadRLuT2EXiFQMiNkXFLQfNVppi5o28wvYdHL2s3fM0D/MZJ8UkNFZWsw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.1.tgz", + "integrity": "sha512-EFYNNGij2WllnzljQDQnlFTXzSJw87cpAs4TVBAWLdkvic5Uh5tISrIL6NRcxoh/b2EFBG/TK8hgRrGx94zD4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.1.tgz", + "integrity": "sha512-ZaNH06O1KeTug9WI2+GRBE5Ujt9kZw4a1+OIwnBHal92I8PxSsl5KpsrPvthRynkhMck4XPdvY0z26Cym/b7oA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.1.tgz", + "integrity": "sha512-n4SLVebZP8uUlJ2r04+g2U/xFeiQlw09Me5UFqny8HGbARl503LNH5CqFTb5U5jNxTouhRjai6qPT0CR5c/Iig==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.1.tgz", + "integrity": "sha512-8vu9c02F16heTqpvo3yeiu7Vi1REDEC/yES/dIfq3tSXe6mLndiwvYr3AAvd1tMNUqE9yeGYa5w7PRbI5QUV+w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.1.tgz", + "integrity": "sha512-K4ncpWl7sQuyp6rWiGUvb6Q18ba8mzM0rjWJ5JgYKlIXAau1db7hZnR0ldJvqKWWJDxqzSLwGUhA4jp+KqgDtQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.1.tgz", + "integrity": "sha512-YykPnXsjUjmXE6j6k2QBBGAn1YsJUix7pYaPLK3RVE0bQL2jfdbfykPxfF8AgBlqtYbfEnYHmLXNa6QETjdOjQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.1.tgz", + "integrity": "sha512-kKvqBGbZ8i9pCGW3a1FH3HNIVg49dXXTsChGFsHGXQaVJPLA4f/O+XmTxfklhccxdF5FefUn2hvkoGJH0ScWOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.1.tgz", + "integrity": "sha512-zzX5nTw1N1plmqC9RGC9vZHFuiM7ZP7oSWQGqpbmfjK7p947D518cVK1/MQudsBdcD84t6k70WNczJOct6+hdg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.1.tgz", + "integrity": "sha512-O8CwgSBo6ewPpktFfSDgB6SJN9XDcPSvuwxfejiddbIC/hn9Tg6Ai0f0eYDf3XvB/+PIWzOQL+7+TZoB8p9Yuw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.1.tgz", + "integrity": "sha512-JnCfFVEKeq6G3h3z8e60kAp8Rd7QVnWCtPm7cxx+5OtP80g/3nmPtfdCXbVl063e3KsRnGSKDHUQMydmzc/wBA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.1.tgz", + "integrity": "sha512-dVxuDqS237eQXkbYzQQfdf/njgeNw6LZuVyEdUaWwRpKHhsLI+y4H/NJV8xJGU19vnOJCVwaBFgr936FHOnJsQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.1.tgz", + "integrity": "sha512-CvvgNl2hrZrTR9jXK1ye0Go0HQRT6ohQdDfWR47/KFKiLd5oN5T14jRdUVGF4tnsN8y9oSfMOqH6RuHh+ck8+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.1.tgz", + "integrity": "sha512-x7ANt2VOg2565oGHJ6rIuuAon+A8sfe1IeUx25IKqi49OjSr/K3awoNqr9gCwGEJo9OuXlOn+H2p1VJKx1psxA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.1.tgz", + "integrity": "sha512-9OADZYryz/7E8/qt0vnaHQgmia2Y0wrjSSn1V/uL+zw/i7NUhxbX4cHXdEQ7dnJgzYDS81d8+tf6nbIdRFZQoQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.1.tgz", + "integrity": "sha512-NuvSCbXEKY+NGWHyivzbjSVJi68Xfq1VnIvGmsuXs6TCtveeoDRKutI5vf2ntmNnVq64Q4zInet0UDQ+yMB6tA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.1.tgz", + "integrity": "sha512-mWz+6FSRb82xuUMMV1X3NGiaPFqbLN9aIueHleTZCc46cJvwTlvIh7reQLk4p97dv0nddyewBhwzryBHH7wtPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.1.tgz", + "integrity": "sha512-7Thzy9TMXDw9AU4f4vsLNBxh7/VOKuXi73VH3d/kHGr0tZ3x/ewgL9uC7ojUKmH1/zvmZe2tLapYcZllk3SO8Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.1.tgz", + "integrity": "sha512-7GVB4luhFmGUNXXJhH2jJwZCFB3pIOixv2E3s17GQHBFUOQaISlt7aGcQgqvCaDSxTZJUzlK/QJ1FN8S94MrzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/chrome": { + "version": "0.0.315", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.315.tgz", + "integrity": "sha512-Oy1dYWkr6BCmgwBtOngLByCHstQ3whltZg7/7lubgIZEYvKobDneqplgc6LKERNRBwckFviV4UU5AZZNUFrJ4A==", + "dev": true, + "dependencies": { + "@types/filesystem": "*", + "@types/har-format": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "node_modules/@types/filesystem": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz", + "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==", + "dev": true, + "dependencies": { + "@types/filewriter": "*" + } + }, + "node_modules/@types/filewriter": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz", + "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==", + "dev": true + }, + "node_modules/@types/har-format": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", + "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.3.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", + "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.192", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.192.tgz", + "integrity": "sha512-rP8Ez0w7UNw/9j5eSXCe10o1g/8B1P5SM90PCCMVkIRQn2R0LEHWz4Eh9RnxkniuDe1W0cTSOB3MLlkTGDcuCg==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rollup": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.1.tgz", + "integrity": "sha512-33xGNBsDJAkzt0PvninskHlWnTIPgDtTwhg0U38CUoNP/7H6wI2Cz6dUeoNPbjdTdsYTGuiFFASuUOWovH0SyQ==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.46.1", + "@rollup/rollup-android-arm64": "4.46.1", + "@rollup/rollup-darwin-arm64": "4.46.1", + "@rollup/rollup-darwin-x64": "4.46.1", + "@rollup/rollup-freebsd-arm64": "4.46.1", + "@rollup/rollup-freebsd-x64": "4.46.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.1", + "@rollup/rollup-linux-arm-musleabihf": "4.46.1", + "@rollup/rollup-linux-arm64-gnu": "4.46.1", + "@rollup/rollup-linux-arm64-musl": "4.46.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.1", + "@rollup/rollup-linux-ppc64-gnu": "4.46.1", + "@rollup/rollup-linux-riscv64-gnu": "4.46.1", + "@rollup/rollup-linux-riscv64-musl": "4.46.1", + "@rollup/rollup-linux-s390x-gnu": "4.46.1", + "@rollup/rollup-linux-x64-gnu": "4.46.1", + "@rollup/rollup-linux-x64-musl": "4.46.1", + "@rollup/rollup-win32-arm64-msvc": "4.46.1", + "@rollup/rollup-win32-ia32-msvc": "4.46.1", + "@rollup/rollup-win32-x64-msvc": "4.46.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-static-copy": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-3.1.1.tgz", + "integrity": "sha512-oR53SkL5cX4KT1t18E/xU50vJDo0N8oaHza4EMk0Fm+2/u6nQivxavOfrDk3udWj+dizRizB/QnBvJOOQrTTAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.6.0", + "fs-extra": "^11.3.0", + "p-map": "^7.0.3", + "picocolors": "^1.1.1", + "tinyglobby": "^0.2.14" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } +} diff --git a/extension/package.json b/extension/package.json new file mode 100644 index 0000000..2202f72 --- /dev/null +++ b/extension/package.json @@ -0,0 +1,36 @@ +{ + "name": "@playwright/mcp-extension", + "version": "0.0.35", + "description": "Playwright MCP Browser Extension", + "type": "module", + "private": true, + "repository": { + "type": "git", + "url": "git+https://github.com/microsoft/playwright-mcp.git" + }, + "homepage": "https://playwright.dev", + "engines": { + "node": ">=18" + }, + "author": { + "name": "Microsoft Corporation" + }, + "license": "Apache-2.0", + "scripts": { + "build": "tsc --project . && tsc --project tsconfig.ui.json && vite build", + "watch": "tsc --watch --project . & tsc --watch --project tsconfig.ui.json & vite build --watch", + "test": "playwright test", + "clean": "rm -rf dist" + }, + "devDependencies": { + "@types/chrome": "^0.0.315", + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "typescript": "^5.8.2", + "vite": "^5.0.0", + "vite-plugin-static-copy": "^3.1.1" + } +} diff --git a/extension/playwright.config.ts b/extension/playwright.config.ts new file mode 100644 index 0000000..4af9827 --- /dev/null +++ b/extension/playwright.config.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { defineConfig } from '@playwright/test'; + +import type { TestOptions } from '../tests/fixtures.js'; + +export default defineConfig({ + testDir: './tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'list', + projects: [ + { name: 'chromium', use: { mcpBrowser: 'chromium' } }, + ], +}); diff --git a/extension/src/background.ts b/extension/src/background.ts new file mode 100644 index 0000000..e71e0df --- /dev/null +++ b/extension/src/background.ts @@ -0,0 +1,222 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RelayConnection, debugLog } from './relayConnection.js'; + +type PageMessage = { + type: 'connectToMCPRelay'; + mcpRelayUrl: string; +} | { + type: 'getTabs'; +} | { + type: 'connectToTab'; + tabId?: number; + windowId?: number; + mcpRelayUrl: string; +} | { + type: 'getConnectionStatus'; +} | { + type: 'disconnect'; +}; + +class TabShareExtension { + private _activeConnection: RelayConnection | undefined; + private _connectedTabId: number | null = null; + private _pendingTabSelection = new Map(); + + constructor() { + chrome.tabs.onRemoved.addListener(this._onTabRemoved.bind(this)); + chrome.tabs.onUpdated.addListener(this._onTabUpdated.bind(this)); + chrome.tabs.onActivated.addListener(this._onTabActivated.bind(this)); + chrome.runtime.onMessage.addListener(this._onMessage.bind(this)); + chrome.action.onClicked.addListener(this._onActionClicked.bind(this)); + } + + // Promise-based message handling is not supported in Chrome: https://issues.chromium.org/issues/40753031 + private _onMessage(message: PageMessage, sender: chrome.runtime.MessageSender, sendResponse: (response: any) => void) { + switch (message.type) { + case 'connectToMCPRelay': + this._connectToRelay(sender.tab!.id!, message.mcpRelayUrl).then( + () => sendResponse({ success: true }), + (error: any) => sendResponse({ success: false, error: error.message })); + return true; + case 'getTabs': + this._getTabs().then( + tabs => sendResponse({ success: true, tabs, currentTabId: sender.tab?.id }), + (error: any) => sendResponse({ success: false, error: error.message })); + return true; + case 'connectToTab': + const tabId = message.tabId || sender.tab?.id!; + const windowId = message.windowId || sender.tab?.windowId!; + this._connectTab(sender.tab!.id!, tabId, windowId, message.mcpRelayUrl!).then( + () => sendResponse({ success: true }), + (error: any) => sendResponse({ success: false, error: error.message })); + return true; // Return true to indicate that the response will be sent asynchronously + case 'getConnectionStatus': + sendResponse({ + connectedTabId: this._connectedTabId + }); + return false; + case 'disconnect': + this._disconnect().then( + () => sendResponse({ success: true }), + (error: any) => sendResponse({ success: false, error: error.message })); + return true; + } + return false; + } + + private async _connectToRelay(selectorTabId: number, mcpRelayUrl: string): Promise { + try { + debugLog(`Connecting to relay at ${mcpRelayUrl}`); + const socket = new WebSocket(mcpRelayUrl); + await new Promise((resolve, reject) => { + socket.onopen = () => resolve(); + socket.onerror = () => reject(new Error('WebSocket error')); + setTimeout(() => reject(new Error('Connection timeout')), 5000); + }); + + const connection = new RelayConnection(socket); + connection.onclose = () => { + debugLog('Connection closed'); + this._pendingTabSelection.delete(selectorTabId); + // TODO: show error in the selector tab? + }; + this._pendingTabSelection.set(selectorTabId, { connection }); + debugLog(`Connected to MCP relay`); + } catch (error: any) { + const message = `Failed to connect to MCP relay: ${error.message}`; + debugLog(message); + throw new Error(message); + } + } + + private async _connectTab(selectorTabId: number, tabId: number, windowId: number, mcpRelayUrl: string): Promise { + try { + debugLog(`Connecting tab ${tabId} to relay at ${mcpRelayUrl}`); + try { + this._activeConnection?.close('Another connection is requested'); + } catch (error: any) { + debugLog(`Error closing active connection:`, error); + } + await this._setConnectedTabId(null); + + this._activeConnection = this._pendingTabSelection.get(selectorTabId)?.connection; + if (!this._activeConnection) + throw new Error('No active MCP relay connection'); + this._pendingTabSelection.delete(selectorTabId); + + this._activeConnection.setTabId(tabId); + this._activeConnection.onclose = () => { + debugLog('MCP connection closed'); + this._activeConnection = undefined; + void this._setConnectedTabId(null); + }; + + await Promise.all([ + this._setConnectedTabId(tabId), + chrome.tabs.update(tabId, { active: true }), + chrome.windows.update(windowId, { focused: true }), + ]); + debugLog(`Connected to MCP bridge`); + } catch (error: any) { + await this._setConnectedTabId(null); + debugLog(`Failed to connect tab ${tabId}:`, error.message); + throw error; + } + } + + private async _setConnectedTabId(tabId: number | null): Promise { + const oldTabId = this._connectedTabId; + this._connectedTabId = tabId; + if (oldTabId && oldTabId !== tabId) + await this._updateBadge(oldTabId, { text: '' }); + if (tabId) + await this._updateBadge(tabId, { text: '✓', color: '#4CAF50', title: 'Connected to MCP client' }); + } + + private async _updateBadge(tabId: number, { text, color, title }: { text: string; color?: string, title?: string }): Promise { + try { + await chrome.action.setBadgeText({ tabId, text }); + await chrome.action.setTitle({ tabId, title: title || '' }); + if (color) + await chrome.action.setBadgeBackgroundColor({ tabId, color }); + } catch (error: any) { + // Ignore errors as the tab may be closed already. + } + } + + private async _onTabRemoved(tabId: number): Promise { + const pendingConnection = this._pendingTabSelection.get(tabId)?.connection; + if (pendingConnection) { + this._pendingTabSelection.delete(tabId); + pendingConnection.close('Browser tab closed'); + return; + } + if (this._connectedTabId !== tabId) + return; + this._activeConnection?.close('Browser tab closed'); + this._activeConnection = undefined; + this._connectedTabId = null; + } + + private _onTabActivated(activeInfo: chrome.tabs.TabActiveInfo) { + for (const [tabId, pending] of this._pendingTabSelection) { + if (tabId === activeInfo.tabId) { + if (pending.timerId) { + clearTimeout(pending.timerId); + pending.timerId = undefined; + } + continue; + } + if (!pending.timerId) { + pending.timerId = setTimeout(() => { + const existed = this._pendingTabSelection.delete(tabId); + if (existed) { + pending.connection.close('Tab has been inactive for 5 seconds'); + chrome.tabs.sendMessage(tabId, { type: 'connectionTimeout' }); + } + }, 5000); + return; + } + } + } + + private _onTabUpdated(tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab) { + if (this._connectedTabId === tabId) + void this._setConnectedTabId(tabId); + } + + private async _getTabs(): Promise { + const tabs = await chrome.tabs.query({}); + return tabs.filter(tab => tab.url && !['chrome:', 'edge:', 'devtools:'].some(scheme => tab.url!.startsWith(scheme))); + } + + private async _onActionClicked(): Promise { + await chrome.tabs.create({ + url: chrome.runtime.getURL('status.html'), + active: true + }); + } + + private async _disconnect(): Promise { + this._activeConnection?.close('User disconnected'); + this._activeConnection = undefined; + await this._setConnectedTabId(null); + } +} + +new TabShareExtension(); diff --git a/extension/src/relayConnection.ts b/extension/src/relayConnection.ts new file mode 100644 index 0000000..b203af4 --- /dev/null +++ b/extension/src/relayConnection.ts @@ -0,0 +1,178 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function debugLog(...args: unknown[]): void { + const enabled = true; + if (enabled) { + // eslint-disable-next-line no-console + console.log('[Extension]', ...args); + } +} + +type ProtocolCommand = { + id: number; + method: string; + params?: any; +}; + +type ProtocolResponse = { + id?: number; + method?: string; + params?: any; + result?: any; + error?: string; +}; + +export class RelayConnection { + private _debuggee: chrome.debugger.Debuggee; + private _ws: WebSocket; + private _eventListener: (source: chrome.debugger.DebuggerSession, method: string, params: any) => void; + private _detachListener: (source: chrome.debugger.Debuggee, reason: string) => void; + private _tabPromise: Promise; + private _tabPromiseResolve!: () => void; + private _closed = false; + + onclose?: () => void; + + constructor(ws: WebSocket) { + this._debuggee = { }; + this._tabPromise = new Promise(resolve => this._tabPromiseResolve = resolve); + this._ws = ws; + this._ws.onmessage = this._onMessage.bind(this); + this._ws.onclose = () => this._onClose(); + // Store listeners for cleanup + this._eventListener = this._onDebuggerEvent.bind(this); + this._detachListener = this._onDebuggerDetach.bind(this); + chrome.debugger.onEvent.addListener(this._eventListener); + chrome.debugger.onDetach.addListener(this._detachListener); + } + + // Either setTabId or close is called after creating the connection. + setTabId(tabId: number): void { + this._debuggee = { tabId }; + this._tabPromiseResolve(); + } + + close(message: string): void { + this._ws.close(1000, message); + // ws.onclose is called asynchronously, so we call it here to avoid forwarding + // CDP events to the closed connection. + this._onClose(); + } + + private _onClose() { + if (this._closed) + return; + this._closed = true; + chrome.debugger.onEvent.removeListener(this._eventListener); + chrome.debugger.onDetach.removeListener(this._detachListener); + chrome.debugger.detach(this._debuggee).catch(() => {}); + this.onclose?.(); + } + + private _onDebuggerEvent(source: chrome.debugger.DebuggerSession, method: string, params: any): void { + if (source.tabId !== this._debuggee.tabId) + return; + debugLog('Forwarding CDP event:', method, params); + const sessionId = source.sessionId; + this._sendMessage({ + method: 'forwardCDPEvent', + params: { + sessionId, + method, + params, + }, + }); + } + + private _onDebuggerDetach(source: chrome.debugger.Debuggee, reason: string): void { + if (source.tabId !== this._debuggee.tabId) + return; + this.close(`Debugger detached: ${reason}`); + this._debuggee = { }; + } + + private _onMessage(event: MessageEvent): void { + this._onMessageAsync(event).catch(e => debugLog('Error handling message:', e)); + } + + private async _onMessageAsync(event: MessageEvent): Promise { + let message: ProtocolCommand; + try { + message = JSON.parse(event.data); + } catch (error: any) { + debugLog('Error parsing message:', error); + this._sendError(-32700, `Error parsing message: ${error.message}`); + return; + } + + debugLog('Received message:', message); + + const response: ProtocolResponse = { + id: message.id, + }; + try { + response.result = await this._handleCommand(message); + } catch (error: any) { + debugLog('Error handling command:', error); + response.error = error.message; + } + debugLog('Sending response:', response); + this._sendMessage(response); + } + + private async _handleCommand(message: ProtocolCommand): Promise { + if (message.method === 'attachToTab') { + await this._tabPromise; + debugLog('Attaching debugger to tab:', this._debuggee); + await chrome.debugger.attach(this._debuggee, '1.3'); + const result: any = await chrome.debugger.sendCommand(this._debuggee, 'Target.getTargetInfo'); + return { + targetInfo: result?.targetInfo, + }; + } + if (!this._debuggee.tabId) + throw new Error('No tab is connected. Please go to the Playwright MCP extension and select the tab you want to connect to.'); + if (message.method === 'forwardCDPCommand') { + const { sessionId, method, params } = message.params; + debugLog('CDP command:', method, params); + const debuggerSession: chrome.debugger.DebuggerSession = { + ...this._debuggee, + sessionId, + }; + // Forward CDP command to chrome.debugger + return await chrome.debugger.sendCommand( + debuggerSession, + method, + params + ); + } + } + + private _sendError(code: number, message: string): void { + this._sendMessage({ + error: { + code, + message, + }, + }); + } + + private _sendMessage(message: any): void { + if (this._ws.readyState === WebSocket.OPEN) + this._ws.send(JSON.stringify(message)); + } +} diff --git a/extension/src/ui/connect.css b/extension/src/ui/connect.css new file mode 100644 index 0000000..60945d4 --- /dev/null +++ b/extension/src/ui/connect.css @@ -0,0 +1,206 @@ +/* + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +body { + margin: 0; + padding: 0; +} + +/* Base styles */ +.app-container { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif; + background-color: #ffffff; + color: #1f2328; + margin: 0; + padding: 16px; + min-height: 100vh; + font-size: 14px; +} + +.content-wrapper { + max-width: 600px; + margin: 0 auto; +} + +/* Status Banner */ +.status-container { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; + padding-right: 12px; +} + +.status-banner { + padding: 12px; + font-size: 14px; + font-weight: 500; + display: flex; + align-items: center; + gap: 8px; + flex: 1; +} + +.status-banner.connected { + color: #1f2328; +} + +.status-banner.connected::before { + content: "\2705"; + margin-right: 8px; +} + +.status-banner.error { + color: #1f2328; +} + +.status-banner.error::before { + content: "\274C"; + margin-right: 8px; +} + +/* Buttons */ +.button-container { + margin-bottom: 16px; + display: flex; + justify-content: flex-end; + padding-right: 12px; +} + +.button { + padding: 8px 16px; + border-radius: 6px; + border: none; + font-size: 14px; + font-weight: 500; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + text-decoration: none; + margin-right: 8px; + min-width: 90px; +} + +.button.primary { + background-color: #f8f9fa; + color: #3c4043; + border: 1px solid #dadce0; +} + +.button.primary:hover { + background-color: #f1f3f4; + border-color: #dadce0; + box-shadow: 0 1px 2px 0 rgba(60,64,67,.1); +} + +.button.default { + background-color: #f6f8fa; + color: #24292f; +} + +.button.default:hover { + background-color: #f3f4f6; +} + +.button.reject { + background-color: #da3633; + color: #ffffff; + border: 1px solid #da3633; +} + +.button.reject:hover { + background-color: #c73836; + border-color: #c73836; +} + +/* Tab selection */ +.tab-section-title { + padding-left: 12px; + font-size: 12px; + font-weight: 400; + margin-bottom: 12px; + color: #656d76; +} + +.tab-item { + display: flex; + align-items: center; + padding: 12px; + margin-bottom: 8px; + background-color: #ffffff; + cursor: pointer; + border-radius: 6px; + transition: background-color 0.2s ease; +} + +.tab-item:hover { + background-color: #f8f9fa; +} + +.tab-item.selected { + background-color: #f6f8fa; +} + +.tab-item.disabled { + cursor: not-allowed; + opacity: 0.5; +} + +.tab-radio { + margin-right: 12px; + flex-shrink: 0; +} + +.tab-favicon { + width: 16px; + height: 16px; + margin-right: 8px; + flex-shrink: 0; +} + +.tab-content { + flex: 1; + min-width: 0; +} + +.tab-title { + font-weight: 500; + color: #1f2328; + margin-bottom: 2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.tab-url { + font-size: 12px; + color: #656d76; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Link-style button */ +.link-button { + background: none; + border: none; + color: #0066cc; + text-decoration: underline; + cursor: pointer; + padding: 0; + font: inherit; +} \ No newline at end of file diff --git a/extension/src/ui/connect.html b/extension/src/ui/connect.html new file mode 100644 index 0000000..3f20e4b --- /dev/null +++ b/extension/src/ui/connect.html @@ -0,0 +1,29 @@ + + + + + Playwright MCP extension + + + + + + + + + + \ No newline at end of file diff --git a/extension/src/ui/connect.tsx b/extension/src/ui/connect.tsx new file mode 100644 index 0000000..0a80a61 --- /dev/null +++ b/extension/src/ui/connect.tsx @@ -0,0 +1,198 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useState, useEffect, useCallback } from 'react'; +import { createRoot } from 'react-dom/client'; +import { Button, TabItem } from './tabItem.js'; +import type { TabInfo } from './tabItem.js'; + +type Status = + | { type: 'connecting'; message: string } + | { type: 'connected'; message: string } + | { type: 'error'; message: string }; + +const ConnectApp: React.FC = () => { + const [tabs, setTabs] = useState([]); + const [status, setStatus] = useState(null); + const [showButtons, setShowButtons] = useState(true); + const [showTabList, setShowTabList] = useState(true); + const [clientInfo, setClientInfo] = useState('unknown'); + const [mcpRelayUrl, setMcpRelayUrl] = useState(''); + const [newTab, setNewTab] = useState(false); + + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const relayUrl = params.get('mcpRelayUrl'); + + if (!relayUrl) { + setShowButtons(false); + setStatus({ type: 'error', message: 'Missing mcpRelayUrl parameter in URL.' }); + return; + } + + setMcpRelayUrl(relayUrl); + + try { + const client = JSON.parse(params.get('client') || '{}'); + const info = `${client.name}/${client.version}`; + setClientInfo(info); + setStatus({ + type: 'connecting', + message: `🎭 Playwright MCP started from "${info}" is trying to connect. Do you want to continue?` + }); + } catch (e) { + setStatus({ type: 'error', message: 'Failed to parse client version.' }); + return; + } + + void connectToMCPRelay(relayUrl); + + // If this is a browser_navigate command, hide the tab list and show simple allow/reject + if (params.get('newTab') === 'true') { + setNewTab(true); + setShowTabList(false); + } else { + void loadTabs(); + } + }, []); + + const handleReject = useCallback((message: string) => { + setShowButtons(false); + setShowTabList(false); + setStatus({ type: 'error', message }); + }, []); + + const connectToMCPRelay = useCallback(async (mcpRelayUrl: string) => { + + const response = await chrome.runtime.sendMessage({ type: 'connectToMCPRelay', mcpRelayUrl }); + if (!response.success) + handleReject(response.error); + }, [handleReject]); + + const loadTabs = useCallback(async () => { + const response = await chrome.runtime.sendMessage({ type: 'getTabs' }); + if (response.success) + setTabs(response.tabs); + else + setStatus({ type: 'error', message: 'Failed to load tabs: ' + response.error }); + }, []); + + const handleConnectToTab = useCallback(async (tab?: TabInfo) => { + setShowButtons(false); + setShowTabList(false); + + try { + const response = await chrome.runtime.sendMessage({ + type: 'connectToTab', + mcpRelayUrl, + tabId: tab?.id, + windowId: tab?.windowId, + }); + + if (response?.success) { + setStatus({ type: 'connected', message: `MCP client "${clientInfo}" connected.` }); + } else { + setStatus({ + type: 'error', + message: response?.error || `MCP client "${clientInfo}" failed to connect.` + }); + } + } catch (e) { + setStatus({ + type: 'error', + message: `MCP client "${clientInfo}" failed to connect: ${e}` + }); + } + }, [clientInfo, mcpRelayUrl]); + + useEffect(() => { + const listener = (message: any) => { + if (message.type === 'connectionTimeout') + handleReject('Connection timed out.'); + }; + chrome.runtime.onMessage.addListener(listener); + return () => { + chrome.runtime.onMessage.removeListener(listener); + }; + }, [handleReject]); + + return ( + + + {status && ( + + + {showButtons && ( + + {newTab ? ( + <> + handleConnectToTab()}> + Allow + + handleReject('Connection rejected. This tab can be closed.')}> + Reject + + > + ) : ( + handleReject('Connection rejected. This tab can be closed.')}> + Reject + + )} + + )} + + )} + + {showTabList && ( + + + Select page to expose to MCP server: + + + {tabs.map(tab => ( + handleConnectToTab(tab)}> + Connect + + } + /> + ))} + + + )} + + + ); +}; + + +const StatusBanner: React.FC<{ status: Status }> = ({ status }) => { + return ( + + {status.message} + + ); +}; + +// Initialize the React app +const container = document.getElementById('root'); +if (container) { + const root = createRoot(container); + root.render(); +} diff --git a/extension/src/ui/status.html b/extension/src/ui/status.html new file mode 100644 index 0000000..ccc1f04 --- /dev/null +++ b/extension/src/ui/status.html @@ -0,0 +1,13 @@ + + + + + + Playwright MCP Bridge Status + + + + + + + \ No newline at end of file diff --git a/extension/src/ui/status.tsx b/extension/src/ui/status.tsx new file mode 100644 index 0000000..9742bc1 --- /dev/null +++ b/extension/src/ui/status.tsx @@ -0,0 +1,110 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useState, useEffect } from 'react'; +import { createRoot } from 'react-dom/client'; +import { Button, TabItem } from './tabItem.js'; + +import type { TabInfo } from './tabItem.js'; + +interface ConnectionStatus { + isConnected: boolean; + connectedTabId: number | null; + connectedTab?: TabInfo; +} + +const StatusApp: React.FC = () => { + const [status, setStatus] = useState({ + isConnected: false, + connectedTabId: null + }); + + useEffect(() => { + void loadStatus(); + }, []); + + const loadStatus = async () => { + // Get current connection status from background script + const { connectedTabId } = await chrome.runtime.sendMessage({ type: 'getConnectionStatus' }); + if (connectedTabId) { + const tab = await chrome.tabs.get(connectedTabId); + setStatus({ + isConnected: true, + connectedTabId, + connectedTab: { + id: tab.id!, + windowId: tab.windowId!, + title: tab.title!, + url: tab.url!, + favIconUrl: tab.favIconUrl + } + }); + } else { + setStatus({ + isConnected: false, + connectedTabId: null + }); + } + }; + + const openConnectedTab = async () => { + if (!status.connectedTabId) + return; + await chrome.tabs.update(status.connectedTabId, { active: true }); + window.close(); + }; + + const disconnect = async () => { + await chrome.runtime.sendMessage({ type: 'disconnect' }); + window.close(); + }; + + return ( + + + {status.isConnected && status.connectedTab ? ( + + + Page with connected MCP client: + + + + Disconnect + + } + onClick={openConnectedTab} + /> + + + ) : ( + + No MCP clients are currently connected. + + )} + + + ); +}; + +// Initialize the React app +const container = document.getElementById('root'); +if (container) { + const root = createRoot(container); + root.render(); +} diff --git a/extension/src/ui/tabItem.tsx b/extension/src/ui/tabItem.tsx new file mode 100644 index 0000000..1483742 --- /dev/null +++ b/extension/src/ui/tabItem.tsx @@ -0,0 +1,67 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; + +export interface TabInfo { + id: number; + windowId: number; + title: string; + url: string; + favIconUrl?: string; +} + +export const Button: React.FC<{ variant: 'primary' | 'default' | 'reject'; onClick: () => void; children: React.ReactNode }> = ({ + variant, + onClick, + children +}) => { + return ( + + {children} + + ); +}; + + +export interface TabItemProps { + tab: TabInfo; + onClick?: () => void; + button?: React.ReactNode; +} + +export const TabItem: React.FC = ({ + tab, + onClick, + button +}) => { + return ( + + '} + alt='' + className='tab-favicon' + /> + + + {tab.title || 'Untitled'} + + {tab.url} + + {button} + + ); +}; diff --git a/extension/src/ui/tsconfig.json b/extension/src/ui/tsconfig.json new file mode 100644 index 0000000..77839b6 --- /dev/null +++ b/extension/src/ui/tsconfig.json @@ -0,0 +1,4 @@ +// Help VSCode to find right tsconfig file. +{ + "extends": "../../tsconfig.ui.json" +} diff --git a/extension/tests/extension.spec.ts b/extension/tests/extension.spec.ts new file mode 100644 index 0000000..cff28d6 --- /dev/null +++ b/extension/tests/extension.spec.ts @@ -0,0 +1,244 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { chromium } from 'playwright'; +import { test as base, expect } from '../../tests/fixtures.js'; + +import type { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import type { BrowserContext } from 'playwright'; +import type { StartClient } from '../../tests/fixtures.js'; + +type BrowserWithExtension = { + userDataDir: string; + launch: (mode?: 'disable-extension') => Promise; +}; + +type TestFixtures = { + browserWithExtension: BrowserWithExtension, + pathToExtension: string, + useShortConnectionTimeout: (timeoutMs: number) => void +}; + +const test = base.extend({ + pathToExtension: async ({}, use) => { + await use(fileURLToPath(new URL('../dist', import.meta.url))); + }, + + browserWithExtension: async ({ mcpBrowser, pathToExtension }, use, testInfo) => { + // The flags no longer work in Chrome since + // https://chromium.googlesource.com/chromium/src/+/290ed8046692651ce76088914750cb659b65fb17%5E%21/chrome/browser/extensions/extension_service.cc?pli=1# + test.skip('chromium' !== mcpBrowser, '--load-extension is not supported for official builds of Chromium'); + + let browserContext: BrowserContext | undefined; + const userDataDir = testInfo.outputPath('extension-user-data-dir'); + await use({ + userDataDir, + launch: async (mode?: 'disable-extension') => { + browserContext = await chromium.launchPersistentContext(userDataDir, { + channel: mcpBrowser, + // Opening the browser singleton only works in headed. + headless: false, + // Automation disables singleton browser process behavior, which is necessary for the extension. + ignoreDefaultArgs: ['--enable-automation'], + args: mode === 'disable-extension' ? [] : [ + `--disable-extensions-except=${pathToExtension}`, + `--load-extension=${pathToExtension}`, + ], + }); + + // for manifest v3: + let [serviceWorker] = browserContext.serviceWorkers(); + if (!serviceWorker) + serviceWorker = await browserContext.waitForEvent('serviceworker'); + + return browserContext; + } + }); + await browserContext?.close(); + }, + + useShortConnectionTimeout: async ({}, use) => { + await use((timeoutMs: number) => { + process.env.PWMCP_TEST_CONNECTION_TIMEOUT = timeoutMs.toString(); + }); + process.env.PWMCP_TEST_CONNECTION_TIMEOUT = undefined; + }, + +}); + +async function startAndCallConnectTool(browserWithExtension: BrowserWithExtension, startClient: StartClient): Promise { + const { client } = await startClient({ + args: [`--connect-tool`], + config: { + browser: { + userDataDir: browserWithExtension.userDataDir, + } + }, + }); + + expect(await client.callTool({ + name: 'browser_connect', + arguments: { + name: 'extension' + } + })).toHaveResponse({ + result: 'Successfully changed connection method.', + }); + + return client; +} + +async function startWithExtensionFlag(browserWithExtension: BrowserWithExtension, startClient: StartClient): Promise { + const { client } = await startClient({ + args: [`--extension`], + config: { + browser: { + userDataDir: browserWithExtension.userDataDir, + } + }, + }); + return client; +} + +const testWithOldExtensionVersion = test.extend({ + pathToExtension: async ({}, use, testInfo) => { + const extensionDir = testInfo.outputPath('extension'); + const oldPath = fileURLToPath(new URL('../dist', import.meta.url)); + + await fs.promises.cp(oldPath, extensionDir, { recursive: true }); + const manifestPath = path.join(extensionDir, 'manifest.json'); + const manifest = JSON.parse(await fs.promises.readFile(manifestPath, 'utf8')); + manifest.version = '0.0.1'; + await fs.promises.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n'); + + await use(extensionDir); + }, +}); + +for (const [mode, startClientMethod] of [ + ['connect-tool', startAndCallConnectTool], + ['extension-flag', startWithExtensionFlag], +] as const) { + + test(`navigate with extension (${mode})`, async ({ browserWithExtension, startClient, server }) => { + const browserContext = await browserWithExtension.launch(); + + const client = await startClientMethod(browserWithExtension, startClient); + + const confirmationPagePromise = browserContext.waitForEvent('page', page => { + return page.url().startsWith('chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/connect.html'); + }); + + const navigateResponse = client.callTool({ + name: 'browser_navigate', + arguments: { url: server.HELLO_WORLD }, + }); + + const selectorPage = await confirmationPagePromise; + // For browser_navigate command, the UI shows Allow/Reject buttons instead of tab selector + await selectorPage.getByRole('button', { name: 'Allow' }).click(); + + expect(await navigateResponse).toHaveResponse({ + pageState: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`), + }); + }); + + test(`snapshot of an existing page (${mode})`, async ({ browserWithExtension, startClient, server }) => { + const browserContext = await browserWithExtension.launch(); + + const page = await browserContext.newPage(); + await page.goto(server.HELLO_WORLD); + + // Another empty page. + await browserContext.newPage(); + expect(browserContext.pages()).toHaveLength(3); + + const client = await startClientMethod(browserWithExtension, startClient); + expect(browserContext.pages()).toHaveLength(3); + + const confirmationPagePromise = browserContext.waitForEvent('page', page => { + return page.url().startsWith('chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/connect.html'); + }); + + const navigateResponse = client.callTool({ + name: 'browser_snapshot', + arguments: { }, + }); + + const selectorPage = await confirmationPagePromise; + expect(browserContext.pages()).toHaveLength(4); + + await selectorPage.locator('.tab-item', { hasText: 'Title' }).getByRole('button', { name: 'Connect' }).click(); + + expect(await navigateResponse).toHaveResponse({ + pageState: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`), + }); + + expect(browserContext.pages()).toHaveLength(4); + }); + + test(`extension not installed timeout (${mode})`, async ({ browserWithExtension, startClient, server, useShortConnectionTimeout }) => { + useShortConnectionTimeout(100); + + const browserContext = await browserWithExtension.launch(); + + const client = await startClientMethod(browserWithExtension, startClient); + + const confirmationPagePromise = browserContext.waitForEvent('page', page => { + return page.url().startsWith('chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/connect.html'); + }); + + expect(await client.callTool({ + name: 'browser_navigate', + arguments: { url: server.HELLO_WORLD }, + })).toHaveResponse({ + result: expect.stringContaining('Extension connection timeout. Make sure the "Playwright MCP Bridge" extension is installed.'), + isError: true, + }); + + await confirmationPagePromise; + }); + + testWithOldExtensionVersion(`works with old extension version (${mode})`, async ({ browserWithExtension, startClient, server, useShortConnectionTimeout }) => { + useShortConnectionTimeout(500); + + // Prelaunch the browser, so that it is properly closed after the test. + const browserContext = await browserWithExtension.launch(); + + const client = await startClientMethod(browserWithExtension, startClient); + + const confirmationPagePromise = browserContext.waitForEvent('page', page => { + return page.url().startsWith('chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/connect.html'); + }); + + const navigateResponse = client.callTool({ + name: 'browser_navigate', + arguments: { url: server.HELLO_WORLD }, + }); + + const selectorPage = await confirmationPagePromise; + // For browser_navigate command, the UI shows Allow/Reject buttons instead of tab selector + await selectorPage.getByRole('button', { name: 'Allow' }).click(); + + expect(await navigateResponse).toHaveResponse({ + pageState: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`), + }); + }); + +} diff --git a/extension/tsconfig.json b/extension/tsconfig.json new file mode 100644 index 0000000..70fb340 --- /dev/null +++ b/extension/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ESNext", + "esModuleInterop": true, + "moduleResolution": "node", + "strict": true, + "module": "ESNext", + "rootDir": "src", + "outDir": "./dist/lib", + "resolveJsonModule": true, + "types": ["chrome"], + "jsx": "react-jsx", + "jsxImportSource": "react" + }, + "include": [ + "src", + ], + "exclude": [ + "src/ui", + ] +} diff --git a/extension/tsconfig.ui.json b/extension/tsconfig.ui.json new file mode 100644 index 0000000..f62dd8a --- /dev/null +++ b/extension/tsconfig.ui.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "esModuleInterop": true, + "moduleResolution": "node", + "strict": true, + "module": "ESNext", + "rootDir": "src", + "outDir": "./lib", + "resolveJsonModule": true, + "types": ["chrome"], + "jsx": "react-jsx", + "jsxImportSource": "react", + "noEmit": true, + }, + "include": [ + "src/ui", + ], +} diff --git a/extension/vite.config.ts b/extension/vite.config.ts new file mode 100644 index 0000000..89ec56c --- /dev/null +++ b/extension/vite.config.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { resolve } from 'path'; +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { viteStaticCopy } from 'vite-plugin-static-copy'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + react(), + viteStaticCopy({ + targets: [ + { + src: '../../icons/*', + dest: 'icons' + }, + { + src: '../../manifest.json', + dest: '.' + } + ] + }) + ], + root: resolve(__dirname, 'src/ui'), + build: { + outDir: resolve(__dirname, 'dist/'), + emptyOutDir: false, + minify: false, + rollupOptions: { + input: ['src/ui/connect.html', 'src/ui/status.html'], + output: { + manualChunks: undefined, + entryFileNames: 'lib/ui/[name].js', + chunkFileNames: 'lib/ui/[name].js', + assetFileNames: 'lib/ui/[name].[ext]' + } + } + } +}); diff --git a/index.d.ts b/index.d.ts index 9ea8bcc..f7a1dde 100644 --- a/index.d.ts +++ b/index.d.ts @@ -19,10 +19,5 @@ import type { Server } from '@modelcontextprotocol/sdk/server/index.js'; import type { Config } from './config.js'; import type { BrowserContext } from 'playwright'; -export type Connection = { - server: Server; - close(): Promise; -}; - -export declare function createConnection(config?: Config, contextGetter?: () => Promise): Promise; +export declare function createConnection(config?: Config, contextGetter?: () => Promise): Promise; export {}; diff --git a/package-lock.json b/package-lock.json index fff4d4c..b60d7e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,46 +1,452 @@ { "name": "@playwright/mcp", - "version": "0.0.30", + "version": "0.0.35", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@playwright/mcp", - "version": "0.0.30", + "version": "0.0.35", "license": "Apache-2.0", "dependencies": { - "@modelcontextprotocol/sdk": "^1.11.0", + "@modelcontextprotocol/sdk": "^1.16.0", "commander": "^13.1.0", "debug": "^4.4.1", + "dotenv": "^17.2.0", "mime": "^4.0.7", - "playwright": "1.54.1", + "playwright": "1.55.0-alpha-2025-08-12", + "playwright-core": "1.55.0-alpha-2025-08-12", "ws": "^8.18.1", + "zod": "^3.24.1", "zod-to-json-schema": "^3.24.4" }, "bin": { "mcp-server-playwright": "cli.js" }, "devDependencies": { + "@anthropic-ai/sdk": "^0.57.0", "@eslint/eslintrc": "^3.2.0", "@eslint/js": "^9.19.0", - "@playwright/test": "1.54.1", + "@playwright/test": "1.55.0-alpha-2025-08-12", "@stylistic/eslint-plugin": "^3.0.1", - "@types/chrome": "^0.0.315", "@types/debug": "^4.1.12", "@types/node": "^22.13.10", "@types/ws": "^8.18.1", "@typescript-eslint/eslint-plugin": "^8.26.1", "@typescript-eslint/parser": "^8.26.1", "@typescript-eslint/utils": "^8.26.1", + "esbuild": "^0.20.1", "eslint": "^9.19.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-notice": "^1.0.0", + "openai": "^5.10.2", "typescript": "^5.8.2" }, "engines": { "node": ">=18" } }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.57.0.tgz", + "integrity": "sha512-z5LMy0MWu0+w2hflUgj4RlJr1R+0BxKXL7ldXTO8FasU8fu599STghO+QKwId2dAD0d464aHtU+ChWuRHw4FNw==", + "dev": true, + "license": "MIT", + "bin": { + "anthropic-ai-sdk": "bin/cli" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", @@ -71,9 +477,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -86,9 +492,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.1.0.tgz", - "integrity": "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -96,9 +502,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -109,9 +515,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", - "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", "dependencies": { @@ -133,13 +539,16 @@ } }, "node_modules/@eslint/js": { - "version": "9.22.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.22.0.tgz", - "integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { @@ -153,13 +562,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", - "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.12.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { @@ -233,15 +642,17 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.0.tgz", - "integrity": "sha512-k/1pb70eD638anoi0e8wUGAlbMJXyvdV4p62Ko+EZ7eBe1xMx8Uhak1R5DgfoofsK5IBBnRwsYGTaLZl+6/+RQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.16.0.tgz", + "integrity": "sha512-8ofX7gkZcLj9H9rSd50mCgm3SSF8C7XoclxJuLoV0Cz3rEQ1tv9MZRYYvJtm9n1BiEQQMzSmE/w2AEkNacLYfg==", "license": "MIT", "dependencies": { + "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", - "cross-spawn": "^7.0.3", + "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", @@ -292,13 +703,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.54.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.1.tgz", - "integrity": "sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==", + "version": "1.55.0-alpha-2025-08-12", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0-alpha-2025-08-12.tgz", + "integrity": "sha512-lyq9MDSd4UcOWx5292AYLBfbYYCstg8iLb+lk6LdM69ps6bwmPloZO3Ol3JO3FQQ63qAuW9VD0w+ZYKL0lRmQA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.54.1" + "playwright": "1.55.0-alpha-2025-08-12" }, "bin": { "playwright": "cli.js" @@ -360,17 +771,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@types/chrome": { - "version": "0.0.315", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.315.tgz", - "integrity": "sha512-Oy1dYWkr6BCmgwBtOngLByCHstQ3whltZg7/7lubgIZEYvKobDneqplgc6LKERNRBwckFviV4UU5AZZNUFrJ4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/filesystem": "*", - "@types/har-format": "*" - } - }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -382,33 +782,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/filesystem": { - "version": "0.0.36", - "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz", - "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/filewriter": "*" - } - }, - "node_modules/@types/filewriter": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz", - "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/har-format": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", - "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, @@ -592,9 +968,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -686,9 +1062,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -712,7 +1088,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -922,9 +1297,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1267,6 +1642,18 @@ "node": ">=0.10.0" } }, + "node_modules/dotenv": { + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.0.tgz", + "integrity": "sha512-Q4sgBT60gzd0BB0lSyYD3xM4YxrXA9y4uBDof1JNYGzOXrQdQ6yX+7XIAqoFOGQFOTK1D3Hts5OllpxMDZFONQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1439,6 +1826,45 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1459,20 +1885,20 @@ } }, "node_modules/eslint": { - "version": "9.22.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.22.0.tgz", - "integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.2", - "@eslint/config-helpers": "^0.1.0", - "@eslint/core": "^0.12.0", - "@eslint/eslintrc": "^3.3.0", - "@eslint/js": "9.22.0", - "@eslint/plugin-kit": "^0.2.7", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.31.0", + "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -1483,9 +1909,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -1639,9 +2065,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -1669,9 +2095,9 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1682,15 +2108,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1700,9 +2126,9 @@ } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1849,7 +2275,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -1886,7 +2311,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { @@ -2807,7 +3231,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { @@ -3152,6 +3575,28 @@ "wrappy": "1" } }, + "node_modules/openai": { + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/openai/-/openai-5.10.2.tgz", + "integrity": "sha512-n+vi74LzHtvlKcDPn9aApgELGiu5CwhaLG40zxLTlFQdoSJCLACORIPC2uVQ3JEYAbqapM+XyRKFy2Thej7bIw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3300,12 +3745,12 @@ } }, "node_modules/playwright": { - "version": "1.54.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.1.tgz", - "integrity": "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==", + "version": "1.55.0-alpha-2025-08-12", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0-alpha-2025-08-12.tgz", + "integrity": "sha512-daZPM5gX0VTG6ae3/qOpEKc9NxoavkM2lfL0UIzTG0k+yK8ZeSPYo63iewZhVANsWRm0BT+XQ1NniAUOwWQ+xA==", "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.54.1" + "playwright-core": "1.55.0-alpha-2025-08-12" }, "bin": { "playwright": "cli.js" @@ -3318,9 +3763,9 @@ } }, "node_modules/playwright-core": { - "version": "1.54.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz", - "integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==", + "version": "1.55.0-alpha-2025-08-12", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0-alpha-2025-08-12.tgz", + "integrity": "sha512-4uxOd9xmeF6gqdsORzzlXd7p795vcACOiAGVHHEiTuFXsD83LYH+0C/SYLWB0Z+fAq4LdKGsy0qEfTm0JkY8Ig==", "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -3366,7 +3811,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4160,7 +4604,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" diff --git a/package.json b/package.json index 5a6dfc8..bf1a012 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/mcp", - "version": "0.0.30", + "version": "0.0.35", "description": "Playwright Tools for MCP", "type": "module", "repository": { @@ -17,7 +17,9 @@ "license": "Apache-2.0", "scripts": { "build": "tsc", - "lint": "npm run update-readme && eslint . && tsc --noEmit", + "lint": "npm run update-readme && npm run check-deps && eslint . && tsc --noEmit", + "lint-fix": "eslint . --fix", + "check-deps": "node utils/check-deps.js", "update-readme": "node utils/update-readme.js", "watch": "tsc --watch", "test": "playwright test", @@ -36,29 +38,34 @@ } }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.11.0", + "@modelcontextprotocol/sdk": "^1.16.0", "commander": "^13.1.0", "debug": "^4.4.1", + "dotenv": "^17.2.0", "mime": "^4.0.7", - "playwright": "1.54.1", + "playwright": "1.55.0-alpha-2025-08-12", + "playwright-core": "1.55.0-alpha-2025-08-12", "ws": "^8.18.1", + "zod": "^3.24.1", "zod-to-json-schema": "^3.24.4" }, "devDependencies": { + "@anthropic-ai/sdk": "^0.57.0", "@eslint/eslintrc": "^3.2.0", "@eslint/js": "^9.19.0", - "@playwright/test": "1.54.1", + "@playwright/test": "1.55.0-alpha-2025-08-12", "@stylistic/eslint-plugin": "^3.0.1", - "@types/chrome": "^0.0.315", "@types/debug": "^4.1.12", "@types/node": "^22.13.10", "@types/ws": "^8.18.1", "@typescript-eslint/eslint-plugin": "^8.26.1", "@typescript-eslint/parser": "^8.26.1", "@typescript-eslint/utils": "^8.26.1", + "esbuild": "^0.20.1", "eslint": "^9.19.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-notice": "^1.0.0", + "openai": "^5.10.2", "typescript": "^5.8.2" }, "bin": { diff --git a/playwright.config.ts b/playwright.config.ts index 9c8ba59..8486de1 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -22,12 +22,10 @@ export default defineConfig({ testDir: './tests', fullyParallel: true, forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, - workers: process.env.CI ? 1 : undefined, + workers: process.env.CI ? 2 : undefined, reporter: 'list', projects: [ { name: 'chrome' }, - { name: 'msedge', use: { mcpBrowser: 'msedge' } }, { name: 'chromium', use: { mcpBrowser: 'chromium' } }, ...process.env.MCP_IN_DOCKER ? [{ name: 'chromium-docker', @@ -39,5 +37,6 @@ export default defineConfig({ }] : [], { name: 'firefox', use: { mcpBrowser: 'firefox' } }, { name: 'webkit', use: { mcpBrowser: 'webkit' } }, + ... process.platform === 'win32' ? [{ name: 'msedge', use: { mcpBrowser: 'msedge' } }] : [], ], }); diff --git a/src/DEPS.list b/src/DEPS.list new file mode 100644 index 0000000..c1e18f2 --- /dev/null +++ b/src/DEPS.list @@ -0,0 +1,7 @@ +[*] +./tools/ +./mcp/ +./utils/ + +[program.ts] +*** diff --git a/src/actions.d.ts b/src/actions.d.ts new file mode 100644 index 0000000..e52b420 --- /dev/null +++ b/src/actions.d.ts @@ -0,0 +1,172 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +type Point = { x: number, y: number }; + +export type ActionName = + 'check' | + 'click' | + 'closePage' | + 'fill' | + 'navigate' | + 'openPage' | + 'press' | + 'select' | + 'uncheck' | + 'setInputFiles' | + 'assertText' | + 'assertValue' | + 'assertChecked' | + 'assertVisible' | + 'assertSnapshot'; + +export type ActionBase = { + name: ActionName, + signals: Signal[], + ariaSnapshot?: string, +}; + +export type ActionWithSelector = ActionBase & { + selector: string, + ref?: string, +}; + +export type ClickAction = ActionWithSelector & { + name: 'click', + button: 'left' | 'middle' | 'right', + modifiers: number, + clickCount: number, + position?: Point, +}; + +export type CheckAction = ActionWithSelector & { + name: 'check', +}; + +export type UncheckAction = ActionWithSelector & { + name: 'uncheck', +}; + +export type FillAction = ActionWithSelector & { + name: 'fill', + text: string, +}; + +export type NavigateAction = ActionBase & { + name: 'navigate', + url: string, +}; + +export type OpenPageAction = ActionBase & { + name: 'openPage', + url: string, +}; + +export type ClosesPageAction = ActionBase & { + name: 'closePage', +}; + +export type PressAction = ActionWithSelector & { + name: 'press', + key: string, + modifiers: number, +}; + +export type SelectAction = ActionWithSelector & { + name: 'select', + options: string[], +}; + +export type SetInputFilesAction = ActionWithSelector & { + name: 'setInputFiles', + files: string[], +}; + +export type AssertTextAction = ActionWithSelector & { + name: 'assertText', + text: string, + substring: boolean, +}; + +export type AssertValueAction = ActionWithSelector & { + name: 'assertValue', + value: string, +}; + +export type AssertCheckedAction = ActionWithSelector & { + name: 'assertChecked', + checked: boolean, +}; + +export type AssertVisibleAction = ActionWithSelector & { + name: 'assertVisible', +}; + +export type AssertSnapshotAction = ActionWithSelector & { + name: 'assertSnapshot', + ariaSnapshot: string, +}; + +export type Action = ClickAction | CheckAction | ClosesPageAction | OpenPageAction | UncheckAction | FillAction | NavigateAction | PressAction | SelectAction | SetInputFilesAction | AssertTextAction | AssertValueAction | AssertCheckedAction | AssertVisibleAction | AssertSnapshotAction; +export type AssertAction = AssertCheckedAction | AssertValueAction | AssertTextAction | AssertVisibleAction | AssertSnapshotAction; +export type PerformOnRecordAction = ClickAction | CheckAction | UncheckAction | PressAction | SelectAction; + +// Signals. + +export type BaseSignal = { +}; + +export type NavigationSignal = BaseSignal & { + name: 'navigation', + url: string, +}; + +export type PopupSignal = BaseSignal & { + name: 'popup', + popupAlias: string, +}; + +export type DownloadSignal = BaseSignal & { + name: 'download', + downloadAlias: string, +}; + +export type DialogSignal = BaseSignal & { + name: 'dialog', + dialogAlias: string, +}; + +export type Signal = NavigationSignal | PopupSignal | DownloadSignal | DialogSignal; + +export type FrameDescription = { + pageGuid: string; + pageAlias: string; + framePath: string[]; +}; + +export type ActionInContext = { + frame: FrameDescription; + description?: string; + action: Action; + startTime: number; + endTime?: number; +}; + +export type SignalInContext = { + frame: FrameDescription; + signal: Signal; + timestamp: number; +}; diff --git a/src/browserContextFactory.ts b/src/browserContextFactory.ts index f14cd7d..81072e1 100644 --- a/src/browserContextFactory.ts +++ b/src/browserContextFactory.ts @@ -14,51 +14,52 @@ * limitations under the License. */ -import fs from 'node:fs'; -import net from 'node:net'; -import path from 'node:path'; -import os from 'node:os'; +import fs from 'fs'; +import net from 'net'; +import path from 'path'; -import debug from 'debug'; import * as playwright from 'playwright'; -import { userDataDir } from './fileUtils.js'; +// @ts-ignore +import { registryDirectory } from 'playwright-core/lib/server/registry/index'; +// @ts-ignore +import { startTraceViewerServer } from 'playwright-core/lib/server'; +import { logUnhandledError, testDebug } from './utils/log.js'; +import { createHash } from './utils/guid.js'; +import { outputFile } from './config.js'; import type { FullConfig } from './config.js'; -import type { BrowserInfo, LaunchBrowserRequest } from './browserServer.js'; - -const testDebug = debug('pw:mcp:test'); - -export function contextFactory(browserConfig: FullConfig['browser']): BrowserContextFactory { - if (browserConfig.remoteEndpoint) - return new RemoteContextFactory(browserConfig); - if (browserConfig.cdpEndpoint) - return new CdpContextFactory(browserConfig); - if (browserConfig.isolated) - return new IsolatedContextFactory(browserConfig); - if (browserConfig.browserAgent) - return new BrowserServerContextFactory(browserConfig); - return new PersistentContextFactory(browserConfig); + +export function contextFactory(config: FullConfig): BrowserContextFactory { + if (config.browser.remoteEndpoint) + return new RemoteContextFactory(config); + if (config.browser.cdpEndpoint) + return new CdpContextFactory(config); + if (config.browser.isolated) + return new IsolatedContextFactory(config); + return new PersistentContextFactory(config); } +export type ClientInfo = { name?: string, version?: string, rootPath?: string }; + export interface BrowserContextFactory { - createContext(): Promise<{ browserContext: playwright.BrowserContext, close: () => Promise }>; + createContext(clientInfo: ClientInfo, abortSignal: AbortSignal, toolName: string | undefined): Promise<{ browserContext: playwright.BrowserContext, close: () => Promise }>; } class BaseContextFactory implements BrowserContextFactory { - readonly browserConfig: FullConfig['browser']; + readonly config: FullConfig; + private _logName: string; protected _browserPromise: Promise | undefined; - readonly name: string; - constructor(name: string, browserConfig: FullConfig['browser']) { - this.name = name; - this.browserConfig = browserConfig; + constructor(name: string, config: FullConfig) { + this._logName = name; + this.config = config; } - protected async _obtainBrowser(): Promise { + protected async _obtainBrowser(clientInfo: ClientInfo): Promise { if (this._browserPromise) return this._browserPromise; - testDebug(`obtain browser (${this.name})`); - this._browserPromise = this._doObtainBrowser(); + testDebug(`obtain browser (${this._logName})`); + this._browserPromise = this._doObtainBrowser(clientInfo); void this._browserPromise.then(browser => { browser.on('disconnected', () => { this._browserPromise = undefined; @@ -69,13 +70,13 @@ class BaseContextFactory implements BrowserContextFactory { return this._browserPromise; } - protected async _doObtainBrowser(): Promise { + protected async _doObtainBrowser(clientInfo: ClientInfo): Promise { throw new Error('Not implemented'); } - async createContext(): Promise<{ browserContext: playwright.BrowserContext, close: () => Promise }> { - testDebug(`create browser context (${this.name})`); - const browser = await this._obtainBrowser(); + async createContext(clientInfo: ClientInfo): Promise<{ browserContext: playwright.BrowserContext, close: () => Promise }> { + testDebug(`create browser context (${this._logName})`); + const browser = await this._obtainBrowser(clientInfo); const browserContext = await this._doCreateContext(browser); return { browserContext, close: () => this._closeBrowserContext(browserContext, browser) }; } @@ -85,27 +86,28 @@ class BaseContextFactory implements BrowserContextFactory { } private async _closeBrowserContext(browserContext: playwright.BrowserContext, browser: playwright.Browser) { - testDebug(`close browser context (${this.name})`); + testDebug(`close browser context (${this._logName})`); if (browser.contexts().length === 1) this._browserPromise = undefined; - await browserContext.close().catch(() => {}); + await browserContext.close().catch(logUnhandledError); if (browser.contexts().length === 0) { - testDebug(`close browser (${this.name})`); - await browser.close().catch(() => {}); + testDebug(`close browser (${this._logName})`); + await browser.close().catch(logUnhandledError); } } } class IsolatedContextFactory extends BaseContextFactory { - constructor(browserConfig: FullConfig['browser']) { - super('isolated', browserConfig); + constructor(config: FullConfig) { + super('isolated', config); } - protected override async _doObtainBrowser(): Promise { - await injectCdpPort(this.browserConfig); - const browserType = playwright[this.browserConfig.browserName]; + protected override async _doObtainBrowser(clientInfo: ClientInfo): Promise { + await injectCdpPort(this.config.browser); + const browserType = playwright[this.config.browser.browserName]; return browserType.launch({ - ...this.browserConfig.launchOptions, + tracesDir: await startTraceServer(this.config, clientInfo.rootPath), + ...this.config.browser.launchOptions, handleSIGINT: false, handleSIGTERM: false, }).catch(error => { @@ -116,35 +118,35 @@ class IsolatedContextFactory extends BaseContextFactory { } protected override async _doCreateContext(browser: playwright.Browser): Promise { - return browser.newContext(this.browserConfig.contextOptions); + return browser.newContext(this.config.browser.contextOptions); } } class CdpContextFactory extends BaseContextFactory { - constructor(browserConfig: FullConfig['browser']) { - super('cdp', browserConfig); + constructor(config: FullConfig) { + super('cdp', config); } protected override async _doObtainBrowser(): Promise { - return playwright.chromium.connectOverCDP(this.browserConfig.cdpEndpoint!); + return playwright.chromium.connectOverCDP(this.config.browser.cdpEndpoint!); } protected override async _doCreateContext(browser: playwright.Browser): Promise { - return this.browserConfig.isolated ? await browser.newContext() : browser.contexts()[0]; + return this.config.browser.isolated ? await browser.newContext() : browser.contexts()[0]; } } class RemoteContextFactory extends BaseContextFactory { - constructor(browserConfig: FullConfig['browser']) { - super('remote', browserConfig); + constructor(config: FullConfig) { + super('remote', config); } protected override async _doObtainBrowser(): Promise { - const url = new URL(this.browserConfig.remoteEndpoint!); - url.searchParams.set('browser', this.browserConfig.browserName); - if (this.browserConfig.launchOptions) - url.searchParams.set('launch-options', JSON.stringify(this.browserConfig.launchOptions)); - return playwright[this.browserConfig.browserName].connect(String(url)); + const url = new URL(this.config.browser.remoteEndpoint!); + url.searchParams.set('browser', this.config.browser.browserName); + if (this.config.browser.launchOptions) + url.searchParams.set('launch-options', JSON.stringify(this.config.browser.launchOptions)); + return playwright[this.config.browser.browserName].connect(String(url)); } protected override async _doCreateContext(browser: playwright.Browser): Promise { @@ -153,27 +155,32 @@ class RemoteContextFactory extends BaseContextFactory { } class PersistentContextFactory implements BrowserContextFactory { - readonly browserConfig: FullConfig['browser']; + readonly config: FullConfig; + readonly name = 'persistent'; + readonly description = 'Create a new persistent browser context'; + private _userDataDirs = new Set(); - constructor(browserConfig: FullConfig['browser']) { - this.browserConfig = browserConfig; + constructor(config: FullConfig) { + this.config = config; } - async createContext(): Promise<{ browserContext: playwright.BrowserContext, close: () => Promise }> { - await injectCdpPort(this.browserConfig); + async createContext(clientInfo: ClientInfo): Promise<{ browserContext: playwright.BrowserContext, close: () => Promise }> { + await injectCdpPort(this.config.browser); testDebug('create browser context (persistent)'); - const userDataDir = this.browserConfig.userDataDir ?? await this._createUserDataDir(); + const userDataDir = this.config.browser.userDataDir ?? await this._createUserDataDir(clientInfo.rootPath); + const tracesDir = await startTraceServer(this.config, clientInfo.rootPath); this._userDataDirs.add(userDataDir); testDebug('lock user data dir', userDataDir); - const browserType = playwright[this.browserConfig.browserName]; + const browserType = playwright[this.config.browser.browserName]; for (let i = 0; i < 5; i++) { try { const browserContext = await browserType.launchPersistentContext(userDataDir, { - ...this.browserConfig.launchOptions, - ...this.browserConfig.contextOptions, + tracesDir, + ...this.config.browser.launchOptions, + ...this.config.browser.contextOptions, handleSIGINT: false, handleSIGTERM: false, }); @@ -201,60 +208,23 @@ class PersistentContextFactory implements BrowserContextFactory { testDebug('close browser context complete (persistent)'); } - private async _createUserDataDir() { - let cacheDirectory: string; - if (process.platform === 'linux') - cacheDirectory = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache'); - else if (process.platform === 'darwin') - cacheDirectory = path.join(os.homedir(), 'Library', 'Caches'); - else if (process.platform === 'win32') - cacheDirectory = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'); - else - throw new Error('Unsupported platform: ' + process.platform); - const result = path.join(cacheDirectory, 'ms-playwright', `mcp-${this.browserConfig.launchOptions?.channel ?? this.browserConfig?.browserName}-profile`); + private async _createUserDataDir(rootPath: string | undefined) { + const dir = process.env.PWMCP_PROFILES_DIR_FOR_TEST ?? registryDirectory; + const browserToken = this.config.browser.launchOptions?.channel ?? this.config.browser?.browserName; + // Hesitant putting hundreds of files into the user's workspace, so using it for hashing instead. + const rootPathToken = rootPath ? `-${createHash(rootPath)}` : ''; + const result = path.join(dir, `mcp-${browserToken}${rootPathToken}`); await fs.promises.mkdir(result, { recursive: true }); return result; } } -export class BrowserServerContextFactory extends BaseContextFactory { - constructor(browserConfig: FullConfig['browser']) { - super('persistent', browserConfig); - } - - protected override async _doObtainBrowser(): Promise { - const response = await fetch(new URL(`/json/launch`, this.browserConfig.browserAgent), { - method: 'POST', - body: JSON.stringify({ - browserType: this.browserConfig.browserName, - userDataDir: this.browserConfig.userDataDir ?? await this._createUserDataDir(), - launchOptions: this.browserConfig.launchOptions, - contextOptions: this.browserConfig.contextOptions, - } as LaunchBrowserRequest), - }); - const info = await response.json() as BrowserInfo; - if (info.error) - throw new Error(info.error); - return await playwright.chromium.connectOverCDP(`http://localhost:${info.cdpPort}/`); - } - - protected override async _doCreateContext(browser: playwright.Browser): Promise { - return this.browserConfig.isolated ? await browser.newContext() : browser.contexts()[0]; - } - - private async _createUserDataDir() { - const dir = await userDataDir(this.browserConfig); - await fs.promises.mkdir(dir, { recursive: true }); - return dir; - } -} - async function injectCdpPort(browserConfig: FullConfig['browser']) { if (browserConfig.browserName === 'chromium') (browserConfig.launchOptions as any).cdpPort = await findFreePort(); } -async function findFreePort() { +async function findFreePort(): Promise { return new Promise((resolve, reject) => { const server = net.createServer(); server.listen(0, () => { @@ -264,3 +234,16 @@ async function findFreePort() { server.on('error', reject); }); } + +async function startTraceServer(config: FullConfig, rootPath: string | undefined): Promise { + if (!config.saveTrace) + return undefined; + + const tracesDir = await outputFile(config, rootPath, `traces-${Date.now()}`); + const server = await startTraceViewerServer(); + const urlPrefix = server.urlPrefix('human-readable'); + const url = urlPrefix + '/trace/index.html?trace=' + tracesDir + '/trace.json'; + // eslint-disable-next-line no-console + console.error('\nTrace viewer listening on ' + url); + return tracesDir; +} diff --git a/src/browserServer.ts b/src/browserServer.ts deleted file mode 100644 index 85c908d..0000000 --- a/src/browserServer.ts +++ /dev/null @@ -1,197 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable no-console */ - -import net from 'net'; - -import { program } from 'commander'; -import playwright from 'playwright'; - -import { HttpServer } from './httpServer.js'; -import { packageJSON } from './package.js'; - -import type http from 'http'; - -export type LaunchBrowserRequest = { - browserType: string; - userDataDir: string; - launchOptions: playwright.LaunchOptions; - contextOptions: playwright.BrowserContextOptions; -}; - -export type BrowserInfo = { - browserType: string; - userDataDir: string; - cdpPort: number; - launchOptions: playwright.LaunchOptions; - contextOptions: playwright.BrowserContextOptions; - error?: string; -}; - -type BrowserEntry = { - browser?: playwright.Browser; - info: BrowserInfo; -}; - -class BrowserServer { - private _server = new HttpServer(); - private _entries: BrowserEntry[] = []; - - constructor() { - this._setupExitHandler(); - } - - async start(port: number) { - await this._server.start({ port }); - this._server.routePath('/json/list', (req, res) => { - this._handleJsonList(res); - }); - this._server.routePath('/json/launch', async (req, res) => { - void this._handleLaunchBrowser(req, res).catch(e => console.error(e)); - }); - this._setEntries([]); - } - - private _handleJsonList(res: http.ServerResponse) { - const list = this._entries.map(browser => browser.info); - res.end(JSON.stringify(list)); - } - - private async _handleLaunchBrowser(req: http.IncomingMessage, res: http.ServerResponse) { - const request = await readBody(req); - let info = this._entries.map(entry => entry.info).find(info => info.userDataDir === request.userDataDir); - if (!info || info.error) - info = await this._newBrowser(request); - res.end(JSON.stringify(info)); - } - - private async _newBrowser(request: LaunchBrowserRequest): Promise { - const cdpPort = await findFreePort(); - (request.launchOptions as any).cdpPort = cdpPort; - const info: BrowserInfo = { - browserType: request.browserType, - userDataDir: request.userDataDir, - cdpPort, - launchOptions: request.launchOptions, - contextOptions: request.contextOptions, - }; - - const browserType = playwright[request.browserType as 'chromium' | 'firefox' | 'webkit']; - const { browser, error } = await browserType.launchPersistentContext(request.userDataDir, { - ...request.launchOptions, - ...request.contextOptions, - handleSIGINT: false, - handleSIGTERM: false, - }).then(context => { - return { browser: context.browser()!, error: undefined }; - }).catch(error => { - return { browser: undefined, error: error.message }; - }); - this._setEntries([...this._entries, { - browser, - info: { - browserType: request.browserType, - userDataDir: request.userDataDir, - cdpPort, - launchOptions: request.launchOptions, - contextOptions: request.contextOptions, - error, - }, - }]); - browser?.on('disconnected', () => { - this._setEntries(this._entries.filter(entry => entry.browser !== browser)); - }); - return info; - } - - private _updateReport() { - // Clear the current line and move cursor to top of screen - process.stdout.write('\x1b[2J\x1b[H'); - process.stdout.write(`Playwright Browser Server v${packageJSON.version}\n`); - process.stdout.write(`Listening on ${this._server.urlPrefix('human-readable')}\n\n`); - - if (this._entries.length === 0) { - process.stdout.write('No browsers currently running\n'); - return; - } - - process.stdout.write('Running browsers:\n'); - for (const entry of this._entries) { - const status = entry.browser ? 'running' : 'error'; - const statusColor = entry.browser ? '\x1b[32m' : '\x1b[31m'; // green for running, red for error - process.stdout.write(`${statusColor}${entry.info.browserType}\x1b[0m (${entry.info.userDataDir}) - ${statusColor}${status}\x1b[0m\n`); - if (entry.info.error) - process.stdout.write(` Error: ${entry.info.error}\n`); - } - - } - - private _setEntries(entries: BrowserEntry[]) { - this._entries = entries; - this._updateReport(); - } - - private _setupExitHandler() { - let isExiting = false; - const handleExit = async () => { - if (isExiting) - return; - isExiting = true; - setTimeout(() => process.exit(0), 15000); - for (const entry of this._entries) - await entry.browser?.close().catch(() => {}); - process.exit(0); - }; - - process.stdin.on('close', handleExit); - process.on('SIGINT', handleExit); - process.on('SIGTERM', handleExit); - } -} - -program - .name('browser-agent') - .option('-p, --port ', 'Port to listen on', '9224') - .action(async options => { - await main(options); - }); - -void program.parseAsync(process.argv); - -async function main(options: { port: string }) { - const server = new BrowserServer(); - await server.start(+options.port); -} - -function readBody(req: http.IncomingMessage): Promise { - return new Promise((resolve, reject) => { - const chunks: Buffer[] = []; - req.on('data', (chunk: Buffer) => chunks.push(chunk)); - req.on('end', () => resolve(JSON.parse(Buffer.concat(chunks).toString()))); - }); -} - -async function findFreePort(): Promise { - return new Promise((resolve, reject) => { - const server = net.createServer(); - server.listen(0, () => { - const { port } = server.address() as net.AddressInfo; - server.close(() => resolve(port)); - }); - server.on('error', reject); - }); -} diff --git a/src/browserServerBackend.ts b/src/browserServerBackend.ts new file mode 100644 index 0000000..10f5ff4 --- /dev/null +++ b/src/browserServerBackend.ts @@ -0,0 +1,88 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { fileURLToPath } from 'url'; +import { FullConfig } from './config.js'; +import { Context } from './context.js'; +import { logUnhandledError } from './utils/log.js'; +import { Response } from './response.js'; +import { SessionLog } from './sessionLog.js'; +import { filteredTools } from './tools.js'; +import { toMcpTool } from './mcp/tool.js'; + +import type { Tool } from './tools/tool.js'; +import type { BrowserContextFactory } from './browserContextFactory.js'; +import type * as mcpServer from './mcp/server.js'; +import type { ServerBackend } from './mcp/server.js'; + +export class BrowserServerBackend implements ServerBackend { + private _tools: Tool[]; + private _context: Context | undefined; + private _sessionLog: SessionLog | undefined; + private _config: FullConfig; + private _browserContextFactory: BrowserContextFactory; + + constructor(config: FullConfig, factory: BrowserContextFactory) { + this._config = config; + this._browserContextFactory = factory; + this._tools = filteredTools(config); + } + + async initialize(server: mcpServer.Server, clientVersion: mcpServer.ClientVersion, roots: mcpServer.Root[]): Promise { + let rootPath: string | undefined; + if (roots.length > 0) { + const firstRootUri = roots[0]?.uri; + const url = firstRootUri ? new URL(firstRootUri) : undefined; + rootPath = url ? fileURLToPath(url) : undefined; + } + this._sessionLog = this._config.saveSession ? await SessionLog.create(this._config, rootPath) : undefined; + this._context = new Context({ + tools: this._tools, + config: this._config, + browserContextFactory: this._browserContextFactory, + sessionLog: this._sessionLog, + clientInfo: { ...clientVersion, rootPath }, + }); + } + + async listTools(): Promise { + return this._tools.map(tool => toMcpTool(tool.schema)); + } + + async callTool(name: string, rawArguments: mcpServer.CallToolRequest['params']['arguments']) { + const tool = this._tools.find(tool => tool.schema.name === name)!; + if (!tool) + throw new Error(`Tool "${name}" not found`); + const parsedArguments = tool.schema.inputSchema.parse(rawArguments || {}); + const context = this._context!; + const response = new Response(context, name, parsedArguments); + context.setRunningTool(name); + try { + await tool.handle(context, parsedArguments, response); + await response.finish(); + this._sessionLog?.logResponse(response); + } catch (error: any) { + response.addError(String(error)); + } finally { + context.setRunningTool(undefined); + } + return response.serialize(); + } + + serverClosed() { + void this._context?.dispose().catch(logUnhandledError); + } +} diff --git a/src/config.ts b/src/config.ts index d2cbd67..e579002 100644 --- a/src/config.ts +++ b/src/config.ts @@ -18,18 +18,17 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; import { devices } from 'playwright'; +import { sanitizeForFilePath } from './utils/fileUtils.js'; import type { Config, ToolCapability } from '../config.js'; import type { BrowserContextOptions, LaunchOptions } from 'playwright'; -import { sanitizeForFilePath } from './tools/utils.js'; export type CLIOptions = { allowedOrigins?: string[]; blockedOrigins?: string[]; blockServiceWorkers?: boolean; browser?: string; - browserAgent?: string; - caps?: string; + caps?: string[]; cdpEndpoint?: string; config?: string; device?: string; @@ -38,18 +37,18 @@ export type CLIOptions = { host?: string; ignoreHttpsErrors?: boolean; isolated?: boolean; - imageResponses?: 'allow' | 'omit' | 'auto'; - sandbox: boolean; + imageResponses?: 'allow' | 'omit'; + sandbox?: boolean; outputDir?: string; port?: number; proxyBypass?: string; proxyServer?: string; + saveSession?: boolean; saveTrace?: boolean; storageState?: string; userAgent?: string; userDataDir?: string; viewportSize?: string; - vision?: boolean; }; const defaultConfig: FullConfig = { @@ -69,7 +68,7 @@ const defaultConfig: FullConfig = { blockedOrigins: undefined, }, server: {}, - outputDir: path.join(os.tmpdir(), 'playwright-mcp-output', sanitizeForFilePath(new Date().toISOString())), + saveTrace: false, }; type BrowserUserConfig = NonNullable; @@ -81,7 +80,7 @@ export type FullConfig = Config & { contextOptions: NonNullable; }, network: NonNullable, - outputDir: string; + saveTrace: boolean; server: NonNullable, }; @@ -91,15 +90,16 @@ export async function resolveConfig(config: Config): Promise { export async function resolveCLIConfig(cliOptions: CLIOptions): Promise { const configInFile = await loadConfig(cliOptions.config); - const cliOverrides = await configFromCLIOptions(cliOptions); - const result = mergeConfig(mergeConfig(defaultConfig, configInFile), cliOverrides); - // Derive artifact output directory from config.outputDir - if (result.saveTrace) - result.browser.launchOptions.tracesDir = path.join(result.outputDir, 'traces'); + const envOverrides = configFromEnv(); + const cliOverrides = configFromCLIOptions(cliOptions); + let result = defaultConfig; + result = mergeConfig(result, configInFile); + result = mergeConfig(result, envOverrides); + result = mergeConfig(result, cliOverrides); return result; } -export async function configFromCLIOptions(cliOptions: CLIOptions): Promise { +export function configFromCLIOptions(cliOptions: CLIOptions): Config { let browserName: 'chromium' | 'firefox' | 'webkit' | undefined; let channel: string | undefined; switch (cliOptions.browser) { @@ -131,7 +131,7 @@ export async function configFromCLIOptions(cliOptions: CLIOptions): Promise c.trim() as ToolCapability), - vision: !!cliOptions.vision, + capabilities: cliOptions.caps as ToolCapability[], network: { allowedOrigins: cliOptions.allowedOrigins, blockedOrigins: cliOptions.blockedOrigins, }, + saveSession: cliOptions.saveSession, saveTrace: cliOptions.saveTrace, outputDir: cliOptions.outputDir, imageResponses: cliOptions.imageResponses, @@ -198,6 +197,36 @@ export async function configFromCLIOptions(cliOptions: CLIOptions): Promise { if (!configFile) return {}; @@ -209,10 +238,14 @@ async function loadConfig(configFile: string | undefined): Promise { } } -export async function outputFile(config: FullConfig, name: string): Promise { - await fs.promises.mkdir(config.outputDir, { recursive: true }); +export async function outputFile(config: FullConfig, rootPath: string | undefined, name: string): Promise { + const outputDir = config.outputDir + ?? (rootPath ? path.join(rootPath, '.playwright-mcp') : undefined) + ?? path.join(os.tmpdir(), 'playwright-mcp-output', sanitizeForFilePath(new Date().toISOString())); + + await fs.promises.mkdir(outputDir, { recursive: true }); const fileName = sanitizeForFilePath(name); - return path.join(config.outputDir, fileName); + return path.join(outputDir, fileName); } function pickDefined(obj: T | undefined): Partial { @@ -255,3 +288,33 @@ function mergeConfig(base: FullConfig, overrides: Config): FullConfig { }, } as FullConfig; } + +export function semicolonSeparatedList(value: string | undefined): string[] | undefined { + if (!value) + return undefined; + return value.split(';').map(v => v.trim()); +} + +export function commaSeparatedList(value: string | undefined): string[] | undefined { + if (!value) + return undefined; + return value.split(',').map(v => v.trim()); +} + +function envToNumber(value: string | undefined): number | undefined { + if (!value) + return undefined; + return +value; +} + +function envToBoolean(value: string | undefined): boolean | undefined { + if (value === 'true' || value === '1') + return true; + if (value === 'false' || value === '0') + return false; + return undefined; +} + +function envToString(value: string | undefined): string | undefined { + return value ? value.trim() : undefined; +} diff --git a/src/connection.ts b/src/connection.ts deleted file mode 100644 index a9508bb..0000000 --- a/src/connection.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js'; -import { CallToolRequestSchema, ListToolsRequestSchema, Tool as McpTool } from '@modelcontextprotocol/sdk/types.js'; -import { zodToJsonSchema } from 'zod-to-json-schema'; - -import { Context } from './context.js'; -import { snapshotTools, visionTools } from './tools.js'; -import { packageJSON } from './package.js'; - -import { FullConfig } from './config.js'; - -import type { BrowserContextFactory } from './browserContextFactory.js'; - -export function createConnection(config: FullConfig, browserContextFactory: BrowserContextFactory): Connection { - const allTools = config.vision ? visionTools : snapshotTools; - const tools = allTools.filter(tool => !config.capabilities || tool.capability === 'core' || config.capabilities.includes(tool.capability)); - const context = new Context(tools, config, browserContextFactory); - const server = new McpServer({ name: 'Playwright', version: packageJSON.version }, { - capabilities: { - tools: {}, - } - }); - - server.setRequestHandler(ListToolsRequestSchema, async () => { - return { - tools: tools.map(tool => ({ - name: tool.schema.name, - description: tool.schema.description, - inputSchema: zodToJsonSchema(tool.schema.inputSchema), - annotations: { - title: tool.schema.title, - readOnlyHint: tool.schema.type === 'readOnly', - destructiveHint: tool.schema.type === 'destructive', - openWorldHint: true, - }, - })) as McpTool[], - }; - }); - - server.setRequestHandler(CallToolRequestSchema, async request => { - const errorResult = (...messages: string[]) => ({ - content: [{ type: 'text', text: messages.join('\n') }], - isError: true, - }); - const tool = tools.find(tool => tool.schema.name === request.params.name); - if (!tool) - return errorResult(`Tool "${request.params.name}" not found`); - - - const modalStates = context.modalStates().map(state => state.type); - if (tool.clearsModalState && !modalStates.includes(tool.clearsModalState)) - return errorResult(`The tool "${request.params.name}" can only be used when there is related modal state present.`, ...context.modalStatesMarkdown()); - if (!tool.clearsModalState && modalStates.length) - return errorResult(`Tool "${request.params.name}" does not handle the modal state.`, ...context.modalStatesMarkdown()); - - try { - return await context.run(tool, request.params.arguments); - } catch (error) { - return errorResult(String(error)); - } - }); - - return new Connection(server, context); -} - -export class Connection { - readonly server: McpServer; - readonly context: Context; - - constructor(server: McpServer, context: Context) { - this.server = server; - this.context = context; - this.server.oninitialized = () => { - this.context.clientVersion = this.server.getClientVersion(); - }; - } - - async close() { - await this.server.close(); - await this.context.close(); - } -} diff --git a/src/context.ts b/src/context.ts index ecf66b9..1890b3d 100644 --- a/src/context.ts +++ b/src/context.ts @@ -17,79 +17,68 @@ import debug from 'debug'; import * as playwright from 'playwright'; -import { callOnPageNoTrace, waitForCompletion } from './tools/utils.js'; -import { ManualPromise } from './manualPromise.js'; +import { logUnhandledError } from './utils/log.js'; import { Tab } from './tab.js'; -import { outputFile } from './config.js'; +import { outputFile } from './config.js'; -import type { ImageContent, TextContent } from '@modelcontextprotocol/sdk/types.js'; -import type { ModalState, Tool, ToolActionResult } from './tools/tool.js'; import type { FullConfig } from './config.js'; -import type { BrowserContextFactory } from './browserContextFactory.js'; - -type PendingAction = { - dialogShown: ManualPromise; -}; +import type { Tool } from './tools/tool.js'; +import type { BrowserContextFactory, ClientInfo } from './browserContextFactory.js'; +import type * as actions from './actions.js'; +import type { SessionLog } from './sessionLog.js'; const testDebug = debug('pw:mcp:test'); +type ContextOptions = { + tools: Tool[]; + config: FullConfig; + browserContextFactory: BrowserContextFactory; + sessionLog: SessionLog | undefined; + clientInfo: ClientInfo; +}; + export class Context { readonly tools: Tool[]; readonly config: FullConfig; + readonly sessionLog: SessionLog | undefined; + readonly options: ContextOptions; private _browserContextPromise: Promise<{ browserContext: playwright.BrowserContext, close: () => Promise }> | undefined; private _browserContextFactory: BrowserContextFactory; private _tabs: Tab[] = []; private _currentTab: Tab | undefined; - private _modalStates: (ModalState & { tab: Tab })[] = []; - private _pendingAction: PendingAction | undefined; - private _downloads: { download: playwright.Download, finished: boolean, outputFile: string }[] = []; - clientVersion: { name: string; version: string; } | undefined; - - constructor(tools: Tool[], config: FullConfig, browserContextFactory: BrowserContextFactory) { - this.tools = tools; - this.config = config; - this._browserContextFactory = browserContextFactory; + private _clientInfo: ClientInfo; + + private static _allContexts: Set = new Set(); + private _closeBrowserContextPromise: Promise | undefined; + private _runningToolName: string | undefined; + private _abortController = new AbortController(); + + constructor(options: ContextOptions) { + this.tools = options.tools; + this.config = options.config; + this.sessionLog = options.sessionLog; + this.options = options; + this._browserContextFactory = options.browserContextFactory; + this._clientInfo = options.clientInfo; testDebug('create context'); + Context._allContexts.add(this); } - clientSupportsImages(): boolean { - if (this.config.imageResponses === 'allow') - return true; - if (this.config.imageResponses === 'omit') - return false; - return !this.clientVersion?.name.includes('cursor'); - } - - modalStates(): ModalState[] { - return this._modalStates; - } - - setModalState(modalState: ModalState, inTab: Tab) { - this._modalStates.push({ ...modalState, tab: inTab }); - } - - clearModalState(modalState: ModalState) { - this._modalStates = this._modalStates.filter(state => state !== modalState); - } - - modalStatesMarkdown(): string[] { - const result: string[] = ['### Modal state']; - if (this._modalStates.length === 0) - result.push('- There is no modal state present'); - for (const state of this._modalStates) { - const tool = this.tools.find(tool => tool.clearsModalState === state.type); - result.push(`- [${state.description}]: can be handled by the "${tool?.schema.name}" tool`); - } - return result; + static async disposeAll() { + await Promise.all([...Context._allContexts].map(context => context.dispose())); } tabs(): Tab[] { return this._tabs; } + currentTab(): Tab | undefined { + return this._currentTab; + } + currentTabOrDie(): Tab { if (!this._currentTab) - throw new Error('No current snapshot available. Capture a snapshot or navigate to a new location first.'); + throw new Error('No open pages available. Use the "browser_navigate" tool to navigate to a page first.'); return this._currentTab; } @@ -101,8 +90,12 @@ export class Context { } async selectTab(index: number) { - this._currentTab = this._tabs[index - 1]; - await this._currentTab.page.bringToFront(); + const tab = this._tabs[index]; + if (!tab) + throw new Error(`Tab ${index} not found`); + await tab.page.bringToFront(); + this._currentTab = tab; + return tab; } async ensureTab(): Promise { @@ -112,162 +105,17 @@ export class Context { return this._currentTab!; } - async listTabsMarkdown(): Promise { - if (!this._tabs.length) - return '### No tabs open'; - const lines: string[] = ['### Open tabs']; - for (let i = 0; i < this._tabs.length; i++) { - const tab = this._tabs[i]; - const title = await tab.title(); - const url = tab.page.url(); - const current = tab === this._currentTab ? ' (current)' : ''; - lines.push(`- ${i + 1}:${current} [${title}] (${url})`); - } - return lines.join('\n'); + async closeTab(index: number | undefined): Promise { + const tab = index === undefined ? this._currentTab : this._tabs[index]; + if (!tab) + throw new Error(`Tab ${index} not found`); + const url = tab.page.url(); + await tab.page.close(); + return url; } - async closeTab(index: number | undefined) { - const tab = index === undefined ? this._currentTab : this._tabs[index - 1]; - await tab?.page.close(); - return await this.listTabsMarkdown(); - } - - async run(tool: Tool, params: Record | undefined) { - // Tab management is done outside of the action() call. - const toolResult = await tool.handle(this, tool.schema.inputSchema.parse(params || {})); - const { code, action, waitForNetwork, captureSnapshot, resultOverride } = toolResult; - const racingAction = action ? () => this._raceAgainstModalDialogs(action) : undefined; - - if (resultOverride) - return resultOverride; - - if (!this._currentTab) { - return { - content: [{ - type: 'text', - text: 'No open pages available. Use the "browser_navigate" tool to navigate to a page first.', - }], - }; - } - - const tab = this.currentTabOrDie(); - // TODO: race against modal dialogs to resolve clicks. - let actionResult: { content?: (ImageContent | TextContent)[] } | undefined; - try { - if (waitForNetwork) - actionResult = await waitForCompletion(this, tab, async () => racingAction?.()) ?? undefined; - else - actionResult = await racingAction?.() ?? undefined; - } finally { - if (captureSnapshot && !this._javaScriptBlocked()) - await tab.captureSnapshot(); - } - - const result: string[] = []; - result.push(`- Ran Playwright code: -\`\`\`js -${code.join('\n')} -\`\`\` -`); - - if (this.modalStates().length) { - result.push(...this.modalStatesMarkdown()); - return { - content: [{ - type: 'text', - text: result.join('\n'), - }], - }; - } - - if (this._downloads.length) { - result.push('', '### Downloads'); - for (const entry of this._downloads) { - if (entry.finished) - result.push(`- Downloaded file ${entry.download.suggestedFilename()} to ${entry.outputFile}`); - else - result.push(`- Downloading file ${entry.download.suggestedFilename()} ...`); - } - result.push(''); - } - - if (this.tabs().length > 1) - result.push(await this.listTabsMarkdown(), ''); - - if (this.tabs().length > 1) - result.push('### Current tab'); - - result.push( - `- Page URL: ${tab.page.url()}`, - `- Page Title: ${await tab.title()}` - ); - - if (captureSnapshot && tab.hasSnapshot()) - result.push(tab.snapshotOrDie().text()); - - const content = actionResult?.content ?? []; - - return { - content: [ - ...content, - { - type: 'text', - text: result.join('\n'), - } - ], - }; - } - - async waitForTimeout(time: number) { - if (!this._currentTab || this._javaScriptBlocked()) { - await new Promise(f => setTimeout(f, time)); - return; - } - - await callOnPageNoTrace(this._currentTab.page, page => { - return page.evaluate(() => new Promise(f => setTimeout(f, 1000))); - }); - } - - private async _raceAgainstModalDialogs(action: () => Promise): Promise { - this._pendingAction = { - dialogShown: new ManualPromise(), - }; - - let result: ToolActionResult | undefined; - try { - await Promise.race([ - action().then(r => result = r), - this._pendingAction.dialogShown, - ]); - } finally { - this._pendingAction = undefined; - } - return result; - } - - private _javaScriptBlocked(): boolean { - return this._modalStates.some(state => state.type === 'dialog'); - } - - dialogShown(tab: Tab, dialog: playwright.Dialog) { - this.setModalState({ - type: 'dialog', - description: `"${dialog.type()}" dialog with message "${dialog.message()}"`, - dialog, - }, tab); - this._pendingAction?.dialogShown.resolve(); - } - - async downloadStarted(tab: Tab, download: playwright.Download) { - const entry = { - download, - finished: false, - outputFile: await outputFile(this.config, download.suggestedFilename()) - }; - this._downloads.push(entry); - await download.saveAs(entry.outputFile); - entry.finished = true; + async outputFile(name: string): Promise { + return outputFile(this.config, this._clientInfo.rootPath, name); } private _onPageCreated(page: playwright.Page) { @@ -278,7 +126,6 @@ ${code.join('\n')} } private _onPageClosed(tab: Tab) { - this._modalStates = this._modalStates.filter(state => state.tab !== tab); const index = this._tabs.indexOf(tab); if (index === -1) return; @@ -287,10 +134,25 @@ ${code.join('\n')} if (this._currentTab === tab) this._currentTab = this._tabs[Math.min(index, this._tabs.length - 1)]; if (!this._tabs.length) - void this.close(); + void this.closeBrowserContext(); } - async close() { + async closeBrowserContext() { + if (!this._closeBrowserContextPromise) + this._closeBrowserContextPromise = this._closeBrowserContextImpl().catch(logUnhandledError); + await this._closeBrowserContextPromise; + this._closeBrowserContextPromise = undefined; + } + + isRunningTool() { + return this._runningToolName !== undefined; + } + + setRunningTool(name: string | undefined) { + this._runningToolName = name; + } + + private async _closeBrowserContextImpl() { if (!this._browserContextPromise) return; @@ -306,6 +168,12 @@ ${code.join('\n')} }); } + async dispose() { + this._abortController.abort('MCP context disposed'); + await this.closeBrowserContext(); + Context._allContexts.delete(this); + } + private async _setupRequestInterception(context: playwright.BrowserContext) { if (this.config.network?.allowedOrigins?.length) { await context.route('**', route => route.abort('blockedbyclient')); @@ -331,10 +199,14 @@ ${code.join('\n')} } private async _setupBrowserContext(): Promise<{ browserContext: playwright.BrowserContext, close: () => Promise }> { + if (this._closeBrowserContextPromise) + throw new Error('Another browser context is being closed.'); // TODO: move to the browser context factory to make it based on isolation mode. - const result = await this._browserContextFactory.createContext(); + const result = await this._browserContextFactory.createContext(this._clientInfo, this._abortController.signal, this._runningToolName); const { browserContext } = result; await this._setupRequestInterception(browserContext); + if (this.sessionLog) + await InputRecorder.create(this, browserContext); for (const page of browserContext.pages()) this._onPageCreated(page); browserContext.on('page', page => this._onPageCreated(page)); @@ -349,3 +221,56 @@ ${code.join('\n')} return result; } } + +export class InputRecorder { + private _context: Context; + private _browserContext: playwright.BrowserContext; + + private constructor(context: Context, browserContext: playwright.BrowserContext) { + this._context = context; + this._browserContext = browserContext; + } + + static async create(context: Context, browserContext: playwright.BrowserContext) { + const recorder = new InputRecorder(context, browserContext); + await recorder._initialize(); + return recorder; + } + + private async _initialize() { + const sessionLog = this._context.sessionLog!; + await (this._browserContext as any)._enableRecorder({ + mode: 'recording', + recorderMode: 'api', + }, { + actionAdded: (page: playwright.Page, data: actions.ActionInContext, code: string) => { + if (this._context.isRunningTool()) + return; + const tab = Tab.forPage(page); + if (tab) + sessionLog.logUserAction(data.action, tab, code, false); + }, + actionUpdated: (page: playwright.Page, data: actions.ActionInContext, code: string) => { + if (this._context.isRunningTool()) + return; + const tab = Tab.forPage(page); + if (tab) + sessionLog.logUserAction(data.action, tab, code, true); + }, + signalAdded: (page: playwright.Page, data: actions.SignalInContext) => { + if (this._context.isRunningTool()) + return; + if (data.signal.name !== 'navigation') + return; + const tab = Tab.forPage(page); + const navigateAction: actions.Action = { + name: 'navigate', + url: data.signal.url, + signals: [], + }; + if (tab) + sessionLog.logUserAction(navigateAction, tab, `await page.goto('${data.signal.url}');`, false); + }, + }); + } +} diff --git a/src/extension/DEPS.list b/src/extension/DEPS.list new file mode 100644 index 0000000..796b333 --- /dev/null +++ b/src/extension/DEPS.list @@ -0,0 +1,3 @@ +[*] +../mcp/ +../utils/ diff --git a/src/extension/cdpRelay.ts b/src/extension/cdpRelay.ts new file mode 100644 index 0000000..c7791db --- /dev/null +++ b/src/extension/cdpRelay.ts @@ -0,0 +1,415 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * WebSocket server that bridges Playwright MCP and Chrome Extension + * + * Endpoints: + * - /cdp/guid - Full CDP interface for Playwright MCP + * - /extension/guid - Extension connection for chrome.debugger forwarding + */ + +import { spawn } from 'child_process'; +import http from 'http'; +import debug from 'debug'; +import { WebSocket, WebSocketServer } from 'ws'; +import { httpAddressToString } from '../mcp/http.js'; +import { logUnhandledError } from '../utils/log.js'; +import { ManualPromise } from '../mcp/manualPromise.js'; + +import type websocket from 'ws'; +import type { ClientInfo } from '../browserContextFactory.js'; + +// @ts-ignore +const { registry } = await import('playwright-core/lib/server/registry/index'); + +const debugLogger = debug('pw:mcp:relay'); + +type CDPCommand = { + id: number; + sessionId?: string; + method: string; + params?: any; +}; + +type CDPResponse = { + id?: number; + sessionId?: string; + method?: string; + params?: any; + result?: any; + error?: { code?: number; message: string }; +}; + +export class CDPRelayServer { + private _wsHost: string; + private _browserChannel: string; + private _userDataDir?: string; + private _cdpPath: string; + private _extensionPath: string; + private _wss: WebSocketServer; + private _playwrightConnection: WebSocket | null = null; + private _extensionConnection: ExtensionConnection | null = null; + private _connectedTabInfo: { + targetInfo: any; + // Page sessionId that should be used by this connection. + sessionId: string; + } | undefined; + private _nextSessionId: number = 1; + private _extensionConnectionPromise!: ManualPromise; + + constructor(server: http.Server, browserChannel: string, userDataDir?: string) { + this._wsHost = httpAddressToString(server.address()).replace(/^http/, 'ws'); + this._browserChannel = browserChannel; + this._userDataDir = userDataDir; + + const uuid = crypto.randomUUID(); + this._cdpPath = `/cdp/${uuid}`; + this._extensionPath = `/extension/${uuid}`; + + this._resetExtensionConnection(); + this._wss = new WebSocketServer({ server }); + this._wss.on('connection', this._onConnection.bind(this)); + } + + cdpEndpoint() { + return `${this._wsHost}${this._cdpPath}`; + } + + extensionEndpoint() { + return `${this._wsHost}${this._extensionPath}`; + } + + async ensureExtensionConnectionForMCPContext(clientInfo: ClientInfo, abortSignal: AbortSignal, toolName: string | undefined) { + debugLogger('Ensuring extension connection for MCP context'); + if (this._extensionConnection) + return; + this._connectBrowser(clientInfo, toolName); + debugLogger('Waiting for incoming extension connection'); + await Promise.race([ + this._extensionConnectionPromise, + new Promise((_, reject) => setTimeout(() => { + reject(new Error(`Extension connection timeout. Make sure the "Playwright MCP Bridge" extension is installed. See https://github.com/microsoft/playwright-mcp/blob/main/extension/README.md for installation instructions.`)); + }, process.env.PWMCP_TEST_CONNECTION_TIMEOUT ? parseInt(process.env.PWMCP_TEST_CONNECTION_TIMEOUT, 10) : 5_000)), + new Promise((_, reject) => abortSignal.addEventListener('abort', reject)) + ]); + debugLogger('Extension connection established'); + } + + private _connectBrowser(clientInfo: ClientInfo, toolName: string | undefined) { + const mcpRelayEndpoint = `${this._wsHost}${this._extensionPath}`; + // Need to specify "key" in the manifest.json to make the id stable when loading from file. + const url = new URL('chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/connect.html'); + url.searchParams.set('mcpRelayUrl', mcpRelayEndpoint); + const client = { + name: clientInfo.name, + version: clientInfo.version, + }; + url.searchParams.set('client', JSON.stringify(client)); + if (toolName) + url.searchParams.set('newTab', String(toolName === 'browser_navigate')); + const href = url.toString(); + const executableInfo = registry.findExecutable(this._browserChannel); + if (!executableInfo) + throw new Error(`Unsupported channel: "${this._browserChannel}"`); + const executablePath = executableInfo.executablePath(); + if (!executablePath) + throw new Error(`"${this._browserChannel}" executable not found. Make sure it is installed at a standard location.`); + + const args: string[] = []; + if (this._userDataDir) + args.push(`--user-data-dir=${this._userDataDir}`); + args.push(href); + + spawn(executablePath, args, { + windowsHide: true, + detached: true, + shell: false, + stdio: 'ignore', + }); + } + + stop(): void { + this.closeConnections('Server stopped'); + this._wss.close(); + } + + closeConnections(reason: string) { + this._closePlaywrightConnection(reason); + this._closeExtensionConnection(reason); + } + + private _onConnection(ws: WebSocket, request: http.IncomingMessage): void { + const url = new URL(`http://localhost${request.url}`); + debugLogger(`New connection to ${url.pathname}`); + if (url.pathname === this._cdpPath) { + this._handlePlaywrightConnection(ws); + } else if (url.pathname === this._extensionPath) { + this._handleExtensionConnection(ws); + } else { + debugLogger(`Invalid path: ${url.pathname}`); + ws.close(4004, 'Invalid path'); + } + } + + private _handlePlaywrightConnection(ws: WebSocket): void { + if (this._playwrightConnection) { + debugLogger('Rejecting second Playwright connection'); + ws.close(1000, 'Another CDP client already connected'); + return; + } + this._playwrightConnection = ws; + ws.on('message', async data => { + try { + const message = JSON.parse(data.toString()); + await this._handlePlaywrightMessage(message); + } catch (error: any) { + debugLogger(`Error while handling Playwright message\n${data.toString()}\n`, error); + } + }); + ws.on('close', () => { + if (this._playwrightConnection !== ws) + return; + this._playwrightConnection = null; + this._closeExtensionConnection('Playwright client disconnected'); + debugLogger('Playwright WebSocket closed'); + }); + ws.on('error', error => { + debugLogger('Playwright WebSocket error:', error); + }); + debugLogger('Playwright MCP connected'); + } + + private _closeExtensionConnection(reason: string) { + this._extensionConnection?.close(reason); + this._extensionConnectionPromise.reject(new Error(reason)); + this._resetExtensionConnection(); + } + + private _resetExtensionConnection() { + this._connectedTabInfo = undefined; + this._extensionConnection = null; + this._extensionConnectionPromise = new ManualPromise(); + void this._extensionConnectionPromise.catch(logUnhandledError); + } + + private _closePlaywrightConnection(reason: string) { + if (this._playwrightConnection?.readyState === WebSocket.OPEN) + this._playwrightConnection.close(1000, reason); + this._playwrightConnection = null; + } + + private _handleExtensionConnection(ws: WebSocket): void { + if (this._extensionConnection) { + ws.close(1000, 'Another extension connection already established'); + return; + } + this._extensionConnection = new ExtensionConnection(ws); + this._extensionConnection.onclose = (c, reason) => { + debugLogger('Extension WebSocket closed:', reason, c === this._extensionConnection); + if (this._extensionConnection !== c) + return; + this._resetExtensionConnection(); + this._closePlaywrightConnection(`Extension disconnected: ${reason}`); + }; + this._extensionConnection.onmessage = this._handleExtensionMessage.bind(this); + this._extensionConnectionPromise.resolve(); + } + + private _handleExtensionMessage(method: string, params: any) { + switch (method) { + case 'forwardCDPEvent': + const sessionId = params.sessionId || this._connectedTabInfo?.sessionId; + this._sendToPlaywright({ + sessionId, + method: params.method, + params: params.params + }); + break; + case 'detachedFromTab': + debugLogger('← Debugger detached from tab:', params); + this._connectedTabInfo = undefined; + break; + } + } + + private async _handlePlaywrightMessage(message: CDPCommand): Promise { + debugLogger('← Playwright:', `${message.method} (id=${message.id})`); + const { id, sessionId, method, params } = message; + try { + const result = await this._handleCDPCommand(method, params, sessionId); + this._sendToPlaywright({ id, sessionId, result }); + } catch (e) { + debugLogger('Error in the extension:', e); + this._sendToPlaywright({ + id, + sessionId, + error: { message: (e as Error).message } + }); + } + } + + private async _handleCDPCommand(method: string, params: any, sessionId: string | undefined): Promise { + switch (method) { + case 'Browser.getVersion': { + return { + protocolVersion: '1.3', + product: 'Chrome/Extension-Bridge', + userAgent: 'CDP-Bridge-Server/1.0.0', + }; + } + case 'Browser.setDownloadBehavior': { + return { }; + } + case 'Target.setAutoAttach': { + // Forward child session handling. + if (sessionId) + break; + // Simulate auto-attach behavior with real target info + const { targetInfo } = await this._extensionConnection!.send('attachToTab'); + this._connectedTabInfo = { + targetInfo, + sessionId: `pw-tab-${this._nextSessionId++}`, + }; + debugLogger('Simulating auto-attach'); + this._sendToPlaywright({ + method: 'Target.attachedToTarget', + params: { + sessionId: this._connectedTabInfo.sessionId, + targetInfo: { + ...this._connectedTabInfo.targetInfo, + attached: true, + }, + waitingForDebugger: false + } + }); + return { }; + } + case 'Target.getTargetInfo': { + return this._connectedTabInfo?.targetInfo; + } + } + return await this._forwardToExtension(method, params, sessionId); + } + + private async _forwardToExtension(method: string, params: any, sessionId: string | undefined): Promise { + if (!this._extensionConnection) + throw new Error('Extension not connected'); + // Top level sessionId is only passed between the relay and the client. + if (this._connectedTabInfo?.sessionId === sessionId) + sessionId = undefined; + return await this._extensionConnection.send('forwardCDPCommand', { sessionId, method, params }); + } + + private _sendToPlaywright(message: CDPResponse): void { + debugLogger('→ Playwright:', `${message.method ?? `response(id=${message.id})`}`); + this._playwrightConnection?.send(JSON.stringify(message)); + } +} + +type ExtensionResponse = { + id?: number; + method?: string; + params?: any; + result?: any; + error?: string; +}; + +class ExtensionConnection { + private readonly _ws: WebSocket; + private readonly _callbacks = new Map void, reject: (e: Error) => void, error: Error }>(); + private _lastId = 0; + + onmessage?: (method: string, params: any) => void; + onclose?: (self: ExtensionConnection, reason: string) => void; + + constructor(ws: WebSocket) { + this._ws = ws; + this._ws.on('message', this._onMessage.bind(this)); + this._ws.on('close', this._onClose.bind(this)); + this._ws.on('error', this._onError.bind(this)); + } + + async send(method: string, params?: any, sessionId?: string): Promise { + if (this._ws.readyState !== WebSocket.OPEN) + throw new Error(`Unexpected WebSocket state: ${this._ws.readyState}`); + const id = ++this._lastId; + this._ws.send(JSON.stringify({ id, method, params, sessionId })); + const error = new Error(`Protocol error: ${method}`); + return new Promise((resolve, reject) => { + this._callbacks.set(id, { resolve, reject, error }); + }); + } + + close(message: string) { + debugLogger('closing extension connection:', message); + if (this._ws.readyState === WebSocket.OPEN) + this._ws.close(1000, message); + } + + private _onMessage(event: websocket.RawData) { + const eventData = event.toString(); + let parsedJson; + try { + parsedJson = JSON.parse(eventData); + } catch (e: any) { + debugLogger(` Closing websocket due to malformed JSON. eventData=${eventData} e=${e?.message}`); + this._ws.close(); + return; + } + try { + this._handleParsedMessage(parsedJson); + } catch (e: any) { + debugLogger(` Closing websocket due to failed onmessage callback. eventData=${eventData} e=${e?.message}`); + this._ws.close(); + } + } + + private _handleParsedMessage(object: ExtensionResponse) { + if (object.id && this._callbacks.has(object.id)) { + const callback = this._callbacks.get(object.id)!; + this._callbacks.delete(object.id); + if (object.error) { + const error = callback.error; + error.message = object.error; + callback.reject(error); + } else { + callback.resolve(object.result); + } + } else if (object.id) { + debugLogger('← Extension: unexpected response', object); + } else { + this.onmessage?.(object.method!, object.params); + } + } + + private _onClose(event: websocket.CloseEvent) { + debugLogger(` code=${event.code} reason=${event.reason}`); + this._dispose(); + this.onclose?.(this, event.reason); + } + + private _onError(event: websocket.ErrorEvent) { + debugLogger(` message=${event.message} type=${event.type} target=${event.target}`); + this._dispose(); + } + + private _dispose() { + for (const callback of this._callbacks.values()) + callback.reject(new Error('WebSocket closed')); + this._callbacks.clear(); + } +} diff --git a/src/extension/extensionContextFactory.ts b/src/extension/extensionContextFactory.ts new file mode 100644 index 0000000..397aaa8 --- /dev/null +++ b/src/extension/extensionContextFactory.ts @@ -0,0 +1,63 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import debug from 'debug'; +import * as playwright from 'playwright'; +import { startHttpServer } from '../mcp/http.js'; +import { CDPRelayServer } from './cdpRelay.js'; + +import type { BrowserContextFactory, ClientInfo } from '../browserContextFactory.js'; + +const debugLogger = debug('pw:mcp:relay'); + +export class ExtensionContextFactory implements BrowserContextFactory { + private _browserChannel: string; + private _userDataDir?: string; + + constructor(browserChannel: string, userDataDir: string | undefined) { + this._browserChannel = browserChannel; + this._userDataDir = userDataDir; + } + + async createContext(clientInfo: ClientInfo, abortSignal: AbortSignal, toolName: string | undefined): Promise<{ browserContext: playwright.BrowserContext, close: () => Promise }> { + const browser = await this._obtainBrowser(clientInfo, abortSignal, toolName); + return { + browserContext: browser.contexts()[0], + close: async () => { + debugLogger('close() called for browser context'); + await browser.close(); + } + }; + } + + private async _obtainBrowser(clientInfo: ClientInfo, abortSignal: AbortSignal, toolName: string | undefined): Promise { + const relay = await this._startRelay(abortSignal); + await relay.ensureExtensionConnectionForMCPContext(clientInfo, abortSignal, toolName); + return await playwright.chromium.connectOverCDP(relay.cdpEndpoint()); + } + + private async _startRelay(abortSignal: AbortSignal) { + const httpServer = await startHttpServer({}); + if (abortSignal.aborted) { + httpServer.close(); + throw new Error(abortSignal.reason); + } + const cdpRelayServer = new CDPRelayServer(httpServer, this._browserChannel, this._userDataDir); + abortSignal.addEventListener('abort', () => cdpRelayServer.stop()); + debugLogger(`CDP relay server started, extension endpoint: ${cdpRelayServer.extensionEndpoint()}.`); + return cdpRelayServer; + } +} diff --git a/src/httpServer.ts b/src/httpServer.ts deleted file mode 100644 index 9e67bef..0000000 --- a/src/httpServer.ts +++ /dev/null @@ -1,232 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import fs from 'fs'; -import path from 'path'; -import http from 'http'; -import net from 'net'; - -import mime from 'mime'; - -import { ManualPromise } from './manualPromise.js'; - - -export type ServerRouteHandler = (request: http.IncomingMessage, response: http.ServerResponse) => void; - -export type Transport = { - sendEvent?: (method: string, params: any) => void; - close?: () => void; - onconnect: () => void; - dispatch: (method: string, params: any) => Promise; - onclose: () => void; -}; - -export class HttpServer { - private _server: http.Server; - private _urlPrefixPrecise: string = ''; - private _urlPrefixHumanReadable: string = ''; - private _port: number = 0; - private _routes: { prefix?: string, exact?: string, handler: ServerRouteHandler }[] = []; - - constructor() { - this._server = http.createServer(this._onRequest.bind(this)); - decorateServer(this._server); - } - - server() { - return this._server; - } - - routePrefix(prefix: string, handler: ServerRouteHandler) { - this._routes.push({ prefix, handler }); - } - - routePath(path: string, handler: ServerRouteHandler) { - this._routes.push({ exact: path, handler }); - } - - port(): number { - return this._port; - } - - private async _tryStart(port: number | undefined, host: string) { - const errorPromise = new ManualPromise(); - const errorListener = (error: Error) => errorPromise.reject(error); - this._server.on('error', errorListener); - - try { - this._server.listen(port, host); - await Promise.race([ - new Promise(cb => this._server!.once('listening', cb)), - errorPromise, - ]); - } finally { - this._server.removeListener('error', errorListener); - } - } - - async start(options: { port?: number, preferredPort?: number, host?: string } = {}): Promise { - const host = options.host || 'localhost'; - if (options.preferredPort) { - try { - await this._tryStart(options.preferredPort, host); - } catch (e: any) { - if (!e || !e.message || !e.message.includes('EADDRINUSE')) - throw e; - await this._tryStart(undefined, host); - } - } else { - await this._tryStart(options.port, host); - } - - const address = this._server.address(); - if (typeof address === 'string') { - this._urlPrefixPrecise = address; - this._urlPrefixHumanReadable = address; - } else { - this._port = address!.port; - const resolvedHost = address!.family === 'IPv4' ? address!.address : `[${address!.address}]`; - this._urlPrefixPrecise = `http://${resolvedHost}:${address!.port}`; - this._urlPrefixHumanReadable = `http://${host}:${address!.port}`; - } - } - - async stop() { - await new Promise(cb => this._server!.close(cb)); - } - - urlPrefix(purpose: 'human-readable' | 'precise'): string { - return purpose === 'human-readable' ? this._urlPrefixHumanReadable : this._urlPrefixPrecise; - } - - serveFile(request: http.IncomingMessage, response: http.ServerResponse, absoluteFilePath: string, headers?: { [name: string]: string }): boolean { - try { - for (const [name, value] of Object.entries(headers || {})) - response.setHeader(name, value); - if (request.headers.range) - this._serveRangeFile(request, response, absoluteFilePath); - else - this._serveFile(response, absoluteFilePath); - return true; - } catch (e) { - return false; - } - } - - _serveFile(response: http.ServerResponse, absoluteFilePath: string) { - const content = fs.readFileSync(absoluteFilePath); - response.statusCode = 200; - const contentType = mime.getType(path.extname(absoluteFilePath)) || 'application/octet-stream'; - response.setHeader('Content-Type', contentType); - response.setHeader('Content-Length', content.byteLength); - response.end(content); - } - - _serveRangeFile(request: http.IncomingMessage, response: http.ServerResponse, absoluteFilePath: string) { - const range = request.headers.range; - if (!range || !range.startsWith('bytes=') || range.includes(', ') || [...range].filter(char => char === '-').length !== 1) { - response.statusCode = 400; - return response.end('Bad request'); - } - - // Parse the range header: https://datatracker.ietf.org/doc/html/rfc7233#section-2.1 - const [startStr, endStr] = range.replace(/bytes=/, '').split('-'); - - // Both start and end (when passing to fs.createReadStream) and the range header are inclusive and start counting at 0. - let start: number; - let end: number; - const size = fs.statSync(absoluteFilePath).size; - if (startStr !== '' && endStr === '') { - // No end specified: use the whole file - start = +startStr; - end = size - 1; - } else if (startStr === '' && endStr !== '') { - // No start specified: calculate start manually - start = size - +endStr; - end = size - 1; - } else { - start = +startStr; - end = +endStr; - } - - // Handle unavailable range request - if (Number.isNaN(start) || Number.isNaN(end) || start >= size || end >= size || start > end) { - // Return the 416 Range Not Satisfiable: https://datatracker.ietf.org/doc/html/rfc7233#section-4.4 - response.writeHead(416, { - 'Content-Range': `bytes */${size}` - }); - return response.end(); - } - - // Sending Partial Content: https://datatracker.ietf.org/doc/html/rfc7233#section-4.1 - response.writeHead(206, { - 'Content-Range': `bytes ${start}-${end}/${size}`, - 'Accept-Ranges': 'bytes', - 'Content-Length': end - start + 1, - 'Content-Type': mime.getType(path.extname(absoluteFilePath))!, - }); - - const readable = fs.createReadStream(absoluteFilePath, { start, end }); - readable.pipe(response); - } - - private _onRequest(request: http.IncomingMessage, response: http.ServerResponse) { - if (request.method === 'OPTIONS') { - response.writeHead(200); - response.end(); - return; - } - - request.on('error', () => response.end()); - try { - if (!request.url) { - response.end(); - return; - } - const url = new URL('http://localhost' + request.url); - for (const route of this._routes) { - if (route.exact && url.pathname === route.exact) { - route.handler(request, response); - return; - } - if (route.prefix && url.pathname.startsWith(route.prefix)) { - route.handler(request, response); - return; - } - } - response.statusCode = 404; - response.end(); - } catch (e) { - response.end(); - } - } -} - -function decorateServer(server: net.Server) { - const sockets = new Set(); - server.on('connection', socket => { - sockets.add(socket); - socket.once('close', () => sockets.delete(socket)); - }); - - const close = server.close; - server.close = (callback?: (err?: Error) => void) => { - for (const socket of sockets) - socket.destroy(); - sockets.clear(); - return close.call(server, callback); - }; -} diff --git a/src/index.ts b/src/index.ts index 2f5f2f9..6809cd9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,22 +14,27 @@ * limitations under the License. */ -import { createConnection as createConnectionImpl } from './connection.js'; -import type { Connection } from '../index.js'; +import { BrowserServerBackend } from './browserServerBackend.js'; import { resolveConfig } from './config.js'; import { contextFactory } from './browserContextFactory.js'; +import * as mcpServer from './mcp/server.js'; +import { packageJSON } from './utils/package.js'; import type { Config } from '../config.js'; import type { BrowserContext } from 'playwright'; import type { BrowserContextFactory } from './browserContextFactory.js'; +import type { Server } from '@modelcontextprotocol/sdk/server/index.js'; -export async function createConnection(userConfig: Config = {}, contextGetter?: () => Promise): Promise { +export async function createConnection(userConfig: Config = {}, contextGetter?: () => Promise): Promise { const config = await resolveConfig(userConfig); - const factory = contextGetter ? new SimpleBrowserContextFactory(contextGetter) : contextFactory(config.browser); - return createConnectionImpl(config, factory); + const factory = contextGetter ? new SimpleBrowserContextFactory(contextGetter) : contextFactory(config); + return mcpServer.createServer('Playwright', packageJSON.version, new BrowserServerBackend(config, factory), false); } class SimpleBrowserContextFactory implements BrowserContextFactory { + name = 'custom'; + description = 'Connect to a browser using a custom context getter'; + private readonly _contextGetter: () => Promise; constructor(contextGetter: () => Promise) { diff --git a/src/loop/loop.ts b/src/loop/loop.ts new file mode 100644 index 0000000..43a8e2e --- /dev/null +++ b/src/loop/loop.ts @@ -0,0 +1,108 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import debug from 'debug'; +import type { Tool, ImageContent, TextContent } from '@modelcontextprotocol/sdk/types.js'; +import type { Client } from '@modelcontextprotocol/sdk/client/index.js'; + +export type LLMToolCall = { + name: string; + arguments: any; + id: string; +}; + +export type LLMTool = { + name: string; + description: string; + inputSchema: any; +}; + +export type LLMMessage = + | { role: 'user'; content: string } + | { role: 'assistant'; content: string; toolCalls?: LLMToolCall[] } + | { role: 'tool'; toolCallId: string; content: string; isError?: boolean }; + +export type LLMConversation = { + messages: LLMMessage[]; + tools: LLMTool[]; +}; + +export interface LLMDelegate { + createConversation(task: string, tools: Tool[], oneShot: boolean): LLMConversation; + makeApiCall(conversation: LLMConversation): Promise; + addToolResults(conversation: LLMConversation, results: Array<{ toolCallId: string; content: string; isError?: boolean }>): void; + checkDoneToolCall(toolCall: LLMToolCall): string | null; +} + +export async function runTask(delegate: LLMDelegate, client: Client, task: string, oneShot: boolean = false): Promise { + const { tools } = await client.listTools(); + const taskContent = oneShot ? `Perform following task: ${task}.` : `Perform following task: ${task}. Once the task is complete, call the "done" tool.`; + const conversation = delegate.createConversation(taskContent, tools, oneShot); + + for (let iteration = 0; iteration < 5; ++iteration) { + debug('history')('Making API call for iteration', iteration); + const toolCalls = await delegate.makeApiCall(conversation); + if (toolCalls.length === 0) + throw new Error('Call the "done" tool when the task is complete.'); + + const toolResults: Array<{ toolCallId: string; content: string; isError?: boolean }> = []; + for (const toolCall of toolCalls) { + const doneResult = delegate.checkDoneToolCall(toolCall); + if (doneResult !== null) + return conversation.messages; + + const { name, arguments: args, id } = toolCall; + try { + debug('tool')(name, args); + const response = await client.callTool({ + name, + arguments: args, + }); + const responseContent = (response.content || []) as (TextContent | ImageContent)[]; + debug('tool')(responseContent); + const text = responseContent.filter(part => part.type === 'text').map(part => part.text).join('\n'); + + toolResults.push({ + toolCallId: id, + content: text, + }); + } catch (error) { + debug('tool')(error); + toolResults.push({ + toolCallId: id, + content: `Error while executing tool "${name}": ${error instanceof Error ? error.message : String(error)}\n\nPlease try to recover and complete the task.`, + isError: true, + }); + + // Skip remaining tool calls for this iteration + for (const remainingToolCall of toolCalls.slice(toolCalls.indexOf(toolCall) + 1)) { + toolResults.push({ + toolCallId: remainingToolCall.id, + content: `This tool call is skipped due to previous error.`, + isError: true, + }); + } + break; + } + } + + delegate.addToolResults(conversation, toolResults); + if (oneShot) + return conversation.messages; + } + + throw new Error('Failed to perform step, max attempts reached'); +} diff --git a/src/loop/loopClaude.ts b/src/loop/loopClaude.ts new file mode 100644 index 0000000..92dc1d5 --- /dev/null +++ b/src/loop/loopClaude.ts @@ -0,0 +1,177 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type Anthropic from '@anthropic-ai/sdk'; +import type { LLMDelegate, LLMConversation, LLMToolCall, LLMTool } from './loop.js'; +import type { Tool } from '@modelcontextprotocol/sdk/types.js'; + +const model = 'claude-sonnet-4-20250514'; + +export class ClaudeDelegate implements LLMDelegate { + private _anthropic: Anthropic | undefined; + + async anthropic(): Promise { + if (!this._anthropic) { + const anthropic = await import('@anthropic-ai/sdk'); + this._anthropic = new anthropic.Anthropic(); + } + return this._anthropic; + } + + createConversation(task: string, tools: Tool[], oneShot: boolean): LLMConversation { + const llmTools: LLMTool[] = tools.map(tool => ({ + name: tool.name, + description: tool.description || '', + inputSchema: tool.inputSchema, + })); + + if (!oneShot) { + llmTools.push({ + name: 'done', + description: 'Call this tool when the task is complete.', + inputSchema: { + type: 'object', + properties: {}, + }, + }); + } + + return { + messages: [{ + role: 'user', + content: task + }], + tools: llmTools, + }; + } + + async makeApiCall(conversation: LLMConversation): Promise { + // Convert generic messages to Claude format + const claudeMessages: Anthropic.Messages.MessageParam[] = []; + + for (const message of conversation.messages) { + if (message.role === 'user') { + claudeMessages.push({ + role: 'user', + content: message.content + }); + } else if (message.role === 'assistant') { + const content: Anthropic.Messages.ContentBlock[] = []; + + // Add text content + if (message.content) { + content.push({ + type: 'text', + text: message.content, + citations: [] + }); + } + + // Add tool calls + if (message.toolCalls) { + for (const toolCall of message.toolCalls) { + content.push({ + type: 'tool_use', + id: toolCall.id, + name: toolCall.name, + input: toolCall.arguments + }); + } + } + + claudeMessages.push({ + role: 'assistant', + content + }); + } else if (message.role === 'tool') { + // Tool results are added differently - we need to find if there's already a user message with tool results + const lastMessage = claudeMessages[claudeMessages.length - 1]; + const toolResult: Anthropic.Messages.ToolResultBlockParam = { + type: 'tool_result', + tool_use_id: message.toolCallId, + content: message.content, + is_error: message.isError, + }; + + if (lastMessage && lastMessage.role === 'user' && Array.isArray(lastMessage.content)) { + // Add to existing tool results message + (lastMessage.content as Anthropic.Messages.ToolResultBlockParam[]).push(toolResult); + } else { + // Create new tool results message + claudeMessages.push({ + role: 'user', + content: [toolResult] + }); + } + } + } + + // Convert generic tools to Claude format + const claudeTools: Anthropic.Messages.Tool[] = conversation.tools.map(tool => ({ + name: tool.name, + description: tool.description, + input_schema: tool.inputSchema, + })); + + const anthropic = await this.anthropic(); + const response = await anthropic.messages.create({ + model, + max_tokens: 10000, + messages: claudeMessages, + tools: claudeTools, + }); + + // Extract tool calls and add assistant message to generic conversation + const toolCalls = response.content.filter(block => block.type === 'tool_use') as Anthropic.Messages.ToolUseBlock[]; + const textContent = response.content.filter(block => block.type === 'text').map(block => (block as Anthropic.Messages.TextBlock).text).join(''); + + const llmToolCalls: LLMToolCall[] = toolCalls.map(toolCall => ({ + name: toolCall.name, + arguments: toolCall.input as any, + id: toolCall.id, + })); + + // Add assistant message to generic conversation + conversation.messages.push({ + role: 'assistant', + content: textContent, + toolCalls: llmToolCalls.length > 0 ? llmToolCalls : undefined + }); + + return llmToolCalls; + } + + addToolResults( + conversation: LLMConversation, + results: Array<{ toolCallId: string; content: string; isError?: boolean }> + ): void { + for (const result of results) { + conversation.messages.push({ + role: 'tool', + toolCallId: result.toolCallId, + content: result.content, + isError: result.isError, + }); + } + } + + checkDoneToolCall(toolCall: LLMToolCall): string | null { + if (toolCall.name === 'done') + return (toolCall.arguments as { result: string }).result; + + return null; + } +} diff --git a/src/loop/loopOpenAI.ts b/src/loop/loopOpenAI.ts new file mode 100644 index 0000000..81e2ef2 --- /dev/null +++ b/src/loop/loopOpenAI.ts @@ -0,0 +1,168 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type OpenAI from 'openai'; +import type { LLMDelegate, LLMConversation, LLMToolCall, LLMTool } from './loop.js'; +import type { Tool } from '@modelcontextprotocol/sdk/types.js'; + +const model = 'gpt-4.1'; + +export class OpenAIDelegate implements LLMDelegate { + private _openai: OpenAI | undefined; + + async openai(): Promise { + if (!this._openai) { + const oai = await import('openai'); + this._openai = new oai.OpenAI(); + } + return this._openai; + } + + createConversation(task: string, tools: Tool[], oneShot: boolean): LLMConversation { + const genericTools: LLMTool[] = tools.map(tool => ({ + name: tool.name, + description: tool.description || '', + inputSchema: tool.inputSchema, + })); + + if (!oneShot) { + genericTools.push({ + name: 'done', + description: 'Call this tool when the task is complete.', + inputSchema: { + type: 'object', + properties: {}, + }, + }); + } + + return { + messages: [{ + role: 'user', + content: task + }], + tools: genericTools, + }; + } + + async makeApiCall(conversation: LLMConversation): Promise { + // Convert generic messages to OpenAI format + const openaiMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = []; + + for (const message of conversation.messages) { + if (message.role === 'user') { + openaiMessages.push({ + role: 'user', + content: message.content + }); + } else if (message.role === 'assistant') { + const toolCalls: OpenAI.Chat.Completions.ChatCompletionMessageToolCall[] = []; + + if (message.toolCalls) { + for (const toolCall of message.toolCalls) { + toolCalls.push({ + id: toolCall.id, + type: 'function', + function: { + name: toolCall.name, + arguments: JSON.stringify(toolCall.arguments) + } + }); + } + } + + const assistantMessage: OpenAI.Chat.Completions.ChatCompletionAssistantMessageParam = { + role: 'assistant' + }; + + if (message.content) + assistantMessage.content = message.content; + + if (toolCalls.length > 0) + assistantMessage.tool_calls = toolCalls; + + openaiMessages.push(assistantMessage); + } else if (message.role === 'tool') { + openaiMessages.push({ + role: 'tool', + tool_call_id: message.toolCallId, + content: message.content, + }); + } + } + + // Convert generic tools to OpenAI format + const openaiTools: OpenAI.Chat.Completions.ChatCompletionTool[] = conversation.tools.map(tool => ({ + type: 'function', + function: { + name: tool.name, + description: tool.description, + parameters: tool.inputSchema, + }, + })); + + const openai = await this.openai(); + const response = await openai.chat.completions.create({ + model, + messages: openaiMessages, + tools: openaiTools, + tool_choice: 'auto' + }); + + const message = response.choices[0].message; + + // Extract tool calls and add assistant message to generic conversation + const toolCalls = message.tool_calls || []; + const genericToolCalls: LLMToolCall[] = toolCalls.map(toolCall => { + const functionCall = toolCall.function; + return { + name: functionCall.name, + arguments: JSON.parse(functionCall.arguments), + id: toolCall.id, + }; + }); + + // Add assistant message to generic conversation + conversation.messages.push({ + role: 'assistant', + content: message.content || '', + toolCalls: genericToolCalls.length > 0 ? genericToolCalls : undefined + }); + + return genericToolCalls; + } + + addToolResults( + conversation: LLMConversation, + results: Array<{ toolCallId: string; content: string; isError?: boolean }> + ): void { + for (const result of results) { + conversation.messages.push({ + role: 'tool', + toolCallId: result.toolCallId, + content: result.content, + isError: result.isError, + }); + } + } + + checkDoneToolCall(toolCall: LLMToolCall): string | null { + if (toolCall.name === 'done') + return toolCall.arguments.result; + + return null; + } +} diff --git a/src/loop/main.ts b/src/loop/main.ts new file mode 100644 index 0000000..b909d4b --- /dev/null +++ b/src/loop/main.ts @@ -0,0 +1,72 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable no-console */ + +import path from 'path'; +import url from 'url'; +import dotenv from 'dotenv'; + +import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { program } from 'commander'; +import { OpenAIDelegate } from './loopOpenAI.js'; +import { ClaudeDelegate } from './loopClaude.js'; +import { runTask } from './loop.js'; + +import type { LLMDelegate } from './loop.js'; + +dotenv.config(); + +const __filename = url.fileURLToPath(import.meta.url); + +async function run(delegate: LLMDelegate) { + const transport = new StdioClientTransport({ + command: 'node', + args: [ + path.resolve(__filename, '../../../cli.js'), + '--save-session', + '--output-dir', path.resolve(__filename, '../../../sessions') + ], + stderr: 'inherit', + env: process.env as Record, + }); + + const client = new Client({ name: 'test', version: '1.0.0' }); + await client.connect(transport); + await client.ping(); + + for (const task of tasks) { + const messages = await runTask(delegate, client, task); + for (const message of messages) + console.log(`${message.role}: ${message.content}`); + } + await client.close(); +} + +const tasks = [ + 'Open https://playwright.dev/', +]; + +program + .option('--model ', 'model to use') + .action(async options => { + if (options.model === 'claude') + await run(new ClaudeDelegate()); + else + await run(new OpenAIDelegate()); + }); +void program.parseAsync(process.argv); diff --git a/src/loopTools/DEPS.list b/src/loopTools/DEPS.list new file mode 100644 index 0000000..5cf594d --- /dev/null +++ b/src/loopTools/DEPS.list @@ -0,0 +1,5 @@ +[*] +../ +../loop/ +../mcp/ +../utils/ diff --git a/src/loopTools/context.ts b/src/loopTools/context.ts new file mode 100644 index 0000000..dadb6b9 --- /dev/null +++ b/src/loopTools/context.ts @@ -0,0 +1,78 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { contextFactory } from '../browserContextFactory.js'; +import { BrowserServerBackend } from '../browserServerBackend.js'; +import { Context as BrowserContext } from '../context.js'; +import { runTask } from '../loop/loop.js'; +import { OpenAIDelegate } from '../loop/loopOpenAI.js'; +import { ClaudeDelegate } from '../loop/loopClaude.js'; +import { InProcessTransport } from '../mcp/inProcessTransport.js'; +import * as mcpServer from '../mcp/server.js'; +import { packageJSON } from '../utils/package.js'; + +import type { LLMDelegate } from '../loop/loop.js'; +import type { FullConfig } from '../config.js'; + +export class Context { + readonly config: FullConfig; + private _client: Client; + private _delegate: LLMDelegate; + + constructor(config: FullConfig, client: Client) { + this.config = config; + this._client = client; + if (process.env.OPENAI_API_KEY) + this._delegate = new OpenAIDelegate(); + else if (process.env.ANTHROPIC_API_KEY) + this._delegate = new ClaudeDelegate(); + else + throw new Error('No LLM API key found. Please set OPENAI_API_KEY or ANTHROPIC_API_KEY environment variable.'); + } + + static async create(config: FullConfig) { + const client = new Client({ name: 'Playwright Proxy', version: packageJSON.version }); + const browserContextFactory = contextFactory(config); + const server = mcpServer.createServer('Playwright Subagent', packageJSON.version, new BrowserServerBackend(config, browserContextFactory), false); + await client.connect(new InProcessTransport(server)); + await client.ping(); + return new Context(config, client); + } + + async runTask(task: string, oneShot: boolean = false): Promise { + const messages = await runTask(this._delegate, this._client!, task, oneShot); + const lines: string[] = []; + + // Skip the first message, which is the user's task. + for (const message of messages.slice(1)) { + // Trim out all page snapshots. + if (!message.content.trim()) + continue; + const index = oneShot ? -1 : message.content.indexOf('### Page state'); + const trimmedContent = index === -1 ? message.content : message.content.substring(0, index); + lines.push(`[${message.role}]:`, trimmedContent); + } + + return { + content: [{ type: 'text', text: lines.join('\n') }], + }; + } + + async close() { + await BrowserContext.disposeAll(); + } +} diff --git a/src/loopTools/main.ts b/src/loopTools/main.ts new file mode 100644 index 0000000..2d017ba --- /dev/null +++ b/src/loopTools/main.ts @@ -0,0 +1,67 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import dotenv from 'dotenv'; + +import * as mcpServer from '../mcp/server.js'; +import { packageJSON } from '../utils/package.js'; +import { Context } from './context.js'; +import { perform } from './perform.js'; +import { snapshot } from './snapshot.js'; +import { toMcpTool } from '../mcp/tool.js'; + +import type { FullConfig } from '../config.js'; +import type { ServerBackend } from '../mcp/server.js'; +import type { Tool } from './tool.js'; + +export async function runLoopTools(config: FullConfig) { + dotenv.config(); + const serverBackendFactory = { + name: 'Playwright', + nameInConfig: 'playwright-loop', + version: packageJSON.version, + create: () => new LoopToolsServerBackend(config) + }; + await mcpServer.start(serverBackendFactory, config.server); +} + +class LoopToolsServerBackend implements ServerBackend { + private _config: FullConfig; + private _context: Context | undefined; + private _tools: Tool[] = [perform, snapshot]; + + constructor(config: FullConfig) { + this._config = config; + } + + async initialize() { + this._context = await Context.create(this._config); + } + + async listTools(): Promise { + return this._tools.map(tool => toMcpTool(tool.schema)); + } + + async callTool(name: string, args: mcpServer.CallToolRequest['params']['arguments']): Promise { + const tool = this._tools.find(tool => tool.schema.name === name)!; + const parsedArguments = tool.schema.inputSchema.parse(args || {}); + return await tool.handle(this._context!, parsedArguments); + } + + serverClosed() { + void this._context!.close(); + } +} diff --git a/src/loopTools/perform.ts b/src/loopTools/perform.ts new file mode 100644 index 0000000..29df7b4 --- /dev/null +++ b/src/loopTools/perform.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { z } from 'zod'; +import { defineTool } from './tool.js'; + +const performSchema = z.object({ + task: z.string().describe('The task to perform with the browser'), +}); + +export const perform = defineTool({ + schema: { + name: 'browser_perform', + title: 'Perform a task with the browser', + description: 'Perform a task with the browser. It can click, type, export, capture screenshot, drag, hover, select options, etc.', + inputSchema: performSchema, + type: 'destructive', + }, + + handle: async (context, params) => { + return await context.runTask(params.task); + }, +}); diff --git a/src/loopTools/snapshot.ts b/src/loopTools/snapshot.ts new file mode 100644 index 0000000..1b0f227 --- /dev/null +++ b/src/loopTools/snapshot.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { z } from 'zod'; +import { defineTool } from './tool.js'; + +export const snapshot = defineTool({ + schema: { + name: 'browser_snapshot', + title: 'Take a snapshot of the browser', + description: 'Take a snapshot of the browser to read what is on the page.', + inputSchema: z.object({}), + type: 'readOnly', + }, + + handle: async (context, params) => { + return await context.runTask('Capture browser snapshot', true); + }, +}); diff --git a/src/loopTools/tool.ts b/src/loopTools/tool.ts new file mode 100644 index 0000000..8ea56aa --- /dev/null +++ b/src/loopTools/tool.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { z } from 'zod'; +import type * as mcpServer from '../mcp/server.js'; +import type { Context } from './context.js'; +import type { ToolSchema } from '../mcp/tool.js'; + + +export type Tool = { + schema: ToolSchema; + handle: (context: Context, params: z.output) => Promise; +}; + +export function defineTool(tool: Tool): Tool { + return tool; +} diff --git a/src/mcp/DEPS.list b/src/mcp/DEPS.list new file mode 100644 index 0000000..e43dcb5 --- /dev/null +++ b/src/mcp/DEPS.list @@ -0,0 +1 @@ +[*] diff --git a/src/mcp/README.md b/src/mcp/README.md new file mode 100644 index 0000000..b8b280e --- /dev/null +++ b/src/mcp/README.md @@ -0,0 +1 @@ +- Generic MCP utils, no dependencies on anything. diff --git a/src/transport.ts b/src/mcp/http.ts similarity index 60% rename from src/transport.ts rename to src/mcp/http.ts index 2342fe9..7cebc07 100644 --- a/src/transport.ts +++ b/src/mcp/http.ts @@ -14,25 +14,63 @@ * limitations under the License. */ -import http from 'node:http'; -import assert from 'node:assert'; -import crypto from 'node:crypto'; +import assert from 'assert'; +import net from 'net'; +import http from 'http'; +import crypto from 'crypto'; import debug from 'debug'; + import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import * as mcpServer from './server.js'; + +import type { ServerBackendFactory } from './server.js'; -import type { AddressInfo } from 'node:net'; -import type { Server } from './server.js'; +const testDebug = debug('pw:mcp:test'); -export async function startStdioTransport(server: Server) { - await server.createConnection(new StdioServerTransport()); +export async function startHttpServer(config: { host?: string, port?: number }, abortSignal?: AbortSignal): Promise { + const { host, port } = config; + const httpServer = http.createServer(); + decorateServer(httpServer); + await new Promise((resolve, reject) => { + httpServer.on('error', reject); + abortSignal?.addEventListener('abort', () => { + httpServer.close(); + reject(new Error('Aborted')); + }); + httpServer.listen(port, host, () => { + resolve(); + httpServer.removeListener('error', reject); + }); + }); + return httpServer; } -const testDebug = debug('pw:mcp:test'); +export function httpAddressToString(address: string | net.AddressInfo | null): string { + assert(address, 'Could not bind server socket'); + if (typeof address === 'string') + return address; + const resolvedPort = address.port; + let resolvedHost = address.family === 'IPv4' ? address.address : `[${address.address}]`; + if (resolvedHost === '0.0.0.0' || resolvedHost === '[::]') + resolvedHost = 'localhost'; + return `http://${resolvedHost}:${resolvedPort}`; +} + +export async function installHttpTransport(httpServer: http.Server, serverBackendFactory: ServerBackendFactory) { + const sseSessions = new Map(); + const streamableSessions = new Map(); + httpServer.on('request', async (req, res) => { + const url = new URL(`http://localhost${req.url}`); + if (url.pathname.startsWith('/sse')) + await handleSSE(serverBackendFactory, req, res, url, sseSessions); + else + await handleStreamable(serverBackendFactory, req, res, streamableSessions); + }); +} -async function handleSSE(server: Server, req: http.IncomingMessage, res: http.ServerResponse, url: URL, sessions: Map