diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index 3c06cfd..0000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "extends": ["./eslint.js"],
- "env": {
- "node": true,
- "browser": true
- },
- "rules": {
- "no-constant-condition": "off"
- }
-}
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000..a4df793
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,56 @@
+name: Linter & Types
+
+on:
+ pull_request:
+ branches:
+ - main
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [20.x]
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 2
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v3
+ with:
+ version: 9
+ run_install: false
+
+ - name: Get pnpm store directory
+ shell: bash
+ run: |
+ echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
+
+ - name: Setup pnpm cache
+ uses: actions/cache@v3
+ with:
+ path: ${{ env.STORE_PATH }}
+ key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
+ restore-keys: |
+ ${{ runner.os }}-pnpm-store-
+
+ - name: Install Node.js dependencies
+ run: pnpm install
+
+ - name: Prettier
+ run: pnpm prettier:check
+
+ - name: Node.js Lint
+ run: pnpm lint
+
+ - name: TypeScript
+ run: pnpm tc
+
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..aec8d47
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,50 @@
+name: Tests
+
+on:
+ pull_request:
+ branches:
+ - main
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ node-version: [20.x]
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 2
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v3
+ with:
+ version: 9
+ run_install: false
+
+ - name: Get pnpm store directory
+ shell: bash
+ run: |
+ echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
+
+ - name: Setup pnpm cache
+ uses: actions/cache@v3
+ with:
+ path: ${{ env.STORE_PATH }}
+ key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
+ restore-keys: |
+ ${{ runner.os }}-pnpm-store-
+
+ - name: Install Node.js dependencies
+ run: pnpm install
+
+ - name: Node.js Test
+ env:
+ NODE_ENV: test
+ run: pnpm test
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..80df6d1
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,4 @@
+.*
+dist
+build
+**/tests/fixtures
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..d0895a2
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "semi": false,
+ "jsxSingleQuote": true,
+ "singleQuote": true,
+ "trailingComma": "all"
+}
diff --git a/README.md b/README.md
index 61e08f6..291ddf6 100644
--- a/README.md
+++ b/README.md
@@ -50,4 +50,4 @@ This is just a small example of what PromptL can do. It is a powerful tool that
## Links
-[Website](https://promptl.ai/) | [Documentation](https://docs.latitude.so/promptl/getting-started/introduction)
\ No newline at end of file
+[Website](https://promptl.ai/) | [Documentation](https://docs.latitude.so/promptl/getting-started/introduction)
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 0000000..3f397e1
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,63 @@
+import typescriptEslintEslintPlugin from '@typescript-eslint/eslint-plugin'
+import prettier from 'eslint-plugin-prettier'
+import globals from 'globals'
+import tsParser from '@typescript-eslint/parser'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+import js from '@eslint/js'
+import { FlatCompat } from '@eslint/eslintrc'
+
+const __filename = fileURLToPath(import.meta.url)
+const __dirname = path.dirname(__filename)
+const compat = new FlatCompat({
+ baseDirectory: __dirname,
+ recommendedConfig: js.configs.recommended,
+ allConfig: js.configs.all,
+})
+
+export default [
+ {
+ ignores: ['**/.*.js', '**/node_modules/', '**/dist/'],
+ },
+ ...compat.extends('eslint:recommended'),
+ {
+ plugins: {
+ '@typescript-eslint': typescriptEslintEslintPlugin,
+ 'prettier': prettier,
+ },
+
+ languageOptions: {
+ globals: {
+ ...globals.node,
+ ...globals.browser,
+ },
+
+ parser: tsParser,
+ },
+
+ settings: {
+ 'import/resolver': {
+ typescript: {
+ project: './tsconfig.json',
+ },
+ },
+ },
+
+ rules: {
+ 'no-constant-condition': 'off',
+ 'no-unused-vars': 'off',
+
+ '@typescript-eslint/no-unused-vars': [
+ 'error',
+ {
+ args: 'all',
+ argsIgnorePattern: '^_',
+ varsIgnorePattern: '^_',
+ },
+ ],
+ },
+ },
+ {
+ files: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'],
+ },
+]
diff --git a/eslint.js b/eslint.js
deleted file mode 100644
index 53e546b..0000000
--- a/eslint.js
+++ /dev/null
@@ -1,39 +0,0 @@
-const { resolve } = require('node:path')
-
-const project = resolve(process.cwd(), 'tsconfig.json')
-
-/** @type {import("eslint").Linter.Config} */
-module.exports = {
- extends: ['eslint:recommended', 'prettier', 'eslint-config-turbo'],
- plugins: ['@typescript-eslint/eslint-plugin'],
- parser: '@typescript-eslint/parser',
- settings: {
- 'import/resolver': {
- typescript: {
- project,
- },
- },
- },
- ignorePatterns: [
- // Ignore dotfiles
- '.*.js',
- 'node_modules/',
- 'dist/',
- ],
- overrides: [
- {
- files: ['*.js?(x)', '*.ts?(x)'],
- },
- ],
- rules: {
- 'no-unused-vars': 'off',
- '@typescript-eslint/no-unused-vars': [
- 'error',
- {
- args: 'all',
- argsIgnorePattern: '^_',
- varsIgnorePattern: '^_',
- },
- ],
- },
-}
diff --git a/package.json b/package.json
index 0852f15..2c5b21b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "promptl-ai",
- "version": "0.3.5",
+ "version": "0.4.5",
"author": "Latitude Data",
"license": "MIT",
"description": "Compiler for PromptL, the prompt language",
@@ -28,6 +28,7 @@
"test": "vitest run",
"test:watch": "vitest",
"prettier": "prettier --write src/**/*.ts",
+ "prettier:check": "prettier --check src/**/*.ts --ignore-path .prettierrcignore",
"lint": "eslint src",
"tc": "tsc --noEmit"
},
@@ -39,10 +40,17 @@
"zod": "^3.23.8"
},
"devDependencies": {
+ "eslint": "^9.17.0",
+ "@eslint/eslintrc": "^3.2.0",
+ "@eslint/js": "^9.17.0",
"@rollup/plugin-alias": "^5.1.0",
"@rollup/plugin-typescript": "^11.1.6",
"@types/estree": "^1.0.1",
"@types/node": "^20.12.12",
+ "@typescript-eslint/eslint-plugin": "^8.19.0",
+ "eslint-plugin-prettier": "^5.2.1",
+ "globals": "^15.14.0",
+ "prettier": "^3.4.2",
"rollup": "^4.10.0",
"rollup-plugin-dts": "^6.1.1",
"tslib": "^2.8.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7b651cc..cb43c31 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -24,6 +24,12 @@ importers:
specifier: ^3.23.8
version: 3.23.8
devDependencies:
+ '@eslint/eslintrc':
+ specifier: ^3.2.0
+ version: 3.2.0
+ '@eslint/js':
+ specifier: ^9.17.0
+ version: 9.17.0
'@rollup/plugin-alias':
specifier: ^5.1.0
version: 5.1.1(rollup@4.27.4)
@@ -36,6 +42,21 @@ importers:
'@types/node':
specifier: ^20.12.12
version: 20.17.8
+ '@typescript-eslint/eslint-plugin':
+ specifier: ^8.19.0
+ version: 8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0)(typescript@5.7.2))(eslint@9.17.0)(typescript@5.7.2)
+ eslint:
+ specifier: ^9.17.0
+ version: 9.17.0
+ eslint-plugin-prettier:
+ specifier: ^5.2.1
+ version: 5.2.1(eslint@9.17.0)(prettier@3.4.2)
+ globals:
+ specifier: ^15.14.0
+ version: 15.14.0
+ prettier:
+ specifier: ^3.4.2
+ version: 3.4.2
rollup:
specifier: ^4.10.0
version: 4.27.4
@@ -347,6 +368,60 @@ packages:
cpu: [x64]
os: [win32]
+ '@eslint-community/eslint-utils@4.4.1':
+ resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+ '@eslint-community/regexpp@4.12.1':
+ resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ '@eslint/config-array@0.19.1':
+ resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/core@0.9.1':
+ resolution: {integrity: sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/eslintrc@3.2.0':
+ resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/js@9.17.0':
+ resolution: {integrity: sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/object-schema@2.1.5':
+ resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/plugin-kit@0.2.4':
+ resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@humanfs/core@0.19.1':
+ resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/node@0.16.6':
+ resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanwhocodes/module-importer@1.0.1':
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+
+ '@humanwhocodes/retry@0.3.1':
+ resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
+ engines: {node: '>=18.18'}
+
+ '@humanwhocodes/retry@0.4.1':
+ resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==}
+ engines: {node: '>=18.18'}
+
'@jest/schemas@29.6.3':
resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -354,6 +429,22 @@ packages:
'@jridgewell/sourcemap-codec@1.5.0':
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
+ '@nodelib/fs.scandir@2.1.5':
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.stat@2.0.5':
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.walk@1.2.8':
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+
+ '@pkgr/core@0.1.1':
+ resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==}
+ engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+
'@rollup/plugin-alias@5.1.1':
resolution: {integrity: sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==}
engines: {node: '>=14.0.0'}
@@ -481,9 +572,59 @@ packages:
'@types/estree@1.0.6':
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
'@types/node@20.17.8':
resolution: {integrity: sha512-ahz2g6/oqbKalW9sPv6L2iRbhLnojxjYWspAqhjvqSWBgGebEJT5GvRmk0QXPj3sbC6rU0GTQjPLQkmR8CObvA==}
+ '@typescript-eslint/eslint-plugin@8.19.0':
+ resolution: {integrity: sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.8.0'
+
+ '@typescript-eslint/parser@8.19.0':
+ resolution: {integrity: sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.8.0'
+
+ '@typescript-eslint/scope-manager@8.19.0':
+ resolution: {integrity: sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/type-utils@8.19.0':
+ resolution: {integrity: sha512-TZs0I0OSbd5Aza4qAMpp1cdCYVnER94IziudE3JU328YUHgWu9gwiwhag+fuLeJ2LkWLXI+F/182TbG+JaBdTg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.8.0'
+
+ '@typescript-eslint/types@8.19.0':
+ resolution: {integrity: sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/typescript-estree@8.19.0':
+ resolution: {integrity: sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <5.8.0'
+
+ '@typescript-eslint/utils@8.19.0':
+ resolution: {integrity: sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.8.0'
+
+ '@typescript-eslint/visitor-keys@8.19.0':
+ resolution: {integrity: sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@vitest/expect@1.6.0':
resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==}
@@ -499,6 +640,11 @@ packages:
'@vitest/utils@1.6.0':
resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==}
+ acorn-jsx@5.3.2:
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
acorn-walk@8.3.4:
resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
engines: {node: '>=0.4.0'}
@@ -508,27 +654,68 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
+ ajv@6.12.6:
+ resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
ansi-styles@5.2.0:
resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
engines: {node: '>=10'}
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
assertion-error@1.1.0:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ brace-expansion@1.1.11:
+ resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+
+ brace-expansion@2.0.1:
+ resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
+ callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
chai@4.5.0:
resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==}
engines: {node: '>=4'}
+ chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+
check-error@1.0.3:
resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
code-red@1.0.4:
resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==}
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
confbox@0.1.8:
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
@@ -549,6 +736,9 @@ packages:
resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==}
engines: {node: '>=6'}
+ deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
diff-sequences@29.6.3:
resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -563,16 +753,114 @@ packages:
engines: {node: '>=18'}
hasBin: true
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ eslint-plugin-prettier@5.2.1:
+ resolution: {integrity: sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ '@types/eslint': '>=8.0.0'
+ eslint: '>=8.0.0'
+ eslint-config-prettier: '*'
+ prettier: '>=3.0.0'
+ peerDependenciesMeta:
+ '@types/eslint':
+ optional: true
+ eslint-config-prettier:
+ optional: true
+
+ eslint-scope@8.2.0:
+ resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ eslint-visitor-keys@4.2.0:
+ resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint@9.17.0:
+ resolution: {integrity: sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ hasBin: true
+ peerDependencies:
+ jiti: '*'
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+
+ espree@10.3.0:
+ resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ esquery@1.6.0:
+ resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
+ engines: {node: '>=0.10'}
+
+ esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
+
+ estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
+
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+ esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+
execa@8.0.1:
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
engines: {node: '>=16.17'}
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-diff@1.3.0:
+ resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
+
+ fast-glob@3.3.2:
+ resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
+ engines: {node: '>=8.6.0'}
+
+ fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+ fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+ fastq@1.18.0:
+ resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==}
+
+ file-entry-cache@8.0.0:
+ resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+ engines: {node: '>=16.0.0'}
+
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+
+ find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+
+ flat-cache@4.0.1:
+ resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+ engines: {node: '>=16'}
+
+ flatted@3.3.2:
+ resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==}
+
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -591,6 +879,29 @@ packages:
get-tsconfig@4.8.1:
resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==}
+ glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+
+ glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+
+ globals@14.0.0:
+ resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+ engines: {node: '>=18'}
+
+ globals@15.14.0:
+ resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==}
+ engines: {node: '>=18'}
+
+ graphemer@1.4.0:
+ resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
+
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
@@ -599,10 +910,34 @@ packages:
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
engines: {node: '>=16.17.0'}
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
+
+ import-fresh@3.3.0:
+ resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
+ engines: {node: '>=6'}
+
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
is-core-module@2.15.1:
resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
engines: {node: '>= 0.4'}
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
is-reference@3.0.3:
resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==}
@@ -619,6 +954,26 @@ packages:
js-tokens@9.0.1:
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
+ js-yaml@4.1.0:
+ resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+ hasBin: true
+
+ json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+ json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+ json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+ keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+ levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+
local-pkg@0.5.1:
resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==}
engines: {node: '>=14'}
@@ -626,6 +981,13 @@ packages:
locate-character@3.0.0:
resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
+ locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+
+ lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
loupe@2.3.7:
resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
@@ -635,10 +997,25 @@ packages:
merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
mimic-fn@4.0.0:
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
engines: {node: '>=12'}
+ minimatch@3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+ minimatch@9.0.5:
+ resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
mlly@1.7.3:
resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==}
@@ -650,6 +1027,9 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
+ natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
npm-run-path@5.3.0:
resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -658,10 +1038,30 @@ packages:
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
engines: {node: '>=12'}
+ optionator@0.9.4:
+ resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+ engines: {node: '>= 0.8.0'}
+
+ p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+
p-limit@5.0.0:
resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==}
engines: {node: '>=18'}
+ p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+
+ parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+
+ path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
@@ -685,6 +1085,10 @@ packages:
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+ picomatch@2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+
picomatch@4.0.2:
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
engines: {node: '>=12'}
@@ -696,13 +1100,37 @@ packages:
resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==}
engines: {node: ^10 || ^12 || >=14}
+ prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+
+ prettier-linter-helpers@1.0.0:
+ resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
+ engines: {node: '>=6.0.0'}
+
+ prettier@3.4.2:
+ resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==}
+ engines: {node: '>=14'}
+ hasBin: true
+
pretty-format@29.7.0:
resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+ resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
@@ -710,6 +1138,10 @@ packages:
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
hasBin: true
+ reusify@1.0.4:
+ resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
rollup-plugin-dts@6.1.1:
resolution: {integrity: sha512-aSHRcJ6KG2IHIioYlvAOcEq6U99sVtqDDKVhnwt70rW6tsz3tv5OSjEiWcgzfsHdLyGXZ/3b/7b/+Za3Y6r1XA==}
engines: {node: '>=16'}
@@ -722,6 +1154,14 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
+ run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+ semver@7.6.3:
+ resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
+ engines: {node: '>=10'}
+ hasBin: true
+
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@@ -751,13 +1191,25 @@ packages:
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
engines: {node: '>=12'}
+ strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+
strip-literal@2.1.1:
resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==}
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+
supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
+ synckit@0.9.2:
+ resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
@@ -769,6 +1221,16 @@ packages:
resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==}
engines: {node: '>=14.0.0'}
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ ts-api-utils@1.4.3:
+ resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==}
+ engines: {node: '>=16'}
+ peerDependencies:
+ typescript: '>=4.2.0'
+
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
@@ -777,6 +1239,10 @@ packages:
engines: {node: '>=18.0.0'}
hasBin: true
+ type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+
type-detect@4.1.0:
resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==}
engines: {node: '>=4'}
@@ -792,6 +1258,9 @@ packages:
undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
+ uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
vite-node@1.6.0:
resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==}
engines: {node: ^18.0.0 || >=20.0.0}
@@ -863,11 +1332,19 @@ packages:
engines: {node: '>=8'}
hasBin: true
+ word-wrap@1.2.5:
+ resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+ engines: {node: '>=0.10.0'}
+
yaml@2.6.1:
resolution: {integrity: sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==}
engines: {node: '>= 14'}
hasBin: true
+ yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+
yocto-queue@1.1.1:
resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==}
engines: {node: '>=12.20'}
@@ -1028,12 +1505,80 @@ snapshots:
'@esbuild/win32-x64@0.23.1':
optional: true
+ '@eslint-community/eslint-utils@4.4.1(eslint@9.17.0)':
+ dependencies:
+ eslint: 9.17.0
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/regexpp@4.12.1': {}
+
+ '@eslint/config-array@0.19.1':
+ dependencies:
+ '@eslint/object-schema': 2.1.5
+ debug: 4.3.7
+ minimatch: 3.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/core@0.9.1':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
+ '@eslint/eslintrc@3.2.0':
+ dependencies:
+ ajv: 6.12.6
+ debug: 4.3.7
+ espree: 10.3.0
+ globals: 14.0.0
+ ignore: 5.3.2
+ import-fresh: 3.3.0
+ js-yaml: 4.1.0
+ minimatch: 3.1.2
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/js@9.17.0': {}
+
+ '@eslint/object-schema@2.1.5': {}
+
+ '@eslint/plugin-kit@0.2.4':
+ dependencies:
+ levn: 0.4.1
+
+ '@humanfs/core@0.19.1': {}
+
+ '@humanfs/node@0.16.6':
+ dependencies:
+ '@humanfs/core': 0.19.1
+ '@humanwhocodes/retry': 0.3.1
+
+ '@humanwhocodes/module-importer@1.0.1': {}
+
+ '@humanwhocodes/retry@0.3.1': {}
+
+ '@humanwhocodes/retry@0.4.1': {}
+
'@jest/schemas@29.6.3':
dependencies:
'@sinclair/typebox': 0.27.8
'@jridgewell/sourcemap-codec@1.5.0': {}
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.stat@2.0.5': {}
+
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.18.0
+
+ '@pkgr/core@0.1.1': {}
+
'@rollup/plugin-alias@5.1.1(rollup@4.27.4)':
optionalDependencies:
rollup: 4.27.4
@@ -1113,10 +1658,89 @@ snapshots:
'@types/estree@1.0.6': {}
+ '@types/json-schema@7.0.15': {}
+
'@types/node@20.17.8':
dependencies:
undici-types: 6.19.8
+ '@typescript-eslint/eslint-plugin@8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0)(typescript@5.7.2))(eslint@9.17.0)(typescript@5.7.2)':
+ dependencies:
+ '@eslint-community/regexpp': 4.12.1
+ '@typescript-eslint/parser': 8.19.0(eslint@9.17.0)(typescript@5.7.2)
+ '@typescript-eslint/scope-manager': 8.19.0
+ '@typescript-eslint/type-utils': 8.19.0(eslint@9.17.0)(typescript@5.7.2)
+ '@typescript-eslint/utils': 8.19.0(eslint@9.17.0)(typescript@5.7.2)
+ '@typescript-eslint/visitor-keys': 8.19.0
+ eslint: 9.17.0
+ graphemer: 1.4.0
+ ignore: 5.3.2
+ natural-compare: 1.4.0
+ ts-api-utils: 1.4.3(typescript@5.7.2)
+ typescript: 5.7.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/parser@8.19.0(eslint@9.17.0)(typescript@5.7.2)':
+ dependencies:
+ '@typescript-eslint/scope-manager': 8.19.0
+ '@typescript-eslint/types': 8.19.0
+ '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.7.2)
+ '@typescript-eslint/visitor-keys': 8.19.0
+ debug: 4.3.7
+ eslint: 9.17.0
+ typescript: 5.7.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/scope-manager@8.19.0':
+ dependencies:
+ '@typescript-eslint/types': 8.19.0
+ '@typescript-eslint/visitor-keys': 8.19.0
+
+ '@typescript-eslint/type-utils@8.19.0(eslint@9.17.0)(typescript@5.7.2)':
+ dependencies:
+ '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.7.2)
+ '@typescript-eslint/utils': 8.19.0(eslint@9.17.0)(typescript@5.7.2)
+ debug: 4.3.7
+ eslint: 9.17.0
+ ts-api-utils: 1.4.3(typescript@5.7.2)
+ typescript: 5.7.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/types@8.19.0': {}
+
+ '@typescript-eslint/typescript-estree@8.19.0(typescript@5.7.2)':
+ dependencies:
+ '@typescript-eslint/types': 8.19.0
+ '@typescript-eslint/visitor-keys': 8.19.0
+ debug: 4.3.7
+ fast-glob: 3.3.2
+ is-glob: 4.0.3
+ minimatch: 9.0.5
+ semver: 7.6.3
+ ts-api-utils: 1.4.3(typescript@5.7.2)
+ typescript: 5.7.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/utils@8.19.0(eslint@9.17.0)(typescript@5.7.2)':
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0)
+ '@typescript-eslint/scope-manager': 8.19.0
+ '@typescript-eslint/types': 8.19.0
+ '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.7.2)
+ eslint: 9.17.0
+ typescript: 5.7.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/visitor-keys@8.19.0':
+ dependencies:
+ '@typescript-eslint/types': 8.19.0
+ eslint-visitor-keys: 4.2.0
+
'@vitest/expect@1.6.0':
dependencies:
'@vitest/spy': 1.6.0
@@ -1146,18 +1770,52 @@ snapshots:
loupe: 2.3.7
pretty-format: 29.7.0
+ acorn-jsx@5.3.2(acorn@8.14.0):
+ dependencies:
+ acorn: 8.14.0
+
acorn-walk@8.3.4:
dependencies:
acorn: 8.14.0
acorn@8.14.0: {}
+ ajv@6.12.6:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
ansi-styles@5.2.0: {}
+ argparse@2.0.1: {}
+
assertion-error@1.1.0: {}
+ balanced-match@1.0.2: {}
+
+ brace-expansion@1.1.11:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ brace-expansion@2.0.1:
+ dependencies:
+ balanced-match: 1.0.2
+
+ braces@3.0.3:
+ dependencies:
+ fill-range: 7.1.1
+
cac@6.7.14: {}
+ callsites@3.1.0: {}
+
chai@4.5.0:
dependencies:
assertion-error: 1.1.0
@@ -1168,6 +1826,11 @@ snapshots:
pathval: 1.1.1
type-detect: 4.1.0
+ chalk@4.1.2:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
check-error@1.0.3:
dependencies:
get-func-name: 2.0.2
@@ -1180,6 +1843,14 @@ snapshots:
estree-walker: 3.0.3
periscopic: 3.1.0
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-name@1.1.4: {}
+
+ concat-map@0.0.1: {}
+
confbox@0.1.8: {}
cross-spawn@7.0.6:
@@ -1196,6 +1867,8 @@ snapshots:
dependencies:
type-detect: 4.1.0
+ deep-is@0.1.4: {}
+
diff-sequences@29.6.3: {}
esbuild@0.21.5:
@@ -1251,12 +1924,87 @@ snapshots:
'@esbuild/win32-ia32': 0.23.1
'@esbuild/win32-x64': 0.23.1
+ escape-string-regexp@4.0.0: {}
+
+ eslint-plugin-prettier@5.2.1(eslint@9.17.0)(prettier@3.4.2):
+ dependencies:
+ eslint: 9.17.0
+ prettier: 3.4.2
+ prettier-linter-helpers: 1.0.0
+ synckit: 0.9.2
+
+ eslint-scope@8.2.0:
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+
+ eslint-visitor-keys@3.4.3: {}
+
+ eslint-visitor-keys@4.2.0: {}
+
+ eslint@9.17.0:
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0)
+ '@eslint-community/regexpp': 4.12.1
+ '@eslint/config-array': 0.19.1
+ '@eslint/core': 0.9.1
+ '@eslint/eslintrc': 3.2.0
+ '@eslint/js': 9.17.0
+ '@eslint/plugin-kit': 0.2.4
+ '@humanfs/node': 0.16.6
+ '@humanwhocodes/module-importer': 1.0.1
+ '@humanwhocodes/retry': 0.4.1
+ '@types/estree': 1.0.6
+ '@types/json-schema': 7.0.15
+ ajv: 6.12.6
+ chalk: 4.1.2
+ cross-spawn: 7.0.6
+ debug: 4.3.7
+ escape-string-regexp: 4.0.0
+ eslint-scope: 8.2.0
+ eslint-visitor-keys: 4.2.0
+ espree: 10.3.0
+ esquery: 1.6.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 8.0.0
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ ignore: 5.3.2
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ json-stable-stringify-without-jsonify: 1.0.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.2
+ natural-compare: 1.4.0
+ optionator: 0.9.4
+ transitivePeerDependencies:
+ - supports-color
+
+ espree@10.3.0:
+ dependencies:
+ acorn: 8.14.0
+ acorn-jsx: 5.3.2(acorn@8.14.0)
+ eslint-visitor-keys: 4.2.0
+
+ esquery@1.6.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ esrecurse@4.3.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ estraverse@5.3.0: {}
+
estree-walker@2.0.2: {}
estree-walker@3.0.3:
dependencies:
'@types/estree': 1.0.6
+ esutils@2.0.3: {}
+
execa@8.0.1:
dependencies:
cross-spawn: 7.0.6
@@ -1269,6 +2017,46 @@ snapshots:
signal-exit: 4.1.0
strip-final-newline: 3.0.0
+ fast-deep-equal@3.1.3: {}
+
+ fast-diff@1.3.0: {}
+
+ fast-glob@3.3.2:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
+ fast-json-stable-stringify@2.1.0: {}
+
+ fast-levenshtein@2.0.6: {}
+
+ fastq@1.18.0:
+ dependencies:
+ reusify: 1.0.4
+
+ file-entry-cache@8.0.0:
+ dependencies:
+ flat-cache: 4.0.1
+
+ fill-range@7.1.1:
+ dependencies:
+ to-regex-range: 5.0.1
+
+ find-up@5.0.0:
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+
+ flat-cache@4.0.1:
+ dependencies:
+ flatted: 3.3.2
+ keyv: 4.5.4
+
+ flatted@3.3.2: {}
+
fsevents@2.3.3:
optional: true
@@ -1282,16 +2070,49 @@ snapshots:
dependencies:
resolve-pkg-maps: 1.0.0
+ glob-parent@5.1.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob-parent@6.0.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ globals@14.0.0: {}
+
+ globals@15.14.0: {}
+
+ graphemer@1.4.0: {}
+
+ has-flag@4.0.0: {}
+
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
human-signals@5.0.0: {}
+ ignore@5.3.2: {}
+
+ import-fresh@3.3.0:
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+
+ imurmurhash@0.1.4: {}
+
is-core-module@2.15.1:
dependencies:
hasown: 2.0.2
+ is-extglob@2.1.1: {}
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ is-number@7.0.0: {}
+
is-reference@3.0.3:
dependencies:
'@types/estree': 1.0.6
@@ -1305,6 +2126,25 @@ snapshots:
js-tokens@9.0.1: {}
+ js-yaml@4.1.0:
+ dependencies:
+ argparse: 2.0.1
+
+ json-buffer@3.0.1: {}
+
+ json-schema-traverse@0.4.1: {}
+
+ json-stable-stringify-without-jsonify@1.0.1: {}
+
+ keyv@4.5.4:
+ dependencies:
+ json-buffer: 3.0.1
+
+ levn@0.4.1:
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
local-pkg@0.5.1:
dependencies:
mlly: 1.7.3
@@ -1312,6 +2152,12 @@ snapshots:
locate-character@3.0.0: {}
+ locate-path@6.0.0:
+ dependencies:
+ p-locate: 5.0.0
+
+ lodash.merge@4.6.2: {}
+
loupe@2.3.7:
dependencies:
get-func-name: 2.0.2
@@ -1322,8 +2168,23 @@ snapshots:
merge-stream@2.0.0: {}
+ merge2@1.4.1: {}
+
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.1
+
mimic-fn@4.0.0: {}
+ minimatch@3.1.2:
+ dependencies:
+ brace-expansion: 1.1.11
+
+ minimatch@9.0.5:
+ dependencies:
+ brace-expansion: 2.0.1
+
mlly@1.7.3:
dependencies:
acorn: 8.14.0
@@ -1335,6 +2196,8 @@ snapshots:
nanoid@3.3.8: {}
+ natural-compare@1.4.0: {}
+
npm-run-path@5.3.0:
dependencies:
path-key: 4.0.0
@@ -1343,10 +2206,33 @@ snapshots:
dependencies:
mimic-fn: 4.0.0
+ optionator@0.9.4:
+ dependencies:
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ word-wrap: 1.2.5
+
+ p-limit@3.1.0:
+ dependencies:
+ yocto-queue: 0.1.0
+
p-limit@5.0.0:
dependencies:
yocto-queue: 1.1.1
+ p-locate@5.0.0:
+ dependencies:
+ p-limit: 3.1.0
+
+ parent-module@1.0.1:
+ dependencies:
+ callsites: 3.1.0
+
+ path-exists@4.0.0: {}
+
path-key@3.1.1: {}
path-key@4.0.0: {}
@@ -1365,6 +2251,8 @@ snapshots:
picocolors@1.1.1: {}
+ picomatch@2.3.1: {}
+
picomatch@4.0.2: {}
pkg-types@1.2.1:
@@ -1379,14 +2267,28 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
+ prelude-ls@1.2.1: {}
+
+ prettier-linter-helpers@1.0.0:
+ dependencies:
+ fast-diff: 1.3.0
+
+ prettier@3.4.2: {}
+
pretty-format@29.7.0:
dependencies:
'@jest/schemas': 29.6.3
ansi-styles: 5.2.0
react-is: 18.3.1
+ punycode@2.3.1: {}
+
+ queue-microtask@1.2.3: {}
+
react-is@18.3.1: {}
+ resolve-from@4.0.0: {}
+
resolve-pkg-maps@1.0.0: {}
resolve@1.22.8:
@@ -1395,6 +2297,8 @@ snapshots:
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
+ reusify@1.0.4: {}
+
rollup-plugin-dts@6.1.1(rollup@4.27.4)(typescript@5.7.2):
dependencies:
magic-string: 0.30.14
@@ -1427,6 +2331,12 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.27.4
fsevents: 2.3.3
+ run-parallel@1.2.0:
+ dependencies:
+ queue-microtask: 1.2.3
+
+ semver@7.6.3: {}
+
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
@@ -1445,18 +2355,37 @@ snapshots:
strip-final-newline@3.0.0: {}
+ strip-json-comments@3.1.1: {}
+
strip-literal@2.1.1:
dependencies:
js-tokens: 9.0.1
+ supports-color@7.2.0:
+ dependencies:
+ has-flag: 4.0.0
+
supports-preserve-symlinks-flag@1.0.0: {}
+ synckit@0.9.2:
+ dependencies:
+ '@pkgr/core': 0.1.1
+ tslib: 2.8.1
+
tinybench@2.9.0: {}
tinypool@0.8.4: {}
tinyspy@2.2.1: {}
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+
+ ts-api-utils@1.4.3(typescript@5.7.2):
+ dependencies:
+ typescript: 5.7.2
+
tslib@2.8.1: {}
tsx@4.19.2:
@@ -1466,6 +2395,10 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
+ type-check@0.4.0:
+ dependencies:
+ prelude-ls: 1.2.1
+
type-detect@4.1.0: {}
typescript@5.7.2: {}
@@ -1474,6 +2407,10 @@ snapshots:
undici-types@6.19.8: {}
+ uri-js@4.4.1:
+ dependencies:
+ punycode: 2.3.1
+
vite-node@1.6.0(@types/node@20.17.8):
dependencies:
cac: 6.7.14
@@ -1544,8 +2481,12 @@ snapshots:
siginfo: 2.0.0
stackback: 0.0.2
+ word-wrap@1.2.5: {}
+
yaml@2.6.1: {}
+ yocto-queue@0.1.0: {}
+
yocto-queue@1.1.1: {}
zod@3.23.8: {}
diff --git a/src/compiler/base/nodes/tags/content.ts b/src/compiler/base/nodes/tags/content.ts
index 6c2fcbd..b296d59 100644
--- a/src/compiler/base/nodes/tags/content.ts
+++ b/src/compiler/base/nodes/tags/content.ts
@@ -107,7 +107,7 @@ export async function compile(
if (toolArguments && typeof toolArguments === 'string') {
try {
rest['arguments'] = JSON.parse(toolArguments)
- } catch (e) {
+ } catch {
baseNodeError(errors.invalidToolCallArguments, node)
}
}
diff --git a/src/compiler/chain-serialize.test.ts b/src/compiler/chain-serialize.test.ts
new file mode 100644
index 0000000..da77470
--- /dev/null
+++ b/src/compiler/chain-serialize.test.ts
@@ -0,0 +1,134 @@
+import { getExpectedError } from '$promptl/test/helpers'
+import { describe, expect, it } from 'vitest'
+
+import { Chain } from './chain'
+import { removeCommonIndent } from './utils'
+import { Adapters } from '$promptl/providers'
+import { MessageRole } from '$promptl/types'
+
+describe('serialize chain', async () => {
+ it('fails when trying to serialize without running step', async () => {
+ const prompt = removeCommonIndent(`
+
+ Before step
+
+
+ After step
+
+ `)
+
+ const chain = new Chain({ prompt, adapter: Adapters.default })
+
+ const action = () => chain.serialize()
+ const error = await getExpectedError(action, Error)
+ expect(error.message).toBe(
+ 'The chain has not started yet. You must call `step` at least once before calling `serialize`.',
+ )
+ })
+
+ it('serialize with single step', async () => {
+ const prompt = removeCommonIndent(`
+ ---
+ provider: OpenAI_PATATA
+ model: gpt-4
+ ---
+ {{ foo = 'foo' }}
+ A message
+ `)
+
+ const chain = new Chain({
+ prompt: removeCommonIndent(prompt),
+ parameters: {},
+ adapter: Adapters.default,
+ })
+ await chain.step()
+ const serialized = chain.serialize()
+ expect(serialized).toEqual({
+ rawText: prompt,
+ scope: {
+ pointers: { foo: 0 },
+ stash: ['foo'],
+ },
+ adapterType: 'default',
+ compilerOptions: {},
+ globalConfig: {
+ provider: 'OpenAI_PATATA',
+ model: 'gpt-4',
+ },
+ ast: expect.any(Object),
+ globalMessages: [
+ {
+ role: 'system',
+ content: [{ type: 'text', text: 'A message' }],
+ },
+ ],
+ })
+ })
+
+ it('serialize 2 steps', async () => {
+ const prompt = removeCommonIndent(`
+
+ {{foo = 5}}
+
+
+ {{foo += 1}}
+
+
+ {{foo}}
+
+ `)
+
+ const chain = new Chain({ prompt, adapter: Adapters.openai })
+
+ await chain.step()
+ await chain.step('First step response')
+ const serialized = chain.serialize()
+ expect(serialized).toEqual({
+ rawText: prompt,
+ scope: {
+ pointers: { foo: 0 },
+ stash: [6],
+ },
+ adapterType: 'openai',
+ compilerOptions: { includeSourceMap: false },
+ globalConfig: undefined,
+ ast: expect.any(Object),
+ globalMessages: [
+ {
+ role: 'assistant',
+ content: [{ type: 'text', text: 'First step response' }],
+ },
+ ],
+ })
+ })
+
+ it('serialize parameters', async () => {
+ const prompt = removeCommonIndent(`
+ Hello {{name}}
+ `)
+
+ const chain = new Chain({
+ prompt,
+ parameters: { name: 'Paco' },
+ adapter: Adapters.default,
+ defaultRole: MessageRole.user,
+ })
+
+ await chain.step()
+ const serialized = chain.serialize()
+ expect(serialized).toEqual({
+ rawText: prompt,
+ adapterType: 'default',
+ scope: { pointers: { name: 0 }, stash: ['Paco'] },
+ compilerOptions: { defaultRole: 'user' },
+ ast: expect.any(Object),
+ globalConfig: undefined,
+ globalMessages: [
+ {
+ role: 'user',
+ content: [{ type: 'text', text: 'Hello Paco' }],
+ },
+ ],
+ })
+ })
+})
diff --git a/src/compiler/chain.test.ts b/src/compiler/chain.test.ts
index 3a07019..f8fef30 100644
--- a/src/compiler/chain.test.ts
+++ b/src/compiler/chain.test.ts
@@ -36,7 +36,7 @@ describe('chain', async () => {
const chain = new Chain({
prompt: removeCommonIndent(prompt),
parameters: {},
- adapter: Adapters.default
+ adapter: Adapters.default,
})
const { steps, messages } = await complete({ chain })
expect(steps).toBe(1)
@@ -173,9 +173,7 @@ describe('chain', async () => {
const chain = new Chain({ prompt, adapter: Adapters.default })
const { steps, messages } = await complete({ chain })
expect(steps).toBe(3)
- const stepMessages = messages.filter(
- (m) => m.role === MessageRole.system,
- )
+ const stepMessages = messages.filter((m) => m.role === MessageRole.system)
expect(stepMessages.length).toBe(3)
expect((stepMessages[0]!.content[0] as TextContent).text).toBe('Step 1')
expect((stepMessages[1]!.content[0] as TextContent).text).toBe('Step 3')
@@ -199,9 +197,7 @@ describe('chain', async () => {
const { steps, messages } = await complete({ chain })
expect(steps).toBe(1)
expect(messages.length).toBe(2)
- expect((messages[0]!.content[0] as TextContent).text).toBe(
- 'Step 1',
- )
+ expect((messages[0]!.content[0] as TextContent).text).toBe('Step 1')
})
it('isolated steps do not include previous messages', async () => {
@@ -268,7 +264,7 @@ describe('chain', async () => {
func1,
func2,
},
- adapter: Adapters.default
+ adapter: Adapters.default,
})
const { messages } = await complete({ chain })
@@ -360,7 +356,10 @@ describe('chain', async () => {
{{bar}}
`)
- const correctChain = new Chain({ prompt: correctPrompt, adapter: Adapters.default })
+ const correctChain = new Chain({
+ prompt: correctPrompt,
+ adapter: Adapters.default,
+ })
const { messages } = await complete({ chain: correctChain })
expect(messages[messages.length - 2]!).toEqual({
@@ -376,7 +375,7 @@ describe('chain', async () => {
const incorrectChain = new Chain({
prompt: incorrectPrompt,
parameters: {},
- adapter: Adapters.default
+ adapter: Adapters.default,
})
const action = () => complete({ chain: incorrectChain })
@@ -389,7 +388,7 @@ describe('chain', async () => {
{{ foo = 0 }}
{{for element in [1, 2, 3]}}
-
+
{{foo}}
@@ -406,15 +405,9 @@ describe('chain', async () => {
const { messages } = await complete({ chain, maxSteps: 6 })
expect(messages.length).toBe(8)
- expect((messages[0]!.content[0]! as TextContent).text).toBe(
- '0',
- )
- expect((messages[2]!.content[0]! as TextContent).text).toBe(
- '1',
- )
- expect((messages[4]!.content[0]! as TextContent).text).toBe(
- '2',
- )
+ expect((messages[0]!.content[0]! as TextContent).text).toBe('0')
+ expect((messages[2]!.content[0]! as TextContent).text).toBe('1')
+ expect((messages[4]!.content[0]! as TextContent).text).toBe('2')
expect(messages[6]).toEqual({
role: MessageRole.system,
content: [
@@ -466,9 +459,7 @@ describe('chain', async () => {
const chain = new Chain({ prompt, adapter: Adapters.default })
const { messages } = await complete({ chain })
- const userMessages = messages.filter(
- (m) => m.role === MessageRole.user,
- )
+ const userMessages = messages.filter((m) => m.role === MessageRole.user)
const userMessageText = userMessages
.map((m) => m.content.map((c) => (c as TextContent).text).join(' '))
.join('\n')
@@ -500,7 +491,7 @@ describe('chain', async () => {
it('saves the response in a variable', async () => {
const prompt = removeCommonIndent(`
<${TAG_NAMES.step} raw="rawResponse" as="responseText"/>
-
+
{{rawResponse}}
@@ -560,3 +551,30 @@ describe('chain', async () => {
expect(finalConversation.config.temperature).toBe(0.5)
})
})
+
+describe('chain global messages count', async () => {
+ it('display messages count', async () => {
+ const prompt = removeCommonIndent(`
+ {{ foo = 'foo' }}
+ System message
+
+ {{for element in [1, 2, 3]}}
+
+ User message: {{element}}
+
+ {{endfor}}
+
+
+ Assistant message: {{foo}}
+
+ `)
+
+ const chain = new Chain({
+ prompt: removeCommonIndent(prompt),
+ parameters: {},
+ adapter: Adapters.default,
+ })
+ await complete({ chain })
+ expect(chain.globalMessagesCount).toBe(6)
+ })
+})
diff --git a/src/compiler/chain.ts b/src/compiler/chain.ts
index 575bab6..a716454 100644
--- a/src/compiler/chain.ts
+++ b/src/compiler/chain.ts
@@ -1,3 +1,7 @@
+import {
+ deserializeChain,
+ SerializedProps,
+} from '$promptl/compiler/deserializeChain'
import { CHAIN_STEP_ISOLATED_ATTR } from '$promptl/constants'
import parse from '$promptl/parser'
import { Fragment } from '$promptl/parser/interfaces'
@@ -25,10 +29,16 @@ type ChainStep = ProviderConversation & {
type StepResponse =
| string
+ | M[]
| (Omit & {
role?: M['role']
})
+type BuildStepResponseContent = {
+ messages?: Message[]
+ contents: MessageContent[] | undefined
+}
+
export class Chain {
public rawText: string
@@ -43,90 +53,87 @@ export class Chain {
private globalConfig: Config | undefined
private wasLastStepIsolated: boolean = false
+ static deserialize(args: SerializedProps) {
+ return deserializeChain(args)
+ }
+
constructor({
prompt,
parameters = {},
+ serialized,
adapter = Adapters.openai as ProviderAdapter,
...compileOptions
}: {
prompt: string
parameters?: Record
adapter?: ProviderAdapter
+ serialized?: {
+ ast: Fragment
+ scope: Scope
+ globalConfig: Config | undefined
+ globalMessages: Message[]
+ }
} & CompileOptions) {
this.rawText = prompt
- this.ast = parse(prompt)
- this.scope = new Scope(parameters)
+
+ // Init from a serialized chain
+ this.ast = serialized?.ast ?? parse(prompt)
+ this.scope = serialized?.scope ?? new Scope(parameters)
+ this.globalConfig = serialized?.globalConfig
+ this.globalMessages = serialized?.globalMessages ?? []
+ this.didStart = !!serialized
+
this.adapter = adapter
this.compileOptions = compileOptions
+
if (this.adapter !== Adapters.default) {
this.compileOptions.includeSourceMap = false
}
}
- private buildStepResponseContent(
- response?: StepResponse,
- ): MessageContent[] | undefined {
- if (response == undefined) return response
-
- if (typeof response === 'string') {
- return [{ type: ContentType.text, text: response }]
- }
-
- const responseMessage = {
- ...response,
- role: response.role ?? MessageRole.assistant,
- } as M
-
- const convertedMessages = this.adapter.toPromptl({
- config: this.globalConfig ?? {},
- messages: [responseMessage],
- })
-
- return convertedMessages.messages[0]!.content
- }
-
async step(response?: StepResponse): Promise> {
if (this._completed) {
throw new Error('The chain has already completed')
}
+
if (!this.didStart && response !== undefined) {
throw new Error('A response is not allowed before the chain has started')
}
+
if (this.didStart && response === undefined) {
throw new Error('A response is required to continue the chain')
}
+
this.didStart = true
const responseContent = this.buildStepResponseContent(response)
- if (responseContent && !this.wasLastStepIsolated) {
- this.globalMessages.push({
- role: MessageRole.assistant,
- content: responseContent ?? [],
- })
+ const newGlobalMessages = this.buildGlobalMessages(responseContent)
+
+ if (newGlobalMessages.length > 0) {
+ this.globalMessages = [
+ ...this.globalMessages,
+ ...(newGlobalMessages as Message[]),
+ ]
}
const compile = new Compile({
ast: this.ast,
rawText: this.rawText,
globalScope: this.scope,
- stepResponse: responseContent,
+ stepResponse: responseContent.contents,
...this.compileOptions,
})
- const {
- completed,
- scopeStash,
- ast,
- messages: messages,
- globalConfig,
- stepConfig,
- } = await compile.run()
+ const { completed, scopeStash, ast, messages, globalConfig, stepConfig } =
+ await compile.run()
this.scope = Scope.withStash(scopeStash).copy(this.scope.getPointers())
this.ast = ast
this.globalConfig = globalConfig ?? this.globalConfig
- this._completed = completed && !messages.length // If it returned a message, there is still a final step to be taken
+
+ // If it returned a message, there is still a final step to be taken
+ this._completed = completed && !messages.length
const config = {
...this.globalConfig,
@@ -140,7 +147,9 @@ export class Chain {
...messages,
]
- if (!this.wasLastStepIsolated) this.globalMessages.push(...messages)
+ if (!this.wasLastStepIsolated) {
+ this.globalMessages.push(...messages)
+ }
return {
...this.adapter.fromPromptl({
@@ -151,7 +160,77 @@ export class Chain {
}
}
+ serialize() {
+ if (!this.didStart) {
+ throw new Error(
+ 'The chain has not started yet. You must call `step` at least once before calling `serialize`.',
+ )
+ }
+
+ return {
+ ast: this.ast,
+ scope: this.scope.serialize(),
+ adapterType: this.adapter.type,
+ compilerOptions: this.compileOptions,
+ globalConfig: this.globalConfig,
+ globalMessages: this.globalMessages,
+ rawText: this.rawText,
+ }
+ }
+
+ get globalMessagesCount(): number {
+ return this.globalMessages.length
+ }
+
get completed(): boolean {
return this._completed
}
+
+ private buildStepResponseContent(
+ response?: StepResponse | M[],
+ ): BuildStepResponseContent {
+ if (response == undefined) return { contents: undefined }
+ if (typeof response === 'string') {
+ return { contents: [{ text: response, type: ContentType.text }] }
+ }
+
+ if (Array.isArray(response)) {
+ const converted = this.adapter.toPromptl({
+ config: this.globalConfig ?? {},
+ messages: response as M[],
+ })
+ const contents = converted.messages.flatMap((m) => m.content)
+ return { messages: converted.messages as Message[], contents }
+ }
+
+ const responseMessage = {
+ ...response,
+ role: response.role ?? MessageRole.assistant,
+ } as M
+
+ const convertedMessages = this.adapter.toPromptl({
+ config: this.globalConfig ?? {},
+ messages: [responseMessage],
+ })
+
+ return { contents: convertedMessages.messages[0]!.content }
+ }
+
+ private buildGlobalMessages(
+ buildStepResponseContent: BuildStepResponseContent,
+ ) {
+ const { messages, contents } = buildStepResponseContent
+
+ if (this.wasLastStepIsolated) return []
+ if (!contents) return []
+
+ if (messages) return messages
+
+ return [
+ {
+ role: MessageRole.assistant,
+ content: contents ?? [],
+ },
+ ]
+ }
}
diff --git a/src/compiler/compile.test.ts b/src/compiler/compile.test.ts
index dd1fcb5..a68e285 100644
--- a/src/compiler/compile.test.ts
+++ b/src/compiler/compile.test.ts
@@ -76,7 +76,11 @@ describe('automatic message grouping', async () => {
it('allows defining the default role', async () => {
const prompt = 'Hello world!'
- const result = await render({ prompt, defaultRole: MessageRole.user, adapter: Adapters.default })
+ const result = await render({
+ prompt,
+ defaultRole: MessageRole.user,
+ adapter: Adapters.default,
+ })
const message = result.messages[0]!
expect(message.role).toBe(MessageRole.user)
})
@@ -463,87 +467,96 @@ describe('operators', async () => {
})
})
-describe("source map", async () => {
- it("does not include source map when not specified", async () => {
+describe('source map', async () => {
+ it('does not include source map when not specified', async () => {
const prompt = `
Given a context, answer questions succintly yet complete.
{{ context }}
Please, help me with {{ question }}!
- `;
+ `
const { messages } = await render({
prompt,
parameters: {
- context: "context",
- question: "question",
+ context: 'context',
+ question: 'question',
},
adapter: Adapters.default,
- });
+ })
expect(messages).toEqual([
{
role: MessageRole.system,
- content: [{ type: ContentType.text, text: "Given a context, answer questions succintly yet complete." }],
+ content: [
+ {
+ type: ContentType.text,
+ text: 'Given a context, answer questions succintly yet complete.',
+ },
+ ],
},
{
role: MessageRole.system,
- content: [{ type: ContentType.text, text: "context" }],
+ content: [{ type: ContentType.text, text: 'context' }],
},
{
role: MessageRole.user,
- content: [{ type: ContentType.text, text: "Please, help me with question!" }],
+ content: [
+ { type: ContentType.text, text: 'Please, help me with question!' },
+ ],
},
- ]);
- });
+ ])
+ })
- it("does not include source map when non-default adapter", async () => {
+ it('does not include source map when non-default adapter', async () => {
const prompt = `
Given a context, answer questions succintly yet complete.
{{ context }}
Please, help me with {{ question }}!
- `;
+ `
const { messages } = await render({
prompt,
parameters: {
- context: "context",
- question: "question",
+ context: 'context',
+ question: 'question',
},
adapter: Adapters.openai,
includeSourceMap: true,
- });
+ })
expect(messages).toEqual([
{
role: MessageRole.system,
- content: "Given a context, answer questions succintly yet complete.",
+ content: 'Given a context, answer questions succintly yet complete.',
},
{
role: MessageRole.system,
- content: "context",
+ content: 'context',
},
{
role: MessageRole.user,
- content: [{ type: ContentType.text, text: "Please, help me with question!" }],
+ content: [
+ { type: ContentType.text, text: 'Please, help me with question!' },
+ ],
},
- ]);
- });
+ ])
+ })
- describe("includes source map when specified", async () => {
- it("returns empty source map when no identifiers", async () => {
+ describe('includes source map when specified', async () => {
+ it('returns empty source map when no identifiers', async () => {
const prompt = `
Given a context, answer questions succintly yet complete.
context
Please, help me with question!
- `;
+ `
const { messages } = await render({
prompt,
adapter: Adapters.default,
includeSourceMap: true,
- });
+ })
expect(messages).toEqual([
{
role: MessageRole.system,
content: [
{
type: ContentType.text,
- text: "Given a context, answer questions succintly yet complete.",
+ text: 'Given a context, answer questions succintly yet complete.',
_promptlSourceMap: [],
},
],
@@ -553,7 +566,7 @@ Given a context, answer questions succintly yet complete.
content: [
{
type: ContentType.text,
- text: "context",
+ text: 'context',
_promptlSourceMap: [],
},
],
@@ -563,36 +576,36 @@ Given a context, answer questions succintly yet complete.
content: [
{
type: ContentType.text,
- text: "Please, help me with question!",
+ text: 'Please, help me with question!',
_promptlSourceMap: [],
},
],
},
- ]);
- });
+ ])
+ })
- it("returns source map when single identifiers per content", async () => {
+ it('returns source map when single identifiers per content', async () => {
const prompt = `
Given a context, answer questions succintly yet complete.
{{ context }}
Please, help me with {{ question }}!
- `;
+ `
const { messages } = await render({
prompt,
parameters: {
- context: "context",
- question: "question",
+ context: 'context',
+ question: 'question',
},
adapter: Adapters.default,
includeSourceMap: true,
- });
+ })
expect(messages).toEqual([
{
role: MessageRole.system,
content: [
{
type: ContentType.text,
- text: "Given a context, answer questions succintly yet complete.",
+ text: 'Given a context, answer questions succintly yet complete.',
_promptlSourceMap: [],
},
],
@@ -602,12 +615,12 @@ Given a context, answer questions succintly yet complete.
content: [
{
type: ContentType.text,
- text: "context",
+ text: 'context',
_promptlSourceMap: [
{
start: 0,
end: 7,
- identifier: "context",
+ identifier: 'context',
},
],
},
@@ -618,44 +631,44 @@ Given a context, answer questions succintly yet complete.
content: [
{
type: ContentType.text,
- text: "Please, help me with question!",
+ text: 'Please, help me with question!',
_promptlSourceMap: [
{
start: 21,
end: 29,
- identifier: "question",
+ identifier: 'question',
},
],
},
],
},
- ]);
- });
+ ])
+ })
- it("returns source map when multiple identifiers per content", async () => {
+ it('returns source map when multiple identifiers per content', async () => {
const prompt = `
Given some context, answer questions succintly yet complete.
{{ context_1 }} and {{ context_2 }}
Please, help me with {{ question_1 }} and {{ question_2 }}!
- `;
+ `
const { messages } = await render({
prompt,
parameters: {
- context_1: "context_1",
- context_2: "context_2",
- question_1: "question_1",
- question_2: "question_2",
+ context_1: 'context_1',
+ context_2: 'context_2',
+ question_1: 'question_1',
+ question_2: 'question_2',
},
adapter: Adapters.default,
includeSourceMap: true,
- });
+ })
expect(messages).toEqual([
{
role: MessageRole.system,
content: [
{
type: ContentType.text,
- text: "Given some context, answer questions succintly yet complete.",
+ text: 'Given some context, answer questions succintly yet complete.',
_promptlSourceMap: [],
},
],
@@ -665,17 +678,17 @@ Given some context, answer questions succintly yet complete.
content: [
{
type: ContentType.text,
- text: "context_1 and context_2",
+ text: 'context_1 and context_2',
_promptlSourceMap: [
{
start: 0,
end: 9,
- identifier: "context_1",
+ identifier: 'context_1',
},
{
start: 14,
end: 23,
- identifier: "context_2",
+ identifier: 'context_2',
},
],
},
@@ -686,47 +699,47 @@ Given some context, answer questions succintly yet complete.
content: [
{
type: ContentType.text,
- text: "Please, help me with question_1 and question_2!",
+ text: 'Please, help me with question_1 and question_2!',
_promptlSourceMap: [
{
start: 21,
end: 31,
- identifier: "question_1",
+ identifier: 'question_1',
},
{
start: 36,
end: 46,
- identifier: "question_2",
+ identifier: 'question_2',
},
],
},
],
},
- ]);
- });
+ ])
+ })
- it("returns source map when duplicated identifiers", async () => {
+ it('returns source map when duplicated identifiers', async () => {
const prompt = `
Given a context, answer questions succintly yet complete.
{{ context }} and {{ context }}
Please, help me with {{ question }} and {{ question }}!
- `;
+ `
const { messages } = await render({
prompt,
parameters: {
- context: "context",
- question: "question",
+ context: 'context',
+ question: 'question',
},
adapter: Adapters.default,
includeSourceMap: true,
- });
+ })
expect(messages).toEqual([
{
role: MessageRole.system,
content: [
{
type: ContentType.text,
- text: "Given a context, answer questions succintly yet complete.",
+ text: 'Given a context, answer questions succintly yet complete.',
_promptlSourceMap: [],
},
],
@@ -736,17 +749,17 @@ Given a context, answer questions succintly yet complete.
content: [
{
type: ContentType.text,
- text: "context and context",
+ text: 'context and context',
_promptlSourceMap: [
{
start: 0,
end: 7,
- identifier: "context",
+ identifier: 'context',
},
{
start: 12,
end: 19,
- identifier: "context",
+ identifier: 'context',
},
],
},
@@ -757,26 +770,26 @@ Given a context, answer questions succintly yet complete.
content: [
{
type: ContentType.text,
- text: "Please, help me with question and question!",
+ text: 'Please, help me with question and question!',
_promptlSourceMap: [
{
start: 21,
end: 29,
- identifier: "question",
+ identifier: 'question',
},
{
start: 34,
end: 42,
- identifier: "question",
+ identifier: 'question',
},
],
},
],
},
- ]);
- });
+ ])
+ })
- it("returns source map when multiple new lines and indents", async () => {
+ it('returns source map when multiple new lines and indents', async () => {
const prompt = `
Given a context, answer questions succintly yet complete.
@@ -807,27 +820,27 @@ Given a context, answer questions succintly yet complete.
{{empty}}{{empty}}
- `;
+ `
const { messages } = await render({
prompt,
parameters: {
- context: "context",
- question: "question",
- lyrics: "lyrics",
- image: "image",
- file: "file",
+ context: 'context',
+ question: 'question',
+ lyrics: 'lyrics',
+ image: 'image',
+ file: 'file',
empty: ' ',
},
adapter: Adapters.default,
includeSourceMap: true,
- });
+ })
expect(messages).toEqual([
{
role: MessageRole.system,
content: [
{
type: ContentType.text,
- text: "Given a context, answer questions succintly yet complete.",
+ text: 'Given a context, answer questions succintly yet complete.',
_promptlSourceMap: [],
},
],
@@ -837,12 +850,12 @@ Given a context, answer questions succintly yet complete.
content: [
{
type: ContentType.text,
- text: "context",
+ text: 'context',
_promptlSourceMap: [
{
start: 0,
end: 7,
- identifier: "context",
+ identifier: 'context',
},
],
},
@@ -853,12 +866,12 @@ Given a context, answer questions succintly yet complete.
content: [
{
type: ContentType.text,
- text: "Please, help me\n with question!",
+ text: 'Please, help me\n with question!',
_promptlSourceMap: [
{
start: 23,
end: 31,
- identifier: "question",
+ identifier: 'question',
},
],
},
@@ -869,46 +882,46 @@ Given a context, answer questions succintly yet complete.
content: [
{
type: ContentType.text,
- text: "Is this the real life?",
+ text: 'Is this the real life?',
_promptlSourceMap: [],
},
{
type: ContentType.text,
- text: "Is this just fantasy?\n lyrics",
+ text: 'Is this just fantasy?\n lyrics',
_promptlSourceMap: [
{
start: 24,
end: 30,
- identifier: "lyrics",
+ identifier: 'lyrics',
},
],
},
{
type: ContentType.image,
- image: "image",
+ image: 'image',
_promptlSourceMap: [
{
start: 0,
end: 5,
- identifier: "image",
+ identifier: 'image',
},
],
},
{
type: ContentType.file,
- file: "file",
- mimeType: "text/plain",
+ file: 'file',
+ mimeType: 'text/plain',
_promptlSourceMap: [
{
start: 0,
end: 4,
- identifier: "file",
+ identifier: 'file',
},
],
},
],
},
- ]);
- });
- });
-});
+ ])
+ })
+ })
+})
diff --git a/src/compiler/compile.ts b/src/compiler/compile.ts
index 951b243..5cbe12b 100644
--- a/src/compiler/compile.ts
+++ b/src/compiler/compile.ts
@@ -149,7 +149,10 @@ export class Compile {
this.globalConfig = config
}
- private getSourceRef(text: string, node?: TemplateNode): PromptlSourceRef | undefined {
+ private getSourceRef(
+ text: string,
+ node?: TemplateNode,
+ ): PromptlSourceRef | undefined {
if (!node) return undefined
if (node.type !== 'MustacheTag') return undefined
@@ -161,9 +164,9 @@ export class Compile {
switch (node.expression.type) {
case 'Identifier':
sourceRef.identifier = node.expression.name
- break;
+ break
default:
- break;
+ break
}
return sourceRef
@@ -184,17 +187,20 @@ export class Compile {
): PromptlSourceRef[] {
const indent = getCommonIndent(text)
let position = 0
- text = text.split('\n').map((line) => {
- const offset = line.length - line.slice(indent).length
- line = line.slice(indent)
- sourceMap = sourceMap.map((ref) => ({
- ...ref,
- start: ref.start >= position ? ref.start - offset : ref.start,
- end: ref.end >= position ? ref.end - offset : ref.end,
- }))
- position += line.length + 1
- return line
- }).join('\n')
+ text = text
+ .split('\n')
+ .map((line) => {
+ const offset = line.length - line.slice(indent).length
+ line = line.slice(indent)
+ sourceMap = sourceMap.map((ref) => ({
+ ...ref,
+ start: ref.start >= position ? ref.start - offset : ref.start,
+ end: ref.end >= position ? ref.end - offset : ref.end,
+ }))
+ position += line.length + 1
+ return line
+ })
+ .join('\n')
const offset = text.length - text.trimStart().length
text = text.trimStart()
@@ -210,7 +216,7 @@ export class Compile {
start: Math.min(ref.start, text.length),
end: Math.min(ref.end, text.length),
}))
-
+
return sourceMap
}
@@ -220,7 +226,7 @@ export class Compile {
this.accumulatedText.sourceMap,
)
const text = removeCommonIndent(this.accumulatedText.text)
-
+
this.accumulatedText.text = ''
this.accumulatedText.sourceMap = []
diff --git a/src/compiler/deserializeChain.test.ts b/src/compiler/deserializeChain.test.ts
new file mode 100644
index 0000000..8e749ca
--- /dev/null
+++ b/src/compiler/deserializeChain.test.ts
@@ -0,0 +1,39 @@
+import { describe, expect, it } from 'vitest'
+
+import { removeCommonIndent } from './utils'
+import { Adapters } from '$promptl/providers'
+import { Chain } from './chain'
+
+describe('deserialize chain', async () => {
+ it('get final step from serialized chain', async () => {
+ const prompt = removeCommonIndent(`
+
+ {{foo = 5}}
+
+
+ {{foo += 1}}
+
+
+
+ The final result is {{foo}}
+
+
+ `)
+
+ const chain = new Chain({ prompt, adapter: Adapters.openai })
+
+ await chain.step()
+ await chain.step('First step response')
+ const serializedChain = chain.serialize()
+ const serialized = JSON.stringify(serializedChain)
+
+ // In another context we deserialize existing chain
+ const deserializedChain = Chain.deserialize({ serialized })
+ const { messages } = await deserializedChain!.step('Last step')
+ expect(messages[messages.length - 1]).toEqual({
+ role: 'assistant',
+ tool_calls: undefined,
+ content: [{ text: 'The final result is 6', type: 'text' }],
+ })
+ })
+})
diff --git a/src/compiler/deserializeChain.ts b/src/compiler/deserializeChain.ts
new file mode 100644
index 0000000..e2c2088
--- /dev/null
+++ b/src/compiler/deserializeChain.ts
@@ -0,0 +1,80 @@
+import { SerializedChain } from '$promptl/compiler'
+import { Chain } from '$promptl/compiler/chain'
+import Scope from '$promptl/compiler/scope'
+import { AdapterKey, Adapters, ProviderAdapter } from '$promptl/providers'
+import { Message } from '$promptl/types'
+
+function getAdapter(adapterType: AdapterKey) {
+ const adapter = Adapters[adapterType]
+
+ if (!adapter) throw new Error(`Adapter not found: ${adapterType}`)
+
+ return adapter as ProviderAdapter
+}
+
+function safeSerializedData(data: string | SerializedChain): SerializedChain {
+ try {
+ const serialized =
+ typeof data === 'string'
+ ? JSON.parse(data)
+ : typeof data === 'object'
+ ? data
+ : {}
+
+ const compilerOptions = serialized.compilerOptions || {}
+ const globalConfig = serialized.globalConfig
+ const globalMessages = serialized.globalMessages || []
+
+ if (
+ typeof serialized !== 'object' ||
+ typeof serialized.ast !== 'object' ||
+ typeof serialized.scope !== 'object' ||
+ typeof serialized.adapterType !== 'string' ||
+ typeof serialized.rawText !== 'string'
+ ) {
+ throw new Error()
+ }
+ return {
+ rawText: serialized.rawText,
+ ast: serialized.ast,
+ scope: serialized.scope,
+ adapterType: serialized.adapterType,
+ compilerOptions,
+ globalConfig,
+ globalMessages,
+ }
+ } catch {
+ throw new Error('Invalid serialized chain data')
+ }
+}
+
+export type SerializedProps = {
+ serialized: string | SerializedChain | undefined | null
+}
+export function deserializeChain({
+ serialized,
+}: SerializedProps): Chain | undefined {
+ if (!serialized) return undefined
+
+ const {
+ rawText,
+ ast,
+ scope: serializedScope,
+ adapterType,
+ compilerOptions,
+ globalConfig,
+ globalMessages,
+ } = safeSerializedData(serialized)
+
+ const adapter = getAdapter(adapterType)
+ const scope = new Scope()
+ scope.setStash(serializedScope.stash)
+ scope.setPointers(serializedScope.pointers)
+
+ return new Chain({
+ prompt: rawText,
+ serialized: { ast, scope, globalConfig, globalMessages },
+ adapter,
+ ...compilerOptions,
+ })
+}
diff --git a/src/compiler/index.ts b/src/compiler/index.ts
index 9c52363..17fdefe 100644
--- a/src/compiler/index.ts
+++ b/src/compiler/index.ts
@@ -58,4 +58,6 @@ export function scan({
}).run()
}
-export { Chain, type Document, type ReferencePromptFn }
+type SerializedChain = ReturnType
+
+export { Chain, type SerializedChain, type Document, type ReferencePromptFn }
diff --git a/src/compiler/scan.test.ts b/src/compiler/scan.test.ts
index a51fc6d..d664c97 100644
--- a/src/compiler/scan.test.ts
+++ b/src/compiler/scan.test.ts
@@ -1,71 +1,70 @@
-import { TAG_NAMES } from "$promptl/constants";
-import path from "path";
-import CompileError from "$promptl/error/error";
-import { describe, expect, it } from "vitest";
-import { z } from "zod";
+import { TAG_NAMES } from '$promptl/constants'
+import path from 'path'
+import CompileError from '$promptl/error/error'
+import { describe, expect, it } from 'vitest'
+import { z } from 'zod'
-import { scan } from ".";
-import { Document } from "./types";
-import { removeCommonIndent } from "./utils";
+import { scan } from '.'
+import { Document } from './types'
+import { removeCommonIndent } from './utils'
type PromptTree = {
- [path: string]: string | PromptTree;
-};
+ [path: string]: string | PromptTree
+}
const referenceFn = (prompts: PromptTree) => {
return async (
relativePath: string,
currentAbsolutePath?: string,
): Promise => {
- path.resolve;
+ path.resolve
const refAbsolutePath = currentAbsolutePath
? path
- .resolve(path.dirname(`/${currentAbsolutePath}`), relativePath)
- .replace(/^\//, "")
- : relativePath;
+ .resolve(path.dirname(`/${currentAbsolutePath}`), relativePath)
+ .replace(/^\//, '')
+ : relativePath
- if (!(refAbsolutePath in prompts)) return undefined;
+ if (!(refAbsolutePath in prompts)) return undefined
return {
path: refAbsolutePath,
content: prompts[refAbsolutePath]!,
- } as Document;
- };
-};
-
-describe("hash", async () => {
- it("always returns the same hash for the same prompt", async () => {
- const prompt1 = "This is a prompt";
- const prompt2 = "This is a prompt";
- const prompt3 = "This is another prompt";
-
- const metadata1 = await scan({ prompt: prompt1 });
- const metadata2 = await scan({ prompt: prompt2 });
- const metadata3 = await scan({ prompt: prompt3 });
-
- expect(metadata1.hash).toBe(metadata2.hash);
- expect(metadata1.hash).not.toBe(metadata3.hash);
- });
-
- it("includes the content from referenced tags into account when calculating the hash", async () => {
- const parent =
- 'This is the parent prompt. The end.';
- const child1 = "ABCDEFG";
- const child2 = "1234567";
+ } as Document
+ }
+}
+
+describe('hash', async () => {
+ it('always returns the same hash for the same prompt', async () => {
+ const prompt1 = 'This is a prompt'
+ const prompt2 = 'This is a prompt'
+ const prompt3 = 'This is another prompt'
+
+ const metadata1 = await scan({ prompt: prompt1 })
+ const metadata2 = await scan({ prompt: prompt2 })
+ const metadata3 = await scan({ prompt: prompt3 })
+
+ expect(metadata1.hash).toBe(metadata2.hash)
+ expect(metadata1.hash).not.toBe(metadata3.hash)
+ })
+
+ it('includes the content from referenced tags into account when calculating the hash', async () => {
+ const parent = 'This is the parent prompt. The end.'
+ const child1 = 'ABCDEFG'
+ const child2 = '1234567'
const metadata1 = await scan({
prompt: parent,
referenceFn: referenceFn({ child: child1 }),
- });
+ })
const metadata2 = await scan({
prompt: parent,
referenceFn: referenceFn({ child: child2 }),
- });
+ })
- expect(metadata1.hash).not.toBe(metadata2.hash);
- });
+ expect(metadata1.hash).not.toBe(metadata2.hash)
+ })
- it("works with multiple levels of nesting", async () => {
+ it('works with multiple levels of nesting', async () => {
const prompts = {
parent: removeCommonIndent(`
Parent:
@@ -81,81 +80,81 @@ describe("hash", async () => {
grandchild2: removeCommonIndent(`
Grandchild 2.
`),
- };
+ }
const parentMetadata1 = await scan({
- prompt: prompts["parent"]!,
+ prompt: prompts['parent']!,
referenceFn: referenceFn({
...prompts,
- grandchild: prompts["grandchild1"],
+ grandchild: prompts['grandchild1'],
}),
- });
+ })
const parentMetadata2 = await scan({
- prompt: prompts["parent"]!,
+ prompt: prompts['parent']!,
referenceFn: referenceFn({
...prompts,
- grandchild: prompts["grandchild2"],
+ grandchild: prompts['grandchild2'],
}),
- });
+ })
- expect(parentMetadata1.hash).not.toBe(parentMetadata2.hash);
- });
+ expect(parentMetadata1.hash).not.toBe(parentMetadata2.hash)
+ })
- it("extract all the paths of the referenced prompts", async () => {
+ it('extract all the paths of the referenced prompts', async () => {
const prompts = {
- rootFile: removeCommonIndent("Root File"),
- "siblingParent/sibling": removeCommonIndent("Sibling Parent File"),
- "somefolder/parent": removeCommonIndent(`
+ rootFile: removeCommonIndent('Root File'),
+ 'siblingParent/sibling': removeCommonIndent('Sibling Parent File'),
+ 'somefolder/parent': removeCommonIndent(`
Parent:
`),
- "somefolder/children/child1": removeCommonIndent(`
+ 'somefolder/children/child1': removeCommonIndent(`
Child 1:
`),
- "somefolder/children/grandchildren/grandchild1": removeCommonIndent(`
+ 'somefolder/children/grandchildren/grandchild1': removeCommonIndent(`
Grandchild 1:
`),
- "somefolder/children/grandchildren/grand-grand-grandChildren/deepestGrandChild":
- removeCommonIndent("Grandchild 2"),
- "somefolder/children/child2": removeCommonIndent(`
+ 'somefolder/children/grandchildren/grand-grand-grandChildren/deepestGrandChild':
+ removeCommonIndent('Grandchild 2'),
+ 'somefolder/children/child2': removeCommonIndent(`
Child 2:
`),
- "somefolder/children/grandchildren/grandchild2":
- removeCommonIndent("Grandchild 2"),
- "somefolder/children/childSibling": removeCommonIndent(`
+ 'somefolder/children/grandchildren/grandchild2':
+ removeCommonIndent('Grandchild 2'),
+ 'somefolder/children/childSibling': removeCommonIndent(`
Link to grand grand child 2:
`),
- };
+ }
const metadata = await scan({
- prompt: prompts["somefolder/parent"],
+ prompt: prompts['somefolder/parent'],
referenceFn: referenceFn(prompts),
- fullPath: "somefolder/parent",
- });
+ fullPath: 'somefolder/parent',
+ })
- const includedPaths = Array.from(metadata.includedPromptPaths);
+ const includedPaths = Array.from(metadata.includedPromptPaths)
expect(includedPaths).toEqual([
- "somefolder/parent",
- "somefolder/children/child1",
- "somefolder/children/grandchildren/grandchild1",
- "somefolder/children/grandchildren/grand-grand-grandChildren/deepestGrandChild",
- "somefolder/children/childSibling",
- "siblingParent/sibling",
- "somefolder/children/child2",
- "somefolder/children/grandchildren/grandchild2",
- ]);
- });
-
- it("works with nested tags", async () => {
+ 'somefolder/parent',
+ 'somefolder/children/child1',
+ 'somefolder/children/grandchildren/grandchild1',
+ 'somefolder/children/grandchildren/grand-grand-grandChildren/deepestGrandChild',
+ 'somefolder/children/childSibling',
+ 'siblingParent/sibling',
+ 'somefolder/children/child2',
+ 'somefolder/children/grandchildren/grandchild2',
+ ])
+ })
+
+ it('works with nested tags', async () => {
const prompts = {
parent: removeCommonIndent(`
{{if foo}}
@@ -173,63 +172,63 @@ describe("hash", async () => {
child2v2: removeCommonIndent(`
baz!
`),
- };
+ }
const parentMetadatav1 = await scan({
- prompt: prompts["parent"]!,
+ prompt: prompts['parent']!,
referenceFn: referenceFn({
...prompts,
- child2: prompts["child2v1"],
+ child2: prompts['child2v1'],
}),
- });
+ })
const parentMetadatav2 = await scan({
- prompt: prompts["parent"]!,
+ prompt: prompts['parent']!,
referenceFn: referenceFn({
...prompts,
- child2: prompts["child2v2"],
+ child2: prompts['child2v2'],
}),
- });
+ })
- expect(parentMetadatav1.hash).not.toBe(parentMetadatav2.hash);
- });
-});
+ expect(parentMetadatav1.hash).not.toBe(parentMetadatav2.hash)
+ })
+})
-describe("resolvedPrompt", async () => {
- it("returns the prompt without comments", async () => {
+describe('resolvedPrompt', async () => {
+ it('returns the prompt without comments', async () => {
const prompt = `
This is a prompt.
/* This is a comment */
This is another prompt.
- `;
+ `
const metadata = await scan({
prompt: removeCommonIndent(prompt),
- });
+ })
expect(metadata.resolvedPrompt).toBe(
- "This is a prompt.\n\nThis is another prompt.",
- );
- });
+ 'This is a prompt.\n\nThis is another prompt.',
+ )
+ })
- it("Replaces reference tags with scope tags", async () => {
+ it('Replaces reference tags with scope tags', async () => {
const prompt = removeCommonIndent(`
This is a prompt.
This is another prompt.
- `);
+ `)
const childPrompt = removeCommonIndent(`
This is the referenced prompt.
{{ bar }}
- `);
+ `)
const metadata = await scan({
prompt,
referenceFn: referenceFn({ child: childPrompt }),
- });
+ })
expect(metadata.resolvedPrompt).toBe(
removeCommonIndent(`
@@ -240,17 +239,17 @@ describe("resolvedPrompt", async () => {
This is another prompt.
`),
- );
- });
+ )
+ })
- it("only includes the parent config", async () => {
+ it('only includes the parent config', async () => {
const prompt = removeCommonIndent(`
---
config: parent
---
- `);
+ `)
const childPrompt = removeCommonIndent(`
---
@@ -259,12 +258,12 @@ describe("resolvedPrompt", async () => {
---
This is the child prompt.
- `);
+ `)
const metadata = await scan({
prompt,
referenceFn: referenceFn({ child: childPrompt }),
- });
+ })
expect(metadata.resolvedPrompt).toBe(
removeCommonIndent(`
@@ -275,13 +274,13 @@ describe("resolvedPrompt", async () => {
This is the child prompt.
`),
- );
- expect(metadata.config).toEqual({ config: "parent" });
- });
-});
+ )
+ expect(metadata.config).toEqual({ config: 'parent' })
+ })
+})
-describe("config", async () => {
- it("compiles the YAML written in the config section and returns it as the config attribute in the result", async () => {
+describe('config', async () => {
+ it('compiles the YAML written in the config section and returns it as the config attribute in the result', async () => {
const prompt = `
---
foo: bar
@@ -289,18 +288,18 @@ describe("config", async () => {
- qux
- quux
---
- `;
+ `
const metadata = await scan({
prompt: removeCommonIndent(prompt),
- });
+ })
expect(metadata.config).toEqual({
- foo: "bar",
- baz: ["qux", "quux"],
- });
- });
+ foo: 'bar',
+ baz: ['qux', 'quux'],
+ })
+ })
- it("does not confuse several dashes with a config section", async () => {
+ it('does not confuse several dashes with a config section', async () => {
const prompt = removeCommonIndent(`
This is not config:
--------------------
@@ -310,18 +309,18 @@ describe("config", async () => {
This ain't either:
--
- `);
+ `)
const metadata = await scan({
prompt: removeCommonIndent(prompt),
- });
+ })
- expect(metadata.errors[0]?.toString()).toBeUndefined();
- expect(metadata.errors.length).toBe(0);
- expect(metadata.config).toEqual({});
- });
+ expect(metadata.errors[0]?.toString()).toBeUndefined()
+ expect(metadata.errors.length).toBe(0)
+ expect(metadata.config).toEqual({})
+ })
- it("can be escaped", async () => {
+ it('can be escaped', async () => {
const prompt = removeCommonIndent(`
This is NOT a config:
\\---
@@ -330,14 +329,14 @@ describe("config", async () => {
- qux
- quux
\\---
- `);
+ `)
- const metadata = await scan({ prompt });
+ const metadata = await scan({ prompt })
- expect(metadata.config).toEqual({});
- });
+ expect(metadata.config).toEqual({})
+ })
- it("fails when there is content before the config section", async () => {
+ it('fails when there is content before the config section', async () => {
const prompt = removeCommonIndent(`
Lorem ipsum
---
@@ -346,32 +345,32 @@ describe("config", async () => {
- qux
- quux
---
- `);
+ `)
- const metadata = await scan({ prompt });
+ const metadata = await scan({ prompt })
- expect(metadata.errors.length).toBe(1);
- expect(metadata.errors[0]).toBeInstanceOf(CompileError);
- expect(metadata.errors[0]!.code).toBe("invalid-config-placement");
- });
+ expect(metadata.errors.length).toBe(1)
+ expect(metadata.errors[0]).toBeInstanceOf(CompileError)
+ expect(metadata.errors[0]!.code).toBe('invalid-config-placement')
+ })
- it("fails when the config is not valid YAML", async () => {
+ it('fails when the config is not valid YAML', async () => {
const prompt = removeCommonIndent(`
---
foo: bar
baa
---
- `);
+ `)
- const metadata = await scan({ prompt });
+ const metadata = await scan({ prompt })
- expect(metadata.config).toEqual({ foo: "bar", baa: null });
- expect(metadata.errors.length).toBe(1);
- expect(metadata.errors[0]).toBeInstanceOf(CompileError);
- expect(metadata.errors[0]!.code).toBe("invalid-config");
- });
+ expect(metadata.config).toEqual({ foo: 'bar', baa: null })
+ expect(metadata.errors.length).toBe(1)
+ expect(metadata.errors[0]).toBeInstanceOf(CompileError)
+ expect(metadata.errors[0]!.code).toBe('invalid-config')
+ })
- it("fails when there are multiple config sections", async () => {
+ it('fails when there are multiple config sections', async () => {
const prompt = removeCommonIndent(`
---
foo: bar
@@ -379,69 +378,69 @@ describe("config", async () => {
---
baz: qux
---
- `);
+ `)
- const metadata = await scan({ prompt });
+ const metadata = await scan({ prompt })
- expect(metadata.errors.length).toBe(1);
- expect(metadata.errors[0]).toBeInstanceOf(CompileError);
- expect(metadata.errors[0]!.code).toBe("config-already-declared");
- });
+ expect(metadata.errors.length).toBe(1)
+ expect(metadata.errors[0]).toBeInstanceOf(CompileError)
+ expect(metadata.errors[0]!.code).toBe('config-already-declared')
+ })
- it("fails when a schema is provided and there is no config section", async () => {
+ it('fails when a schema is provided and there is no config section', async () => {
const prompt = removeCommonIndent(`
Lorem ipsum
- `);
+ `)
const metadata = await scan({
prompt,
configSchema: z.object({
foo: z.string(),
}),
- });
+ })
- expect(metadata.errors.length).toBe(1);
- expect(metadata.errors[0]).toBeInstanceOf(CompileError);
- expect(metadata.errors[0]!.code).toBe("config-not-found");
- });
+ expect(metadata.errors.length).toBe(1)
+ expect(metadata.errors[0]).toBeInstanceOf(CompileError)
+ expect(metadata.errors[0]!.code).toBe('config-not-found')
+ })
- it("fails when the configSchema is not validated", async () => {
+ it('fails when the configSchema is not validated', async () => {
const prompt = removeCommonIndent(`
---
foo: 2
---
- `);
+ `)
const metadata = await scan({
prompt,
configSchema: z.object({
foo: z.string(),
}),
- });
+ })
- expect(metadata.errors.length).toBe(1);
- expect(metadata.errors[0]).toBeInstanceOf(CompileError);
- expect(metadata.errors[0]!.code).toBe("invalid-config");
- });
+ expect(metadata.errors.length).toBe(1)
+ expect(metadata.errors[0]).toBeInstanceOf(CompileError)
+ expect(metadata.errors[0]!.code).toBe('invalid-config')
+ })
- it("does not fail when the config schema is validated", async () => {
+ it('does not fail when the config schema is validated', async () => {
const prompt = removeCommonIndent(`
---
foo: bar
---
- `);
+ `)
const metadata = await scan({
prompt,
configSchema: z.object({
foo: z.string(),
}),
- });
+ })
- expect(metadata.errors.length).toBe(0);
- });
+ expect(metadata.errors.length).toBe(0)
+ })
- it("returns the correct positions of parsing errors", async () => {
+ it('returns the correct positions of parsing errors', async () => {
const prompt = removeCommonIndent(`
/*
Lorem ipsum
@@ -450,100 +449,100 @@ describe("config", async () => {
foo: bar
baa
---
- `);
+ `)
- const expectedErrorPosition = prompt.indexOf("baa");
+ const expectedErrorPosition = prompt.indexOf('baa')
- const metadata = await scan({ prompt });
+ const metadata = await scan({ prompt })
- expect(metadata.errors.length).toBe(1);
- expect(metadata.errors[0]).toBeInstanceOf(CompileError);
- expect(metadata.errors[0]!.code).toBe("invalid-config");
- expect(metadata.errors[0]!.pos).toBe(expectedErrorPosition);
- });
+ expect(metadata.errors.length).toBe(1)
+ expect(metadata.errors[0]).toBeInstanceOf(CompileError)
+ expect(metadata.errors[0]!.code).toBe('invalid-config')
+ expect(metadata.errors[0]!.pos).toBe(expectedErrorPosition)
+ })
- it("returns the correct positions of schema errors", async () => {
+ it('returns the correct positions of schema errors', async () => {
const prompt = removeCommonIndent(`
---
foo: bar
---
- `);
+ `)
const metadata = await scan({
prompt,
configSchema: z.object({
foo: z.number(),
}),
- });
- const expectedErrorPosition = prompt.indexOf("bar");
+ })
+ const expectedErrorPosition = prompt.indexOf('bar')
- expect(metadata.errors.length).toBe(1);
- expect(metadata.errors[0]).toBeInstanceOf(CompileError);
- expect(metadata.errors[0]!.code).toBe("invalid-config");
- expect(metadata.errors[0]!.pos).toBe(expectedErrorPosition);
- });
+ expect(metadata.errors.length).toBe(1)
+ expect(metadata.errors[0]).toBeInstanceOf(CompileError)
+ expect(metadata.errors[0]!.code).toBe('invalid-config')
+ expect(metadata.errors[0]!.pos).toBe(expectedErrorPosition)
+ })
- it("fails when the config section is defined inside an if block", async () => {
+ it('fails when the config section is defined inside an if block', async () => {
const prompt = removeCommonIndent(`
{{ if true }}
---
foo: bar
---
{{ endif }}
- `);
+ `)
- const metadata = await scan({ prompt });
+ const metadata = await scan({ prompt })
- expect(metadata.errors.length).toBe(1);
- expect(metadata.errors[0]).toBeInstanceOf(CompileError);
- expect(metadata.errors[0]!.code).toBe("config-outside-root");
- });
-});
+ expect(metadata.errors.length).toBe(1)
+ expect(metadata.errors[0]).toBeInstanceOf(CompileError)
+ expect(metadata.errors[0]!.code).toBe('config-outside-root')
+ })
+})
-describe("parameters", async () => {
- it("detects undefined variables being used in the prompt", async () => {
+describe('parameters', async () => {
+ it('detects undefined variables being used in the prompt', async () => {
const prompt = `
{{ foo }}
- `;
+ `
const metadata = await scan({
prompt: removeCommonIndent(prompt),
- });
+ })
- expect(metadata.parameters).toEqual(new Set(["foo"]));
- });
+ expect(metadata.parameters).toEqual(new Set(['foo']))
+ })
- it("ignores variables that are defined in the prompt", async () => {
+ it('ignores variables that are defined in the prompt', async () => {
const prompt = `
{{ foo = 5 }}
{{ foo }}
- `;
+ `
const metadata = await scan({
prompt: removeCommonIndent(prompt),
- });
+ })
- expect(metadata.parameters).toEqual(new Set());
- });
+ expect(metadata.parameters).toEqual(new Set())
+ })
- it("adds the correct parameters to the scope context", async () => {
+ it('adds the correct parameters to the scope context', async () => {
const prompt = `
{{ foo }}
{{ bar }}
{{ for val in arr }}
{{ endfor }}
- `;
+ `
const metadata = await scan({
prompt: removeCommonIndent(prompt),
- });
+ })
- expect(metadata.parameters).toEqual(new Set(["foo", "bar", "arr"]));
- });
-});
+ expect(metadata.parameters).toEqual(new Set(['foo', 'bar', 'arr']))
+ })
+})
-describe("referenced prompts", async () => {
- it("does not include parameters from referenced prompts", async () => {
+describe('referenced prompts', async () => {
+ it('does not include parameters from referenced prompts', async () => {
const prompts = {
parent: removeCommonIndent(`
This is the parent prompt.
@@ -554,18 +553,18 @@ describe("referenced prompts", async () => {
child: removeCommonIndent(`
{{ childParam }}
`),
- };
+ }
const metadata = await scan({
- prompt: prompts["parent"]!,
+ prompt: prompts['parent']!,
referenceFn: referenceFn(prompts),
- });
+ })
- expect(metadata.parameters).toContain("parentParam");
- expect(metadata.parameters).not.toContain("childParam");
- });
+ expect(metadata.parameters).toContain('parentParam')
+ expect(metadata.parameters).not.toContain('childParam')
+ })
- it("returns an error if a child param is not included in the reference tag", async () => {
+ it('returns an error if a child param is not included in the reference tag', async () => {
const prompts = {
child: removeCommonIndent(`
{{ childParam }}
@@ -580,87 +579,87 @@ describe("referenced prompts", async () => {
The end.
`),
- };
+ }
const metadataCorrect = await scan({
- prompt: prompts["parentCorrect"]!,
+ prompt: prompts['parentCorrect']!,
referenceFn: referenceFn(prompts),
- });
+ })
- expect(metadataCorrect.errors.length).toBe(0);
+ expect(metadataCorrect.errors.length).toBe(0)
const metadataWrong = await scan({
- prompt: prompts["parentWrong"]!,
+ prompt: prompts['parentWrong']!,
referenceFn: referenceFn(prompts),
- });
+ })
- expect(metadataWrong.errors.length).toBe(1);
- });
-});
+ expect(metadataWrong.errors.length).toBe(1)
+ })
+})
-describe("scope tags", async () => {
- it("can scan prompts with scope tags", async () => {
+describe('scope tags', async () => {
+ it('can scan prompts with scope tags', async () => {
const prompt = removeCommonIndent(`
This is the prompt.
- `);
+ `)
- const metadata = await scan({ prompt });
+ const metadata = await scan({ prompt })
- expect(metadata.parameters).toEqual(new Set());
- });
+ expect(metadata.parameters).toEqual(new Set())
+ })
- it("Does not add parameters from the scope tag to the parent scope", async () => {
+ it('Does not add parameters from the scope tag to the parent scope', async () => {
const prompt = removeCommonIndent(`
{{ foo }}
- `);
+ `)
- const metadata = await scan({ prompt });
+ const metadata = await scan({ prompt })
- expect(metadata.parameters).toEqual(new Set(["bar"]));
- });
+ expect(metadata.parameters).toEqual(new Set(['bar']))
+ })
- it("returns an error when the scope tag is trying to use a variable that is not defined", async () => {
+ it('returns an error when the scope tag is trying to use a variable that is not defined', async () => {
const prompt = removeCommonIndent(`
{{ foo1 }}
{{ foo2 }}
- `);
+ `)
const metadata = await scan({
prompt: removeCommonIndent(prompt),
- });
-
- expect(metadata.parameters).toEqual(new Set(["bar1"]));
- expect(metadata.errors.length).toBe(1);
- expect(metadata.errors[0]).toBeInstanceOf(CompileError);
- const error = metadata.errors[0] as CompileError;
- expect(error.code).toBe("reference-missing-parameter");
- });
-});
-
-describe("syntax errors", async () => {
- it("returns CompileErrors when the prompt syntax is invalid", async () => {
+ })
+
+ expect(metadata.parameters).toEqual(new Set(['bar1']))
+ expect(metadata.errors.length).toBe(1)
+ expect(metadata.errors[0]).toBeInstanceOf(CompileError)
+ const error = metadata.errors[0] as CompileError
+ expect(error.code).toBe('reference-missing-parameter')
+ })
+})
+
+describe('syntax errors', async () => {
+ it('returns CompileErrors when the prompt syntax is invalid', async () => {
const prompt = `
- `;
+ `
const metadata = await scan({
prompt,
- });
+ })
- expect(metadata.errors.length).toBe(1);
- expect(metadata.errors[0]).toBeInstanceOf(CompileError);
- });
+ expect(metadata.errors.length).toBe(1)
+ expect(metadata.errors[0]).toBeInstanceOf(CompileError)
+ })
- it("finds circular references", async () => {
+ it('finds circular references', async () => {
const prompts = {
parent: removeCommonIndent(`
This is the parent prompt.
@@ -671,19 +670,19 @@ describe("syntax errors", async () => {
This is the child prompt.
`),
- };
+ }
const metadata = await scan({
- prompt: prompts["parent"]!,
+ prompt: prompts['parent']!,
referenceFn: referenceFn(prompts),
- });
+ })
- expect(metadata.errors.length).toBe(1);
- expect(metadata.errors[0]).toBeInstanceOf(CompileError);
- expect(metadata.errors[0]!.code).toBe("circular-reference");
- });
+ expect(metadata.errors.length).toBe(1)
+ expect(metadata.errors[0]).toBeInstanceOf(CompileError)
+ expect(metadata.errors[0]!.code).toBe('circular-reference')
+ })
- it("shows errors from referenced prompts as errors in the parent", async () => {
+ it('shows errors from referenced prompts as errors in the parent', async () => {
const prompts = {
parent: removeCommonIndent(`
This is the parent prompt.
@@ -695,35 +694,35 @@ describe("syntax errors", async () => {
Error: (close unopened tag)
${TAG_NAMES.message}>
`),
- };
+ }
const metadata = await scan({
- prompt: prompts["parent"]!,
+ prompt: prompts['parent']!,
referenceFn: referenceFn(prompts),
- });
+ })
- expect(metadata.errors.length).toBe(1);
- expect(metadata.errors[0]).toBeInstanceOf(CompileError);
- expect(metadata.errors[0]!.code).toBe("reference-error");
+ expect(metadata.errors.length).toBe(1)
+ expect(metadata.errors[0]).toBeInstanceOf(CompileError)
+ expect(metadata.errors[0]!.code).toBe('reference-error')
expect(metadata.errors[0]!.message).contains(
- "The referenced prompt contains an error:",
- );
+ 'The referenced prompt contains an error:',
+ )
expect(metadata.errors[0]!.message).contains(
`Unexpected closing tag for ${TAG_NAMES.message}`,
- );
- });
+ )
+ })
- it("allows message tags inside steps", async () => {
+ it('allows message tags inside steps', async () => {
const prompt = removeCommonIndent(`
{{ user_message }}
- `);
+ `)
- const metadata = await scan({ prompt });
+ const metadata = await scan({ prompt })
- expect(metadata.errors.length).toBe(0);
- });
-});
+ expect(metadata.errors.length).toBe(0)
+ })
+})
diff --git a/src/compiler/scan.ts b/src/compiler/scan.ts
index 4dc0519..945da0e 100644
--- a/src/compiler/scan.ts
+++ b/src/compiler/scan.ts
@@ -1,14 +1,14 @@
-import { createHash } from "crypto";
+import { createHash } from 'crypto'
import {
CUSTOM_MESSAGE_ROLE_ATTR,
REFERENCE_DEPTH_LIMIT,
REFERENCE_PROMPT_ATTR,
TAG_NAMES,
-} from "$promptl/constants";
-import CompileError, { error } from "$promptl/error/error";
-import errors from "$promptl/error/errors";
-import parse from "$promptl/parser/index";
+} from '$promptl/constants'
+import CompileError, { error } from '$promptl/error/error'
+import errors from '$promptl/error/errors'
+import parse from '$promptl/parser/index'
import type {
Attribute,
BaseNode,
@@ -16,20 +16,20 @@ import type {
ElementTag,
Fragment,
TemplateNode,
-} from "$promptl/parser/interfaces";
+} from '$promptl/parser/interfaces'
import {
Config,
ContentTypeTagName,
ConversationMetadata,
MessageRole,
-} from "$promptl/types";
-import { Node as LogicalExpression } from "estree";
-import yaml, { Node as YAMLItem } from "yaml";
-import { z } from "zod";
-
-import { updateScopeContextForNode } from "./logic";
-import { ScopeContext } from "./scope";
-import { Document, ReferencePromptFn } from "./types";
+} from '$promptl/types'
+import { Node as LogicalExpression } from 'estree'
+import yaml, { Node as YAMLItem } from 'yaml'
+import { z } from 'zod'
+
+import { updateScopeContextForNode } from './logic'
+import { ScopeContext } from './scope'
+import { Document, ReferencePromptFn } from './types'
import {
findYAMLItemPosition,
isChainStepTag,
@@ -37,37 +37,37 @@ import {
isMessageTag,
isRefTag,
isScopeTag,
-} from "./utils";
+} from './utils'
function copyScopeContext(scopeContext: ScopeContext): ScopeContext {
return {
...scopeContext,
definedVariables: new Set(scopeContext.definedVariables),
- };
+ }
}
export class Scan {
- includedPromptPaths: Set;
+ includedPromptPaths: Set
- private rawText: string;
- private referenceFn?: ReferencePromptFn;
- private fullPath: string;
- private withParameters?: string[];
- private configSchema?: z.ZodType;
+ private rawText: string
+ private referenceFn?: ReferencePromptFn
+ private fullPath: string
+ private withParameters?: string[]
+ private configSchema?: z.ZodType
- private config?: Config;
- private configPosition?: { start: number; end: number };
- private resolvedPrompt: string;
- private resolvedPromptOffset: number = 0;
- private hasContent: boolean = false;
- private stepTagsCount: number = 0;
+ private config?: Config
+ private configPosition?: { start: number; end: number }
+ private resolvedPrompt: string
+ private resolvedPromptOffset: number = 0
+ private hasContent: boolean = false
+ private stepTagsCount: number = 0
- private accumulatedToolCalls: ContentTag[] = [];
- private errors: CompileError[] = [];
+ private accumulatedToolCalls: ContentTag[] = []
+ private errors: CompileError[] = []
- private references: { [from: string]: string[] } = {};
- private referencedHashes: string[] = [];
- private referenceDepth: number = 0;
+ private references: { [from: string]: string[] } = {}
+ private referencedHashes: string[] = []
+ private referenceDepth: number = 0
constructor({
document,
@@ -75,19 +75,19 @@ export class Scan {
withParameters,
configSchema,
}: {
- document: Document;
- referenceFn?: ReferencePromptFn;
- withParameters?: string[];
- configSchema?: z.ZodType;
+ document: Document
+ referenceFn?: ReferencePromptFn
+ withParameters?: string[]
+ configSchema?: z.ZodType
}) {
- this.rawText = document.content;
- this.referenceFn = referenceFn;
- this.fullPath = document.path;
- this.withParameters = withParameters;
- this.configSchema = configSchema;
-
- this.resolvedPrompt = document.content;
- this.includedPromptPaths = new Set([this.fullPath]);
+ this.rawText = document.content
+ this.referenceFn = referenceFn
+ this.fullPath = document.path
+ this.withParameters = withParameters
+ this.configSchema = configSchema
+
+ this.resolvedPrompt = document.content
+ this.includedPromptPaths = new Set([this.fullPath])
}
async run(): Promise {
@@ -97,19 +97,19 @@ export class Scan {
: undefined,
usedUndefinedVariables: new Set(),
definedVariables: new Set(),
- };
+ }
- let fragment: Fragment;
+ let fragment: Fragment
try {
- fragment = parse(this.rawText);
+ fragment = parse(this.rawText)
} catch (e) {
- const parseError = e as CompileError;
+ const parseError = e as CompileError
if (parseError instanceof CompileError) {
- this.errors.push(parseError);
- fragment = parseError.fragment!;
+ this.errors.push(parseError)
+ fragment = parseError.fragment!
} else {
- throw parseError;
+ throw parseError
}
}
@@ -120,39 +120,39 @@ export class Scan {
isInsideMessageTag: false,
isInsideContentTag: false,
isRoot: true,
- });
+ })
if (this.configSchema && !this.config) {
- this.baseNodeError(errors.missingConfig, fragment, { start: 0, end: 0 });
+ this.baseNodeError(errors.missingConfig, fragment, { start: 0, end: 0 })
}
const resolvedPrompt =
Object.keys(this.config ?? {}).length > 0
- ? "---\n" +
- yaml.stringify(this.config, { indent: 2 }) +
- "---\n" +
- this.resolvedPrompt
- : this.resolvedPrompt;
+ ? '---\n' +
+ yaml.stringify(this.config, { indent: 2 }) +
+ '---\n' +
+ this.resolvedPrompt
+ : this.resolvedPrompt
const setConfig = (config: Config) => {
- const start = this.configPosition?.start ?? 0;
- const end = this.configPosition?.end ?? 0;
+ const start = this.configPosition?.start ?? 0
+ const end = this.configPosition?.end ?? 0
if (Object.keys(config).length === 0) {
- return this.rawText.slice(0, start) + this.rawText.slice(end);
+ return this.rawText.slice(0, start) + this.rawText.slice(end)
}
return (
this.rawText.slice(0, start) +
- "---\n" +
+ '---\n' +
yaml.stringify(config, { indent: 2 }) +
- "---\n" +
+ '---\n' +
this.rawText.slice(end)
- );
- };
+ )
+ }
- const contentToHash = [this.rawText, ...this.referencedHashes].join("");
- const hash = createHash("sha256").update(contentToHash).digest("hex");
+ const contentToHash = [this.rawText, ...this.referencedHashes].join('')
+ const hash = createHash('sha256').update(contentToHash).digest('hex')
return {
parameters: new Set([
@@ -166,21 +166,21 @@ export class Scan {
setConfig,
isChain: this.stepTagsCount > 1,
includedPromptPaths: this.includedPromptPaths,
- };
+ }
}
private async updateScopeContext({
node,
scopeContext,
}: {
- node: LogicalExpression;
- scopeContext: ScopeContext;
+ node: LogicalExpression
+ scopeContext: ScopeContext
}): Promise {
await updateScopeContextForNode({
node,
scopeContext,
raiseError: this.expressionError.bind(this),
- });
+ })
}
private async listTagAttributes({
@@ -188,42 +188,42 @@ export class Scan {
scopeContext,
literalAttributes = [], // Tags that don't allow Mustache expressions
}: {
- tagNode: ElementTag;
- scopeContext: ScopeContext;
- literalAttributes?: string[];
+ tagNode: ElementTag
+ scopeContext: ScopeContext
+ literalAttributes?: string[]
}): Promise> {
- const attributeNodes = tagNode.attributes;
- if (attributeNodes.length === 0) return new Set();
+ const attributeNodes = tagNode.attributes
+ if (attributeNodes.length === 0) return new Set()
- const attributes: Set = new Set();
+ const attributes: Set = new Set()
for (const attributeNode of attributeNodes) {
- const { name, value } = attributeNode;
+ const { name, value } = attributeNode
if (value === true) {
- attributes.add(name);
- continue;
+ attributes.add(name)
+ continue
}
if (literalAttributes.includes(name)) {
- if (value.some((node) => node.type === "MustacheTag")) {
+ if (value.some((node) => node.type === 'MustacheTag')) {
this.baseNodeError(
errors.invalidStaticAttribute(name),
- value.find((node) => node.type === "MustacheTag")!,
- );
- continue;
+ value.find((node) => node.type === 'MustacheTag')!,
+ )
+ continue
}
}
for await (const node of value) {
- if (node.type === "MustacheTag") {
- const expression = node.expression;
- await this.updateScopeContext({ node: expression, scopeContext });
+ if (node.type === 'MustacheTag') {
+ const expression = node.expression
+ await this.updateScopeContext({ node: expression, scopeContext })
}
}
- attributes.add(name);
+ attributes.add(name)
}
- return attributes;
+ return attributes
}
private async readBaseMetadata({
@@ -234,14 +234,14 @@ export class Scan {
isInsideContentTag,
isRoot = false,
}: {
- node: TemplateNode;
- scopeContext: ScopeContext;
- isInsideStepTag: boolean;
- isInsideMessageTag: boolean;
- isInsideContentTag: boolean;
- isRoot?: boolean;
+ node: TemplateNode
+ scopeContext: ScopeContext
+ isInsideStepTag: boolean
+ isInsideMessageTag: boolean
+ isInsideContentTag: boolean
+ isRoot?: boolean
}): Promise {
- if (node.type === "Fragment") {
+ if (node.type === 'Fragment') {
for await (const childNode of node.children ?? []) {
await this.readBaseMetadata({
node: childNode,
@@ -250,106 +250,106 @@ export class Scan {
isInsideMessageTag,
isInsideContentTag,
isRoot,
- });
+ })
}
- return;
+ return
}
- if (node.type === "Comment" || node.type === "Config") {
+ if (node.type === 'Comment' || node.type === 'Config') {
/* Remove from the resolved prompt */
- const start = node.start! + this.resolvedPromptOffset;
- const end = node.end! + this.resolvedPromptOffset;
+ const start = node.start! + this.resolvedPromptOffset
+ const end = node.end! + this.resolvedPromptOffset
this.resolvedPrompt =
- this.resolvedPrompt.slice(0, start) + this.resolvedPrompt.slice(end);
- this.resolvedPromptOffset -= end - start;
+ this.resolvedPrompt.slice(0, start) + this.resolvedPrompt.slice(end)
+ this.resolvedPromptOffset -= end - start
}
- if (node.type === "Config") {
+ if (node.type === 'Config') {
if (this.config) {
- this.baseNodeError(errors.configAlreadyDeclared, node);
+ this.baseNodeError(errors.configAlreadyDeclared, node)
}
if (!isRoot) {
- this.baseNodeError(errors.configOutsideRoot, node);
+ this.baseNodeError(errors.configOutsideRoot, node)
}
if (this.hasContent) {
- this.baseNodeError(errors.invalidConfigPlacement, node);
+ this.baseNodeError(errors.invalidConfigPlacement, node)
}
- this.configPosition = { start: node.start!, end: node.end! };
+ this.configPosition = { start: node.start!, end: node.end! }
const parsedYaml = yaml.parseDocument(node.value, {
keepSourceTokens: true,
- });
+ })
- const CONFIG_START_OFFSET = 3; // The config is always offsetted by 3 characters due to the `---`
+ const CONFIG_START_OFFSET = 3 // The config is always offsetted by 3 characters due to the `---`
if (parsedYaml.errors.length) {
parsedYaml.errors.forEach((error) => {
- const [errorStart, errorEnd] = error.pos;
+ const [errorStart, errorEnd] = error.pos
this.baseNodeError(errors.invalidConfig(error.message), node, {
start: node.start! + CONFIG_START_OFFSET + errorStart,
end: node.start! + CONFIG_START_OFFSET + errorEnd,
- });
- });
+ })
+ })
}
- const parsedObj = parsedYaml.toJS() ?? {};
+ const parsedObj = parsedYaml.toJS() ?? {}
try {
- this.configSchema?.parse(parsedObj);
+ this.configSchema?.parse(parsedObj)
} catch (err) {
if (err instanceof z.ZodError) {
err.errors.forEach((error) => {
- const issue = error.message;
+ const issue = error.message
const range = findYAMLItemPosition(
parsedYaml.contents as YAMLItem,
error.path,
- );
+ )
const errorStart = range
? node.start! + CONFIG_START_OFFSET + range[0]
- : node.start!;
+ : node.start!
const errorEnd = range
? node.start! + CONFIG_START_OFFSET + range[1] + 1
- : node.end!;
+ : node.end!
this.baseNodeError(errors.invalidConfig(issue), node, {
start: errorStart,
end: errorEnd,
- });
- });
+ })
+ })
}
}
- this.config = parsedObj;
- return;
+ this.config = parsedObj
+ return
}
- if (node.type === "Text") {
+ if (node.type === 'Text') {
if (node.data.trim()) {
- this.hasContent = true;
+ this.hasContent = true
}
/* do nothing */
- return;
+ return
}
- if (node.type === "Comment") {
+ if (node.type === 'Comment') {
/* do nothing */
- return;
+ return
}
- if (node.type === "MustacheTag") {
- this.hasContent = true;
- const expression = node.expression;
- await this.updateScopeContext({ node: expression, scopeContext });
- return;
+ if (node.type === 'MustacheTag') {
+ this.hasContent = true
+ const expression = node.expression
+ await this.updateScopeContext({ node: expression, scopeContext })
+ return
}
- if (node.type === "IfBlock") {
- await this.updateScopeContext({ node: node.expression, scopeContext });
- const ifScope = copyScopeContext(scopeContext);
- const elseScope = copyScopeContext(scopeContext);
+ if (node.type === 'IfBlock') {
+ await this.updateScopeContext({ node: node.expression, scopeContext })
+ const ifScope = copyScopeContext(scopeContext)
+ const elseScope = copyScopeContext(scopeContext)
for await (const childNode of node.children ?? []) {
await this.readBaseMetadata({
node: childNode,
@@ -357,7 +357,7 @@ export class Scan {
isInsideStepTag,
isInsideMessageTag,
isInsideContentTag,
- });
+ })
}
for await (const childNode of node.else?.children ?? []) {
await this.readBaseMetadata({
@@ -366,15 +366,15 @@ export class Scan {
isInsideStepTag,
isInsideMessageTag,
isInsideContentTag,
- });
+ })
}
- return;
+ return
}
- if (node.type === "ForBlock") {
- await this.updateScopeContext({ node: node.expression, scopeContext });
+ if (node.type === 'ForBlock') {
+ await this.updateScopeContext({ node: node.expression, scopeContext })
- const elseScope = copyScopeContext(scopeContext);
+ const elseScope = copyScopeContext(scopeContext)
for await (const childNode of node.else?.children ?? []) {
await this.readBaseMetadata({
node: childNode,
@@ -382,30 +382,30 @@ export class Scan {
isInsideStepTag,
isInsideMessageTag,
isInsideContentTag,
- });
+ })
}
- const contextVarName = node.context.name;
- const indexVarName = node.index?.name;
+ const contextVarName = node.context.name
+ const indexVarName = node.index?.name
if (scopeContext.definedVariables.has(contextVarName)) {
this.expressionError(
errors.variableAlreadyDeclared(contextVarName),
node.context,
- );
- return;
+ )
+ return
}
if (indexVarName && scopeContext.definedVariables.has(indexVarName)) {
this.expressionError(
errors.variableAlreadyDeclared(indexVarName),
node.index!,
- );
- return;
+ )
+ return
}
- const iterableScope = copyScopeContext(scopeContext);
- iterableScope.definedVariables.add(contextVarName);
+ const iterableScope = copyScopeContext(scopeContext)
+ iterableScope.definedVariables.add(contextVarName)
if (indexVarName) {
- iterableScope.definedVariables.add(indexVarName);
+ iterableScope.definedVariables.add(indexVarName)
}
for await (const childNode of node.children ?? []) {
await this.readBaseMetadata({
@@ -414,33 +414,33 @@ export class Scan {
isInsideStepTag,
isInsideMessageTag,
isInsideContentTag,
- });
+ })
}
- return;
+ return
}
- if (node.type === "ElementTag") {
- this.hasContent = true;
+ if (node.type === 'ElementTag') {
+ this.hasContent = true
if (isContentTag(node)) {
if (isInsideContentTag) {
- this.baseNodeError(errors.contentTagInsideContent, node);
+ this.baseNodeError(errors.contentTagInsideContent, node)
}
if (node.name === ContentTypeTagName.toolCall) {
- this.accumulatedToolCalls.push(node);
+ this.accumulatedToolCalls.push(node)
const attributes = await this.listTagAttributes({
tagNode: node,
scopeContext,
- });
+ })
- if (!attributes.has("id")) {
- this.baseNodeError(errors.toolCallTagWithoutId, node);
+ if (!attributes.has('id')) {
+ this.baseNodeError(errors.toolCallTagWithoutId, node)
}
- if (!attributes.has("name")) {
- this.baseNodeError(errors.toolCallWithoutName, node);
+ if (!attributes.has('name')) {
+ this.baseNodeError(errors.toolCallWithoutName, node)
}
}
@@ -451,42 +451,42 @@ export class Scan {
isInsideStepTag,
isInsideMessageTag,
isInsideContentTag: true,
- });
+ })
}
- return;
+ return
}
if (isMessageTag(node)) {
if (isInsideContentTag || isInsideMessageTag) {
- this.baseNodeError(errors.messageTagInsideMessage, node);
+ this.baseNodeError(errors.messageTagInsideMessage, node)
}
const attributes = await this.listTagAttributes({
tagNode: node,
scopeContext,
- });
+ })
- const role = node.name as MessageRole;
+ const role = node.name as MessageRole
if (node.name === TAG_NAMES.message) {
if (!attributes.has(CUSTOM_MESSAGE_ROLE_ATTR)) {
- this.baseNodeError(errors.messageTagWithoutRole, node);
- return;
+ this.baseNodeError(errors.messageTagWithoutRole, node)
+ return
}
- attributes.delete(CUSTOM_MESSAGE_ROLE_ATTR);
+ attributes.delete(CUSTOM_MESSAGE_ROLE_ATTR)
}
- if (role === MessageRole.tool && !attributes.has("id")) {
- this.baseNodeError(errors.toolMessageWithoutId, node);
- return;
+ if (role === MessageRole.tool && !attributes.has('id')) {
+ this.baseNodeError(errors.toolMessageWithoutId, node)
+ return
}
if (this.accumulatedToolCalls.length > 0) {
this.accumulatedToolCalls.forEach((toolCallNode) => {
- this.baseNodeError(errors.invalidToolCallPlacement, toolCallNode);
- return;
- });
+ this.baseNodeError(errors.invalidToolCallPlacement, toolCallNode)
+ return
+ })
}
- this.accumulatedToolCalls = [];
+ this.accumulatedToolCalls = []
for await (const childNode of node.children ?? []) {
await this.readBaseMetadata({
@@ -495,7 +495,7 @@ export class Scan {
isInsideStepTag,
isInsideMessageTag: true,
isInsideContentTag,
- });
+ })
}
if (
@@ -503,163 +503,163 @@ export class Scan {
this.accumulatedToolCalls.length > 0
) {
this.accumulatedToolCalls.forEach((toolCallNode) => {
- this.baseNodeError(errors.invalidToolCallPlacement, toolCallNode);
- return;
- });
+ this.baseNodeError(errors.invalidToolCallPlacement, toolCallNode)
+ return
+ })
}
- this.accumulatedToolCalls = [];
- return;
+ this.accumulatedToolCalls = []
+ return
}
if (isRefTag(node)) {
if (node.children?.length ?? 0 > 0) {
- this.baseNodeError(errors.referenceTagHasContent, node);
- return;
+ this.baseNodeError(errors.referenceTagHasContent, node)
+ return
}
const attributes = await this.listTagAttributes({
tagNode: node,
scopeContext,
literalAttributes: [REFERENCE_PROMPT_ATTR],
- });
+ })
if (!attributes.has(REFERENCE_PROMPT_ATTR)) {
- this.baseNodeError(errors.referenceTagWithoutPrompt, node);
- return;
+ this.baseNodeError(errors.referenceTagWithoutPrompt, node)
+ return
}
if (!this.referenceFn) {
- this.baseNodeError(errors.missingReferenceFunction, node);
- return;
+ this.baseNodeError(errors.missingReferenceFunction, node)
+ return
}
if (this.referenceDepth > REFERENCE_DEPTH_LIMIT) {
- this.baseNodeError(errors.referenceDepthLimit, node);
- return;
+ this.baseNodeError(errors.referenceDepthLimit, node)
+ return
}
const refPromptAttribute = node.attributes.find(
(attribute: Attribute) => attribute.name === REFERENCE_PROMPT_ATTR,
- ) as Attribute;
+ ) as Attribute
const refPromptPath = (refPromptAttribute.value as TemplateNode[])
.map((node) => node.data)
- .join("");
+ .join('')
- attributes.delete(REFERENCE_PROMPT_ATTR); // The rest of the attributes are used as parameters
+ attributes.delete(REFERENCE_PROMPT_ATTR) // The rest of the attributes are used as parameters
- const currentReferences = this.references[this.fullPath] ?? [];
+ const currentReferences = this.references[this.fullPath] ?? []
- const start = node.start! + this.resolvedPromptOffset;
- const end = node.end! + this.resolvedPromptOffset;
- let resolvedRefPrompt = this.resolvedPrompt.slice(start, end);
+ const start = node.start! + this.resolvedPromptOffset
+ const end = node.end! + this.resolvedPromptOffset
+ let resolvedRefPrompt = this.resolvedPrompt.slice(start, end)
const resolveRef = async () => {
if (!this.referenceFn) {
- this.baseNodeError(errors.missingReferenceFunction, node);
- return;
+ this.baseNodeError(errors.missingReferenceFunction, node)
+ return
}
if (currentReferences.includes(refPromptPath)) {
- this.baseNodeError(errors.circularReference, node);
- return;
+ this.baseNodeError(errors.circularReference, node)
+ return
}
const refDocument = await this.referenceFn(
refPromptPath,
this.fullPath,
- );
+ )
if (!refDocument) {
- this.baseNodeError(errors.referenceNotFound, node);
- return;
+ this.baseNodeError(errors.referenceNotFound, node)
+ return
}
const refScan = new Scan({
document: refDocument,
referenceFn: this.referenceFn,
- });
- refScan.accumulatedToolCalls = this.accumulatedToolCalls;
+ })
+ refScan.accumulatedToolCalls = this.accumulatedToolCalls
refScan.references = {
...this.references,
[this.fullPath]: [...currentReferences, refPromptPath],
- };
+ }
- this.includedPromptPaths.add(refDocument.path);
+ this.includedPromptPaths.add(refDocument.path)
- refScan.referenceDepth = this.referenceDepth + 1;
+ refScan.referenceDepth = this.referenceDepth + 1
- const refPromptMetadata = await refScan.run();
+ const refPromptMetadata = await refScan.run()
refPromptMetadata.includedPromptPaths.forEach((path) => {
- this.includedPromptPaths.add(path);
- });
+ this.includedPromptPaths.add(path)
+ })
refPromptMetadata.parameters.forEach((paramName: string) => {
if (!attributes.has(paramName)) {
this.baseNodeError(
errors.referenceMissingParameter(paramName),
node,
- );
+ )
}
- });
+ })
refPromptMetadata.errors.forEach((error: CompileError) => {
if (
- error.code === "reference-error" ||
- error.code === "circular-reference"
+ error.code === 'reference-error' ||
+ error.code === 'circular-reference'
) {
this.baseNodeError(
{ code: error.code, message: error.message },
node,
- );
- return;
+ )
+ return
}
- this.baseNodeError(errors.referenceError(error), node);
- });
- this.accumulatedToolCalls = refScan.accumulatedToolCalls;
- this.referencedHashes.push(refPromptMetadata.hash);
- resolvedRefPrompt = refScan.resolvedPrompt;
- };
+ this.baseNodeError(errors.referenceError(error), node)
+ })
+ this.accumulatedToolCalls = refScan.accumulatedToolCalls
+ this.referencedHashes.push(refPromptMetadata.hash)
+ resolvedRefPrompt = refScan.resolvedPrompt
+ }
try {
- await resolveRef();
+ await resolveRef()
} catch (error: unknown) {
- this.baseNodeError(errors.referenceError(error), node);
+ this.baseNodeError(errors.referenceError(error), node)
}
- const pretext = this.resolvedPrompt.slice(0, start);
- const posttext = this.resolvedPrompt.slice(end);
+ const pretext = this.resolvedPrompt.slice(0, start)
+ const posttext = this.resolvedPrompt.slice(end)
const attributeTags = node.attributes
.filter((a) => a.name !== REFERENCE_PROMPT_ATTR)
.map((attr) => {
- const attrStart = attr.start! + this.resolvedPromptOffset;
- const attrEnd = attr.end! + this.resolvedPromptOffset;
- return this.resolvedPrompt.slice(attrStart, attrEnd);
- });
+ const attrStart = attr.start! + this.resolvedPromptOffset
+ const attrEnd = attr.end! + this.resolvedPromptOffset
+ return this.resolvedPrompt.slice(attrStart, attrEnd)
+ })
const resolvedNode =
- `<${TAG_NAMES.scope} ${attributeTags.join(" ")}>` +
+ `<${TAG_NAMES.scope} ${attributeTags.join(' ')}>` +
resolvedRefPrompt +
- `${TAG_NAMES.scope}>`;
+ `${TAG_NAMES.scope}>`
- this.resolvedPrompt = pretext + resolvedNode + posttext;
- this.resolvedPromptOffset += resolvedNode.length - (end - start);
+ this.resolvedPrompt = pretext + resolvedNode + posttext
+ this.resolvedPromptOffset += resolvedNode.length - (end - start)
- return;
+ return
}
if (isScopeTag(node)) {
const attributes = await this.listTagAttributes({
tagNode: node,
scopeContext,
- });
+ })
const newScopeContext: ScopeContext = {
onlyPredefinedVariables: scopeContext.onlyPredefinedVariables,
usedUndefinedVariables: new Set(),
definedVariables: attributes,
- };
+ }
for await (const childNode of node.children ?? []) {
await this.readBaseMetadata({
@@ -668,32 +668,29 @@ export class Scan {
isInsideStepTag,
isInsideMessageTag,
isInsideContentTag,
- });
+ })
}
newScopeContext.usedUndefinedVariables.forEach((variable) => {
if (!attributes.has(variable)) {
- this.baseNodeError(
- errors.referenceMissingParameter(variable),
- node,
- );
+ this.baseNodeError(errors.referenceMissingParameter(variable), node)
}
- });
+ })
- return;
+ return
}
if (isChainStepTag(node)) {
- this.stepTagsCount += 1;
+ this.stepTagsCount += 1
if (isInsideStepTag) {
- this.baseNodeError(errors.stepTagInsideStep, node);
+ this.baseNodeError(errors.stepTagInsideStep, node)
}
const attributes = await this.listTagAttributes({
tagNode: node,
scopeContext,
- literalAttributes: ["as", "raw"],
- });
+ literalAttributes: ['as', 'raw'],
+ })
for await (const childNode of node.children ?? []) {
await this.readBaseMetadata({
@@ -702,35 +699,35 @@ export class Scan {
isInsideStepTag: true,
isInsideMessageTag,
isInsideContentTag,
- });
+ })
}
- if (attributes.has("as")) {
- const asAttribute = node.attributes.find((a) => a.name === "as")!;
+ if (attributes.has('as')) {
+ const asAttribute = node.attributes.find((a) => a.name === 'as')!
if (asAttribute.value !== true) {
- const asValue = asAttribute.value.map((n) => n.data).join("");
- scopeContext.definedVariables.add(asValue);
+ const asValue = asAttribute.value.map((n) => n.data).join('')
+ scopeContext.definedVariables.add(asValue)
}
}
- if (attributes.has("raw")) {
- const rawAttribute = node.attributes.find((a) => a.name === "raw")!;
+ if (attributes.has('raw')) {
+ const rawAttribute = node.attributes.find((a) => a.name === 'raw')!
if (rawAttribute.value !== true) {
- const asValue = rawAttribute.value.map((n) => n.data).join("");
- scopeContext.definedVariables.add(asValue);
+ const asValue = rawAttribute.value.map((n) => n.data).join('')
+ scopeContext.definedVariables.add(asValue)
}
}
- return;
+ return
}
// Should not be reachable, as non-recognized tags are caught by the parser
- this.baseNodeError(errors.unknownTag(node.name), node);
- return;
+ this.baseNodeError(errors.unknownTag(node.name), node)
+ return
}
//@ts-ignore - Linter knows this should be unreachable. That's what this error is for.
- this.baseNodeError(errors.unsupportedBaseNodeType(node.type), node);
+ this.baseNodeError(errors.unsupportedBaseNodeType(node.type), node)
}
private baseNodeError(
@@ -740,14 +737,14 @@ export class Scan {
): void {
try {
error(message, {
- name: "CompileError",
+ name: 'CompileError',
code,
- source: this.rawText || "",
+ source: this.rawText || '',
start: customPos?.start || node.start || 0,
end: customPos?.end || node.end || undefined,
- });
+ })
} catch (error) {
- this.errors.push(error as CompileError);
+ this.errors.push(error as CompileError)
}
}
@@ -755,28 +752,27 @@ export class Scan {
{ code, message }: { code: string; message: string },
node: LogicalExpression,
): void {
- const source = (node.loc?.source ?? this.rawText)!.split("\n");
+ const source = (node.loc?.source ?? this.rawText)!.split('\n')
const start =
source
.slice(0, node.loc?.start.line! - 1)
.reduce((acc, line) => acc + line.length + 1, 0) +
- node.loc?.start.column!;
+ node.loc?.start.column!
const end =
source
.slice(0, node.loc?.end.line! - 1)
- .reduce((acc, line) => acc + line.length + 1, 0) +
- node.loc?.end.column!;
+ .reduce((acc, line) => acc + line.length + 1, 0) + node.loc?.end.column!
try {
error(message, {
- name: "CompileError",
+ name: 'CompileError',
code,
- source: this.rawText || "",
+ source: this.rawText || '',
start,
end,
- });
+ })
} catch (error) {
- this.errors.push(error as CompileError);
+ this.errors.push(error as CompileError)
}
}
}
diff --git a/src/compiler/scope.ts b/src/compiler/scope.ts
index ab796e5..eb21365 100644
--- a/src/compiler/scope.ts
+++ b/src/compiler/scope.ts
@@ -22,8 +22,11 @@ export default class Scope {
* Local pointers
* Every scope has its own local pointers that contains the indexes of the variables in the global stash.
*/
- private globalStash: ScopeStash = [] // Stash of every variable value in the global scope
- private localPointers: ScopePointers = {} // Index of every variable in the stash in the current scope
+
+ // Stash of every variable value in the global scope
+ private globalStash: ScopeStash = []
+ // Index of every variable in the stash in the current scope
+ private localPointers: ScopePointers = {}
constructor(initialState: Record = {}) {
for (const [key, value] of Object.entries(initialState)) {
@@ -37,27 +40,17 @@ export default class Scope {
return scope
}
- private readFromStash(index: number): unknown {
- return this.globalStash[index]
- }
-
- private addToStash(value: unknown): number {
- this.globalStash.push(value)
- return this.globalStash.length - 1
- }
-
- private modifyStash(index: number, value: unknown): void {
- this.globalStash[index] = value
- }
-
exists(name: string): boolean {
return name in this.localPointers
}
get(name: string): unknown {
const index = this.localPointers[name] ?? undefined
- if (index === undefined)
+
+ if (index === undefined) {
throw new Error(`Variable '${name}' does not exist`)
+ }
+
return this.readFromStash(index)
}
@@ -88,10 +81,37 @@ export default class Scope {
setPointers(pointers: ScopePointers): void {
this.localPointers = pointers
}
+
+ setStash(stash: ScopeStash): void {
+ this.globalStash = stash
+ }
+
+ serialize(): { stash: ScopeStash; pointers: ScopePointers } {
+ return {
+ stash: this.globalStash,
+ pointers: this.localPointers,
+ }
+ }
+
+ private readFromStash(index: number): unknown {
+ return this.globalStash[index]
+ }
+
+ private addToStash(value: unknown): number {
+ this.globalStash.push(value)
+ return this.globalStash.length - 1
+ }
+
+ private modifyStash(index: number, value: unknown): void {
+ this.globalStash[index] = value
+ }
}
export type ScopeContext = {
- onlyPredefinedVariables?: Set // If defined, all usedUndefinedVariables that are not in this set will return an error
- usedUndefinedVariables: Set // Variables that are not in current scope but have been used
- definedVariables: Set // Variables that are in current scope
+ // If defined, all usedUndefinedVariables that are not in this set will return an error
+ onlyPredefinedVariables?: Set
+ // Variables that are not in current scope but have been used
+ usedUndefinedVariables: Set
+ // Variables that are in current scope
+ definedVariables: Set
}
diff --git a/src/compiler/test/helpers.ts b/src/compiler/test/helpers.ts
index cc7bd67..25f2231 100644
--- a/src/compiler/test/helpers.ts
+++ b/src/compiler/test/helpers.ts
@@ -38,21 +38,23 @@ export async function complete({
steps: number
}> {
let steps = 0
-
let responseMessage: Omit | undefined
+
while (true) {
- const { completed, messages, config } =
- await chain.step(responseMessage)
+ const { completed, messages, config } = await chain.step(responseMessage)
- if (completed)
+ if (completed) {
return {
messages,
config,
steps,
response: responseMessage!.content as MessageContent[],
}
+ }
- const response = callback ? await callback({ messages, config }) : 'RESPONSE'
+ const response = callback
+ ? await callback({ messages, config })
+ : 'RESPONSE'
responseMessage = { content: [{ type: ContentType.text, text: response }] }
steps++
diff --git a/src/compiler/utils.ts b/src/compiler/utils.ts
index 3a7bab1..ea31e0e 100644
--- a/src/compiler/utils.ts
+++ b/src/compiler/utils.ts
@@ -22,17 +22,20 @@ export async function hasContent(iterable: Iterable) {
}
export function getCommonIndent(text: string): number {
- return text.split('\n').reduce((acc: number | null, line: string) => {
- if (line.trim() === '') return acc
- const indent = line.match(/^\s*/)![0]
- if (acc === null) return indent.length
- return indent.length < acc ? indent.length : acc
- }, null) ?? 0
+ return (
+ text.split('\n').reduce((acc: number | null, line: string) => {
+ if (line.trim() === '') return acc
+ const indent = line.match(/^\s*/)![0]
+ if (acc === null) return indent.length
+ return indent.length < acc ? indent.length : acc
+ }, null) ?? 0
+ )
}
export function removeCommonIndent(text: string): string {
const indent = getCommonIndent(text)
- return text.split('\n')
+ return text
+ .split('\n')
.map((line) => line.slice(indent))
.join('\n')
.trim()
diff --git a/src/parser/parser.test.ts b/src/parser/parser.test.ts
index 90984c1..a6b50a9 100644
--- a/src/parser/parser.test.ts
+++ b/src/parser/parser.test.ts
@@ -115,12 +115,12 @@ describe('Tags', async () => {
it('parses tags with unknown tag names as plain text even if the name starts like a known tag', async () => {
const fragment1 = parse('')
const fragment2 = parse('')
-
+
expect(fragment1.children.length).toBe(1)
const node1 = fragment1.children[0]!
expect(node1.type).toBe('Text')
expect(node1.data).toBe('')
-
+
expect(fragment2.children.length).toBe(1)
const node2 = fragment2.children[0]!
expect(node2.type).toBe('Text')
diff --git a/src/providers/adapter.ts b/src/providers/adapter.ts
index 28f37c2..514ec19 100644
--- a/src/providers/adapter.ts
+++ b/src/providers/adapter.ts
@@ -1,3 +1,4 @@
+import { AdapterKey } from '$promptl/providers'
import { Message, Conversation as PromptlConversation } from '$promptl/types'
export type ProviderConversation = {
@@ -6,11 +7,13 @@ export type ProviderConversation = {
}
export type ProviderAdapter = {
+ type: AdapterKey
toPromptl(conversation: ProviderConversation): PromptlConversation
fromPromptl(conversation: PromptlConversation): ProviderConversation
}
export const defaultAdapter: ProviderAdapter = {
+ type: 'default',
toPromptl: (c) => c,
fromPromptl: (c) => c,
}
diff --git a/src/providers/anthropic/adapter.ts b/src/providers/anthropic/adapter.ts
index e67d23a..8b9797a 100644
--- a/src/providers/anthropic/adapter.ts
+++ b/src/providers/anthropic/adapter.ts
@@ -26,6 +26,7 @@ import {
} from './types'
export const AnthropicAdapter: ProviderAdapter = {
+ type: 'anthropic',
fromPromptl(
promptlConversation: PromptlConversation,
): ProviderConversation {
@@ -158,11 +159,11 @@ function toAnthropicFile(
...rest,
type: AnthropicContentType.document,
source: {
- type: 'base64',
+ type: 'base64',
media_type: mimeType,
data: file.toString('base64'),
},
- }
+ }
}
return {
diff --git a/src/providers/index.ts b/src/providers/index.ts
index 24c208b..e8311c5 100644
--- a/src/providers/index.ts
+++ b/src/providers/index.ts
@@ -10,6 +10,7 @@ export const Adapters = {
anthropic: AnthropicAdapter,
} as const
+export type AdapterKey = keyof typeof Adapters
export type AdapterMessageType<
T extends keyof typeof Adapters = keyof typeof Adapters,
> = ReturnType<(typeof Adapters)[T]['fromPromptl']>['messages'][number]
diff --git a/src/providers/openai/adapter.ts b/src/providers/openai/adapter.ts
index 6b4abf1..892aa52 100644
--- a/src/providers/openai/adapter.ts
+++ b/src/providers/openai/adapter.ts
@@ -23,6 +23,7 @@ import {
} from './types'
export const OpenAIAdapter: ProviderAdapter = {
+ type: 'openai',
fromPromptl(
promptlConversation: PromptlConversation,
): ProviderConversation {
@@ -54,7 +55,7 @@ function toOpenAiFile(
type: OpenAIContentType.input_audio,
data: file.toString('base64'),
format: mimeType.split('/').at(-1)!,
- }
+ }
}
return {
diff --git a/src/types/index.ts b/src/types/index.ts
index 2064179..6e13828 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -20,4 +20,5 @@ export type ConversationMetadata = {
includedPromptPaths: Set
}
+export { type SerializedChain } from '$promptl/compiler'
export * from './message'
diff --git a/src/types/message.ts b/src/types/message.ts
index fd18956..204f2a0 100644
--- a/src/types/message.ts
+++ b/src/types/message.ts
@@ -50,7 +50,11 @@ export type ToolCallContent = {
toolArguments: Record
}
-export type MessageContent = TextContent | ImageContent | FileContent | ToolCallContent
+export type MessageContent =
+ | TextContent
+ | ImageContent
+ | FileContent
+ | ToolCallContent
/* Message */