From 185a65524ef4975f808c4dd0aa55de05fd24220a Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Thu, 9 Apr 2026 10:22:02 -0400 Subject: [PATCH] chore: replace ESLint with Biome for linting and formatting Replace ESLint with Biome for faster, unified linting and formatting. Add grit plugins (no-iife, use-named-arguments, prefer-nullish) from openrouter-web. Update CI workflow to run biome check on PRs. --- .github/workflows/ci.yaml | 9 +- .grit/no-iife.grit | 25 + ...prefer-nullish-over-optional-nullable.grit | 24 + .grit/use-named-arguments.grit | 45 + biome.json | 136 +++ eslint.config.mjs | 29 - package.json | 8 +- pnpm-lock.yaml | 869 ++---------------- src/index.ts | 97 +- src/inner-loop/call-model.ts | 6 +- src/lib/anthropic-compat.test.ts | 3 +- src/lib/anthropic-compat.ts | 9 +- src/lib/claude-type-guards.ts | 36 +- src/lib/conversation-state.ts | 12 +- src/lib/model-result.ts | 143 +-- src/lib/next-turn-params.test.ts | 19 +- src/lib/next-turn-params.ts | 3 +- src/lib/reusable-stream.ts | 1 + src/lib/stream-transformers.ts | 3 +- src/lib/tool-context.ts | 3 +- src/lib/tool-executor.ts | 10 +- src/lib/tool-orchestrator.ts | 8 +- src/lib/tool-types.ts | 5 +- src/lib/tool.ts | 20 +- src/lib/turn-context.ts | 3 +- src/openrouter.ts | 7 +- tests/e2e/call-model-state.test.ts | 10 +- tests/e2e/call-model.test.ts | 41 +- tests/e2e/multi-turn-tool-state.test.ts | 23 +- .../get-items-stream-manual-tools.test.ts | 31 +- .../manual-tool-calls-adversarial.test.ts | 379 ++++++-- tests/unit/openrouter.test.ts | 24 +- tests/unit/shared-context.test.ts | 3 +- tests/unit/tool-context.test.ts | 3 +- tests/unit/tool-event-broadcaster.test.ts | 21 +- tests/unit/tool-executor-return.test.ts | 3 +- tests/unit/tool-type-compat.test.ts | 2 +- tests/unit/turn-end-race-condition.test.ts | 4 + 38 files changed, 959 insertions(+), 1118 deletions(-) create mode 100644 .grit/no-iife.grit create mode 100644 .grit/prefer-nullish-over-optional-nullable.grit create mode 100644 .grit/use-named-arguments.grit create mode 100644 biome.json delete mode 100644 eslint.config.mjs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1a39cd4..106ab78 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,8 +19,11 @@ jobs: - run: pnpm install --frozen-lockfile - - run: pnpm run lint + - name: Biome check + run: pnpm run lint - - run: pnpm run typecheck + - name: TypeScript typecheck + run: pnpm run typecheck - - run: pnpm run test + - name: Tests + run: pnpm run test diff --git a/.grit/no-iife.grit b/.grit/no-iife.grit new file mode 100644 index 0000000..7279a26 --- /dev/null +++ b/.grit/no-iife.grit @@ -0,0 +1,25 @@ +engine biome(2.0) +language js(typescript, jsx) + +or { + // Match: (function() { ... })() or (function(args) { ... })() + `($func)()` as $match where { + $func <: or { + `function() { $body }`, + `function($args) { $body }`, + `async function() { $body }`, + `async function($args) { $body }` + }, + register_diagnostic(span=$match, message="Avoid using IIFEs. Consider extracting to a helper function.", severity="error") + }, + // Match: (() => { ... })() or ((args) => { ... })() + `($arrow)()` as $match where { + $arrow <: or { + `() => $body`, + `($args) => $body`, + `async () => $body`, + `async ($args) => $body` + }, + register_diagnostic(span=$match, message="Avoid using IIFEs. Consider extracting to a helper function.", severity="error") + } +} diff --git a/.grit/prefer-nullish-over-optional-nullable.grit b/.grit/prefer-nullish-over-optional-nullable.grit new file mode 100644 index 0000000..166b6ef --- /dev/null +++ b/.grit/prefer-nullish-over-optional-nullable.grit @@ -0,0 +1,24 @@ +// Pattern: Detect .optional().nullable() or .nullable().optional() on zod schemas +// These should be replaced with .nullish() for better clarity and correctness +// Matches both direct chains and chains with other method calls in between +// Only matches expressions that start with z. to ensure we're targeting zod schemas + +engine biome(2.0) +language js(typescript, jsx) + +or { + // Direct chains starting from a zod constructor + `z.$fn($...).nullable().optional()` as $match where { + register_diagnostic(span=$match, message="Use .nullish() instead of .nullable().optional(). The .nullish() method is equivalent and more concise.", severity="error") + }, + `z.$fn($...).optional().nullable()` as $match where { + register_diagnostic(span=$match, message="Use .nullish() instead of .optional().nullable(). The .nullish() method is equivalent and more concise.", severity="error") + }, + // Allow one method in between (covers common cases like .default(), .array(), .openapi(), etc.) + `z.$fn($...).$mid.nullable().optional()` as $match where { + register_diagnostic(span=$match, message="Use .nullish() instead of .nullable().optional() with extra chaining. The .nullish() method is equivalent and more concise.", severity="error") + }, + `z.$fn($...).$mid.optional().nullable()` as $match where { + register_diagnostic(span=$match, message="Use .nullish() instead of .optional().nullable() with extra chaining. The .nullish() method is equivalent and more concise.", severity="error") + } +} diff --git a/.grit/use-named-arguments.grit b/.grit/use-named-arguments.grit new file mode 100644 index 0000000..7335eae --- /dev/null +++ b/.grit/use-named-arguments.grit @@ -0,0 +1,45 @@ +engine biome(2.0) +language js(typescript, jsx) + +or { + // Function declarations + `function $name($a, $b, $c, $d, $...) { $body }` as $match where { + register_diagnostic(span=$match, message="Functions with more than 3 parameters should use named arguments with object destructuring", severity="error") + }, + // Function declarations with return type + `function $name($a, $b, $c, $d, $...): $ret { $body }` as $match where { + register_diagnostic(span=$match, message="Functions with more than 3 parameters should use named arguments with object destructuring", severity="error") + }, + // Async function declarations + `async function $name($a, $b, $c, $d, $...) { $body }` as $match where { + register_diagnostic(span=$match, message="Functions with more than 3 parameters should use named arguments with object destructuring", severity="error") + }, + // Async function declarations with return type + `async function $name($a, $b, $c, $d, $...): $ret { $body }` as $match where { + register_diagnostic(span=$match, message="Functions with more than 3 parameters should use named arguments with object destructuring", severity="error") + }, + // Function expressions + `function($a, $b, $c, $d, $...) { $body }` as $match where { + register_diagnostic(span=$match, message="Functions with more than 3 parameters should use named arguments with object destructuring", severity="error") + }, + // Function expressions with return type + `function($a, $b, $c, $d, $...): $ret { $body }` as $match where { + register_diagnostic(span=$match, message="Functions with more than 3 parameters should use named arguments with object destructuring", severity="error") + }, + // Async function expressions + `async function($a, $b, $c, $d, $...) { $body }` as $match where { + register_diagnostic(span=$match, message="Functions with more than 3 parameters should use named arguments with object destructuring", severity="error") + }, + // Async function expressions with return type + `async function($a, $b, $c, $d, $...): $ret { $body }` as $match where { + register_diagnostic(span=$match, message="Functions with more than 3 parameters should use named arguments with object destructuring", severity="error") + }, + // Arrow functions + `($a, $b, $c, $d, $...) => $body` as $match where { + register_diagnostic(span=$match, message="Functions with more than 3 parameters should use named arguments with object destructuring", severity="error") + }, + // Arrow functions with return type + `($a, $b, $c, $d, $...): $ret => $body` as $match where { + register_diagnostic(span=$match, message="Functions with more than 3 parameters should use named arguments with object destructuring", severity="error") + } +} diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..2d7c7af --- /dev/null +++ b/biome.json @@ -0,0 +1,136 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.8/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true, + "defaultBranch": "origin/main" + }, + "plugins": [ + ".grit/no-iife.grit", + ".grit/use-named-arguments.grit", + ".grit/prefer-nullish-over-optional-nullable.grit" + ], + "files": { + "ignoreUnknown": false, + "includes": [ + "**", + "!**/*.json", + "!!**/biome.json", + "!esm/**" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100, + "expand": "always", + "attributePosition": "multiline" + }, + "assist": { + "actions": { + "source": { + "organizeImports": { + "level": "on" + } + } + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "complexity": { + "useLiteralKeys": "off", + "noExtraBooleanCast": "off", + "noForEach": "off", + "noBannedTypes": "error", + "noUselessSwitchCase": "off" + }, + "style": { + "noNonNullAssertion": "off", + "useNodejsImportProtocol": "error", + "useTemplate": "off", + "useBlockStatements": "error", + "noParameterAssign": "error", + "useConst": "error", + "useAsConstAssertion": "error", + "useDefaultParameterLast": "error", + "useEnumInitializers": "error", + "useSelfClosingElements": "error", + "useSingleVarDeclarator": "error", + "noUnusedTemplateLiteral": "error", + "useNumberNamespace": "error", + "noInferrableTypes": "error", + "noUselessElse": "error", + "useImportType": { + "level": "on", + "options": { + "style": "separatedType" + } + } + }, + "correctness": { + "noUnusedImports": "error", + "useExhaustiveDependencies": "off", + "noInnerDeclarations": "error", + "useParseIntRadix": "error" + }, + "suspicious": { + "noExplicitAny": "off", + "noAssignInExpressions": "error", + "noAsyncPromiseExecutor": "off", + "noFallthroughSwitchClause": "error", + "noConsole": "off", + "noDoubleEquals": { + "level": "error", + "options": { + "ignoreNull": false + } + }, + "noExtraNonNullAssertion": "error" + }, + "performance": { + "recommended": true, + "noAccumulatingSpread": "error" + }, + "security": { + "recommended": true + } + } + }, + "javascript": { + "formatter": { + "expand": "always", + "lineWidth": 100, + "arrowParentheses": "always", + "quoteProperties": "asNeeded", + "trailingCommas": "all", + "semicolons": "always", + "bracketSpacing": true, + "bracketSameLine": false, + "quoteStyle": "single", + "enabled": true + } + }, + "overrides": [ + { + "includes": [ + "tests/**", + "**/*.test.ts", + "**/*.spec.ts" + ], + "linter": { + "rules": { + "suspicious": { + "noExplicitAny": "off" + }, + "correctness": { + "useYield": "off" + } + } + } + } + ] +} diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index 7a12d55..0000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,29 +0,0 @@ -import pluginJs from '@eslint/js'; -import globals from 'globals'; -import tseslint from 'typescript-eslint'; - -/** @type {import('eslint').Linter.Config[]} */ -export default [ - { - files: [ - '**/*.{js,mjs,cjs,ts}', - ], - }, - { - languageOptions: { - globals: globals.browser, - }, - }, - pluginJs.configs.recommended, - ...tseslint.configs.recommended, - { - rules: { - 'no-constant-condition': 'off', - 'no-useless-escape': 'off', - '@typescript-eslint/no-unused-vars': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-empty-object-type': 'off', - '@typescript-eslint/no-namespace': 'off', - }, - }, -]; diff --git a/package.json b/package.json index b0fe96c..9a6f274 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,8 @@ "LICENSE" ], "scripts": { - "lint": "eslint --cache --max-warnings=0 src", + "lint": "biome check src tests", + "lint:fix": "biome check --write src tests", "build": "tsc", "test": "vitest --run --project unit", "test:e2e": "vitest --run --project e2e", @@ -117,15 +118,12 @@ "zod": "^4.0.0" }, "devDependencies": { + "@biomejs/biome": "^2.4.10", "@changesets/changelog-github": "^0.6.0", "@changesets/cli": "^2.30.0", - "@eslint/js": "^9.19.0", "@types/node": "^22.13.12", "dotenv": "^16.4.7", - "eslint": "^9.19.0", - "globals": "^15.14.0", "typescript": "~5.8.3", - "typescript-eslint": "^8.26.0", "vitest": "^3.2.4" }, "pnpm": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4a0ed3..5fe5cb5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,33 +15,24 @@ importers: specifier: ^4.0.0 version: 4.3.6 devDependencies: + '@biomejs/biome': + specifier: ^2.4.10 + version: 2.4.10 '@changesets/changelog-github': specifier: ^0.6.0 version: 0.6.0 '@changesets/cli': specifier: ^2.30.0 version: 2.30.0(@types/node@22.19.15) - '@eslint/js': - specifier: ^9.19.0 - version: 9.39.4 '@types/node': specifier: ^22.13.12 version: 22.19.15 dotenv: specifier: ^16.4.7 version: 16.6.1 - eslint: - specifier: ^9.19.0 - version: 9.39.4 - globals: - specifier: ^15.14.0 - version: 15.15.0 typescript: specifier: ~5.8.3 version: 5.8.3 - typescript-eslint: - specifier: ^8.26.0 - version: 8.58.0(eslint@9.39.4)(typescript@5.8.3) vitest: specifier: ^3.2.4 version: 3.2.4(@types/node@22.19.15) @@ -52,6 +43,59 @@ packages: resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} + '@biomejs/biome@2.4.10': + resolution: {integrity: sha512-xxA3AphFQ1geij4JTHXv4EeSTda1IFn22ye9LdyVPoJU19fNVl0uzfEuhsfQ4Yue/0FaLs2/ccVi4UDiE7R30w==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.4.10': + resolution: {integrity: sha512-vuzzI1cWqDVzOMIkYyHbKqp+AkQq4K7k+UCXWpkYcY/HDn1UxdsbsfgtVpa40shem8Kax4TLDLlx8kMAecgqiw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.4.10': + resolution: {integrity: sha512-14fzASRo+BPotwp7nWULy2W5xeUyFnTaq1V13Etrrxkrih+ez/2QfgFm5Ehtf5vSjtgx/IJycMMpn5kPd5ZNaA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.4.10': + resolution: {integrity: sha512-WrJY6UuiSD/Dh+nwK2qOTu8kdMDlLV3dLMmychIghHPAysWFq1/DGC1pVZx8POE3ZkzKR3PUUnVrtZfMfaJjyQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@2.4.10': + resolution: {integrity: sha512-7MH1CMW5uuxQ/s7FLST63qF8B3Hgu2HRdZ7tA1X1+mk+St4JOuIrqdhIBnnyqeyWJNI+Bww7Es5QZ0wIc1Cmkw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@2.4.10': + resolution: {integrity: sha512-kDTi3pI6PBN6CiczsWYOyP2zk0IJI08EWEQyDMQWW221rPaaEz6FvjLhnU07KMzLv8q3qSuoB93ua6inSQ55Tw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@2.4.10': + resolution: {integrity: sha512-tZLvEEi2u9Xu1zAqRjTcpIDGVtldigVvzug2fTuPG0ME/g8/mXpRPcNgLB22bGn6FvLJpHHnqLnwliOu8xjYrg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@2.4.10': + resolution: {integrity: sha512-umwQU6qPzH+ISTf/eHyJ/QoQnJs3V9Vpjz2OjZXe9MVBZ7prgGafMy7yYeRGnlmDAn87AKTF3Q6weLoMGpeqdQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.4.10': + resolution: {integrity: sha512-aW/JU5GuyH4uxMrNYpoC2kjaHlyJGLgIa3XkhPEZI0uKhZhJZU8BuEyJmvgzSPQNGozBwWjC972RaNdcJ9KyJg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + '@changesets/apply-release-plan@7.1.0': resolution: {integrity: sha512-yq8ML3YS7koKQ/9bk1PqO0HMzApIFNwjlwCnwFEXMzNe8NpzeeYYKCmnhWJGkN8g7E51MnWaSbqRcTcdIxUgnQ==} @@ -269,60 +313,6 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} - 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.2': - resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/config-array@0.21.2': - resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/config-helpers@0.4.2': - resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@0.17.0': - resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/eslintrc@3.3.5': - resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/js@9.39.4': - resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/object-schema@2.1.7': - resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/plugin-kit@0.4.1': - resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} - 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.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} - engines: {node: '>=18.18.0'} - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/retry@0.4.3': - resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} - engines: {node: '>=18.18'} - '@inquirer/external-editor@1.0.3': resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} engines: {node: '>=18'} @@ -490,74 +480,12 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} '@types/node@22.19.15': resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==} - '@typescript-eslint/eslint-plugin@8.58.0': - resolution: {integrity: sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.58.0 - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/parser@8.58.0': - resolution: {integrity: sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/project-service@8.58.0': - resolution: {integrity: sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/scope-manager@8.58.0': - resolution: {integrity: sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/tsconfig-utils@8.58.0': - resolution: {integrity: sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/type-utils@8.58.0': - resolution: {integrity: sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/types@8.58.0': - resolution: {integrity: sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.58.0': - resolution: {integrity: sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/utils@8.58.0': - resolution: {integrity: sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/visitor-keys@8.58.0': - resolution: {integrity: sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -587,19 +515,6 @@ packages: '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true - - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} - ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -608,10 +523,6 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -626,24 +537,10 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} - better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} - brace-expansion@1.1.13: - resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==} - - brace-expansion@5.0.5: - resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} - engines: {node: 18 || 20 || >=22} - braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -652,18 +549,10 @@ packages: 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@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} @@ -671,16 +560,6 @@ packages: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} - 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==} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -701,9 +580,6 @@ packages: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} @@ -732,64 +608,14 @@ packages: engines: {node: '>=18'} hasBin: true - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - eslint-scope@8.4.0: - resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} - 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.1: - resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint-visitor-keys@5.0.1: - resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - eslint@9.39.4: - resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true - - espree@10.4.0: - resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true - esquery@1.7.0: - resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} - 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@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} @@ -797,19 +623,10 @@ packages: extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} 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.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -822,10 +639,6 @@ packages: picomatch: optional: true - 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'} @@ -834,17 +647,6 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} 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.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} - fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -862,18 +664,6 @@ packages: 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.15.0: - resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} - engines: {node: '>=18'} - globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -881,10 +671,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - human-id@4.1.3: resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} hasBin: true @@ -897,18 +683,6 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} - engines: {node: '>= 4'} - - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -943,36 +717,13 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} 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==} - jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - 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'} - locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} - 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==} - lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} @@ -990,13 +741,6 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} - engines: {node: 18 || 20 || >=22} - - minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -1009,9 +753,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -1021,10 +762,6 @@ packages: encoding: optional: true - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} @@ -1036,18 +773,10 @@ packages: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - p-map@2.1.0: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} @@ -1059,10 +788,6 @@ packages: package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} - 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'} @@ -1101,19 +826,11 @@ packages: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - prettier@2.8.8: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} hasBin: true - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} @@ -1124,10 +841,6 @@ packages: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -1195,17 +908,9 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} @@ -1239,23 +944,6 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - ts-api-utils@2.5.0: - resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' - - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - typescript-eslint@8.58.0: - resolution: {integrity: sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - typescript@5.8.3: resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} @@ -1268,9 +956,6 @@ packages: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -1360,14 +1045,6 @@ packages: engines: {node: '>=8'} hasBin: true - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} @@ -1375,6 +1052,41 @@ snapshots: '@babel/runtime@7.29.2': {} + '@biomejs/biome@2.4.10': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.4.10 + '@biomejs/cli-darwin-x64': 2.4.10 + '@biomejs/cli-linux-arm64': 2.4.10 + '@biomejs/cli-linux-arm64-musl': 2.4.10 + '@biomejs/cli-linux-x64': 2.4.10 + '@biomejs/cli-linux-x64-musl': 2.4.10 + '@biomejs/cli-win32-arm64': 2.4.10 + '@biomejs/cli-win32-x64': 2.4.10 + + '@biomejs/cli-darwin-arm64@2.4.10': + optional: true + + '@biomejs/cli-darwin-x64@2.4.10': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.4.10': + optional: true + + '@biomejs/cli-linux-arm64@2.4.10': + optional: true + + '@biomejs/cli-linux-x64-musl@2.4.10': + optional: true + + '@biomejs/cli-linux-x64@2.4.10': + optional: true + + '@biomejs/cli-win32-arm64@2.4.10': + optional: true + + '@biomejs/cli-win32-x64@2.4.10': + optional: true + '@changesets/apply-release-plan@7.1.0': dependencies: '@changesets/config': 3.1.3 @@ -1611,63 +1323,6 @@ snapshots: '@esbuild/win32-x64@0.27.4': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)': - dependencies: - eslint: 9.39.4 - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.2': {} - - '@eslint/config-array@0.21.2': - dependencies: - '@eslint/object-schema': 2.1.7 - debug: 4.4.3 - minimatch: 3.1.5 - transitivePeerDependencies: - - supports-color - - '@eslint/config-helpers@0.4.2': - dependencies: - '@eslint/core': 0.17.0 - - '@eslint/core@0.17.0': - dependencies: - '@types/json-schema': 7.0.15 - - '@eslint/eslintrc@3.3.5': - dependencies: - ajv: 6.14.0 - debug: 4.4.3 - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.5 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@9.39.4': {} - - '@eslint/object-schema@2.1.7': {} - - '@eslint/plugin-kit@0.4.1': - dependencies: - '@eslint/core': 0.17.0 - levn: 0.4.1 - - '@humanfs/core@0.19.1': {} - - '@humanfs/node@0.16.7': - dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.4.3 - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/retry@0.4.3': {} - '@inquirer/external-editor@1.0.3(@types/node@22.19.15)': dependencies: chardet: 2.1.1 @@ -1793,105 +1448,12 @@ snapshots: '@types/estree@1.0.8': {} - '@types/json-schema@7.0.15': {} - '@types/node@12.20.55': {} '@types/node@22.19.15': dependencies: undici-types: 6.21.0 - '@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.8.3))(eslint@9.39.4)(typescript@5.8.3)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.58.0(eslint@9.39.4)(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.58.0 - '@typescript-eslint/type-utils': 8.58.0(eslint@9.39.4)(typescript@5.8.3) - '@typescript-eslint/utils': 8.58.0(eslint@9.39.4)(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.58.0 - eslint: 9.39.4 - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@5.8.3) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.8.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.58.0 - '@typescript-eslint/types': 8.58.0 - '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.58.0 - debug: 4.4.3 - eslint: 9.39.4 - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/project-service@8.58.0(typescript@5.8.3)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.58.0(typescript@5.8.3) - '@typescript-eslint/types': 8.58.0 - debug: 4.4.3 - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@8.58.0': - dependencies: - '@typescript-eslint/types': 8.58.0 - '@typescript-eslint/visitor-keys': 8.58.0 - - '@typescript-eslint/tsconfig-utils@8.58.0(typescript@5.8.3)': - dependencies: - typescript: 5.8.3 - - '@typescript-eslint/type-utils@8.58.0(eslint@9.39.4)(typescript@5.8.3)': - dependencies: - '@typescript-eslint/types': 8.58.0 - '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.58.0(eslint@9.39.4)(typescript@5.8.3) - debug: 4.4.3 - eslint: 9.39.4 - ts-api-utils: 2.5.0(typescript@5.8.3) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@8.58.0': {} - - '@typescript-eslint/typescript-estree@8.58.0(typescript@5.8.3)': - dependencies: - '@typescript-eslint/project-service': 8.58.0(typescript@5.8.3) - '@typescript-eslint/tsconfig-utils': 8.58.0(typescript@5.8.3) - '@typescript-eslint/types': 8.58.0 - '@typescript-eslint/visitor-keys': 8.58.0 - debug: 4.4.3 - minimatch: 10.2.5 - semver: 7.7.4 - tinyglobby: 0.2.15 - ts-api-utils: 2.5.0(typescript@5.8.3) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.58.0(eslint@9.39.4)(typescript@5.8.3)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) - '@typescript-eslint/scope-manager': 8.58.0 - '@typescript-eslint/types': 8.58.0 - '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.8.3) - eslint: 9.39.4 - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/visitor-keys@8.58.0': - dependencies: - '@typescript-eslint/types': 8.58.0 - eslint-visitor-keys: 5.0.1 - '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.3 @@ -1934,27 +1496,10 @@ snapshots: loupe: 3.2.1 tinyrainbow: 2.0.0 - acorn-jsx@5.3.2(acorn@8.16.0): - dependencies: - acorn: 8.16.0 - - acorn@8.16.0: {} - - ajv@6.14.0: - 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-colors@4.1.3: {} ansi-regex@5.0.1: {} - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -1965,31 +1510,16 @@ snapshots: assertion-error@2.0.1: {} - balanced-match@1.0.2: {} - - balanced-match@4.0.4: {} - better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 - brace-expansion@1.1.13: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@5.0.5: - dependencies: - balanced-match: 4.0.4 - braces@3.0.3: dependencies: fill-range: 7.1.1 cac@6.7.14: {} - callsites@3.1.0: {} - chai@5.3.3: dependencies: assertion-error: 2.0.1 @@ -1998,23 +1528,10 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - chardet@2.1.1: {} check-error@2.1.3: {} - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - concat-map@0.0.1: {} - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -2029,8 +1546,6 @@ snapshots: deep-eql@5.0.2: {} - deep-is@0.1.4: {} - detect-indent@6.1.0: {} dir-glob@3.0.1: @@ -2077,88 +1592,16 @@ snapshots: '@esbuild/win32-ia32': 0.27.4 '@esbuild/win32-x64': 0.27.4 - escape-string-regexp@4.0.0: {} - - eslint-scope@8.4.0: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint-visitor-keys@4.2.1: {} - - eslint-visitor-keys@5.0.1: {} - - eslint@9.39.4: - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) - '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.2 - '@eslint/config-helpers': 0.4.2 - '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.5 - '@eslint/js': 9.39.4 - '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.7 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - ajv: 6.14.0 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.3 - escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 - esquery: 1.7.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.5 - natural-compare: 1.4.0 - optionator: 0.9.4 - transitivePeerDependencies: - - supports-color - - espree@10.4.0: - dependencies: - acorn: 8.16.0 - acorn-jsx: 5.3.2(acorn@8.16.0) - eslint-visitor-keys: 4.2.1 - esprima@4.0.1: {} - esquery@1.7.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@5.3.0: {} - estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 - esutils@2.0.3: {} - expect-type@1.3.0: {} extendable-error@0.1.7: {} - fast-deep-equal@3.1.3: {} - fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2167,10 +1610,6 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -2179,10 +1618,6 @@ snapshots: optionalDependencies: picomatch: 4.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 @@ -2192,18 +1627,6 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - flat-cache@4.0.1: - dependencies: - flatted: 3.4.2 - keyv: 4.5.4 - - flatted@3.4.2: {} - fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -2223,14 +1646,6 @@ snapshots: dependencies: is-glob: 4.0.3 - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - - globals@14.0.0: {} - - globals@15.15.0: {} - globby@11.1.0: dependencies: array-union: 2.1.0 @@ -2242,8 +1657,6 @@ snapshots: graceful-fs@4.2.11: {} - has-flag@4.0.0: {} - human-id@4.1.3: {} iconv-lite@0.7.2: @@ -2252,15 +1665,6 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.5: {} - - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - imurmurhash@0.1.4: {} - is-extglob@2.1.1: {} is-glob@4.0.3: @@ -2288,35 +1692,14 @@ snapshots: dependencies: argparse: 2.0.1 - json-buffer@3.0.1: {} - - json-schema-traverse@0.4.1: {} - - json-stable-stringify-without-jsonify@1.0.1: {} - jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 - 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 - locate-path@5.0.0: dependencies: p-locate: 4.1.0 - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - lodash.merge@4.6.2: {} - lodash.startcase@4.4.0: {} loupe@3.2.1: {} @@ -2332,35 +1715,16 @@ snapshots: braces: 3.0.3 picomatch: 2.3.2 - minimatch@10.2.5: - dependencies: - brace-expansion: 5.0.5 - - minimatch@3.1.5: - dependencies: - brace-expansion: 1.1.13 - mri@1.2.0: {} ms@2.1.3: {} nanoid@3.3.11: {} - natural-compare@1.4.0: {} - node-fetch@2.7.0: dependencies: whatwg-url: 5.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 - outdent@0.5.0: {} p-filter@2.1.0: @@ -2371,18 +1735,10 @@ snapshots: dependencies: p-try: 2.2.0 - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - p-locate@4.1.0: dependencies: p-limit: 2.3.0 - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - p-map@2.1.0: {} p-try@2.2.0: {} @@ -2391,10 +1747,6 @@ snapshots: dependencies: quansync: 0.2.11 - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - path-exists@4.0.0: {} path-key@3.1.1: {} @@ -2419,12 +1771,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - prelude-ls@1.2.1: {} - prettier@2.8.8: {} - punycode@2.3.1: {} - quansync@0.2.11: {} queue-microtask@1.2.3: {} @@ -2436,8 +1784,6 @@ snapshots: pify: 4.0.1 strip-bom: 3.0.0 - resolve-from@4.0.0: {} - resolve-from@5.0.0: {} reusify@1.1.0: {} @@ -2512,16 +1858,10 @@ snapshots: strip-bom@3.0.0: {} - strip-json-comments@3.1.1: {} - strip-literal@3.1.0: dependencies: js-tokens: 9.0.1 - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - term-size@2.2.1: {} tinybench@2.9.0: {} @@ -2545,35 +1885,12 @@ snapshots: tr46@0.0.3: {} - ts-api-utils@2.5.0(typescript@5.8.3): - dependencies: - typescript: 5.8.3 - - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 - - typescript-eslint@8.58.0(eslint@9.39.4)(typescript@5.8.3): - dependencies: - '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.8.3))(eslint@9.39.4)(typescript@5.8.3) - '@typescript-eslint/parser': 8.58.0(eslint@9.39.4)(typescript@5.8.3) - '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.58.0(eslint@9.39.4)(typescript@5.8.3) - eslint: 9.39.4 - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - typescript@5.8.3: {} undici-types@6.21.0: {} universalify@0.1.2: {} - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - vite-node@3.2.4(@types/node@22.19.15): dependencies: cac: 6.7.14 @@ -2664,8 +1981,4 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - word-wrap@1.2.5: {} - - yocto-queue@0.1.0: {} - zod@4.3.6: {} diff --git a/src/index.ts b/src/index.ts index fde6106..ac4fdbf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,59 +1,13 @@ // Message format compatibility helpers +// High-level model calling +export { callModel } from './inner-loop/call-model.js'; +export { fromClaudeMessages, toClaudeMessage } from './lib/anthropic-compat.js'; export type { CallModelInput, CallModelInputWithState, ResolvedCallModelInput, } from './lib/async-params.js'; -export type { GetResponseOptions } from './lib/model-result.js'; -export type { StreamableOutputItem } from './lib/stream-transformers.js'; -export type { ContextInput } from './lib/tool-context.js'; -export type { - ChatStreamEvent, - ConversationState, - ConversationStatus, - HasApprovalTools, - InferToolEvent, - InferToolEventsUnion, - InferToolInput, - InferToolOutput, - InferToolOutputsUnion, - ManualTool, - NextTurnParamsContext, - NextTurnParamsFunctions, - ParsedToolCall, - PartialResponse, - ResponseStreamEvent, - ResponseStreamEvent as EnhancedResponseStreamEvent, - StateAccessor, - StepResult, - StopCondition, - StopWhen, - Tool, - ToolApprovalCheck, - ToolCallOutputEvent, - ToolExecutionResult, - ToolExecutionResultUnion, - ToolHasApproval, - ToolPreliminaryResultEvent, - ToolResultEvent, - ToolStreamEvent, - ToolWithExecute, - ToolWithGenerator, - TurnContext, - TurnEndEvent, - TurnStartEvent, - TypedToolCall, - TypedToolCallUnion, - UnsentToolResult, - Warning, -} from './lib/tool-types.js'; - -// High-level model calling -export { callModel } from './inner-loop/call-model.js'; -export { OpenRouter } from './openrouter.js'; -export type { SDKOptions } from './openrouter.js'; -export { fromClaudeMessages, toClaudeMessage } from './lib/anthropic-compat.js'; export { hasAsyncFunctions, resolveAsyncFunctions } from './lib/async-params.js'; export { fromChatMessages, toChatMessage } from './lib/chat-compat.js'; // Claude constants and type guards @@ -70,6 +24,7 @@ export { toolRequiresApproval, updateState, } from './lib/conversation-state.js'; +export type { GetResponseOptions } from './lib/model-result.js'; export { ModelResult } from './lib/model-result.js'; // Next turn params helpers export { @@ -86,6 +41,7 @@ export { maxTokensUsed, stepCountIs, } from './lib/stop-conditions.js'; +export type { StreamableOutputItem } from './lib/stream-transformers.js'; export { extractUnsupportedContent, getUnsupportedContentSummary, @@ -93,10 +49,51 @@ export { } from './lib/stream-transformers.js'; // Tool creation helpers export { tool } from './lib/tool.js'; +export type { ContextInput } from './lib/tool-context.js'; // Tool context helpers export { buildToolExecuteContext, ToolContextStore } from './lib/tool-context.js'; // Real-time tool event broadcasting export { ToolEventBroadcaster } from './lib/tool-event-broadcaster.js'; +export type { + ChatStreamEvent, + ConversationState, + ConversationStatus, + HasApprovalTools, + InferToolEvent, + InferToolEventsUnion, + InferToolInput, + InferToolOutput, + InferToolOutputsUnion, + ManualTool, + NextTurnParamsContext, + NextTurnParamsFunctions, + ParsedToolCall, + PartialResponse, + ResponseStreamEvent, + ResponseStreamEvent as EnhancedResponseStreamEvent, + StateAccessor, + StepResult, + StopCondition, + StopWhen, + Tool, + ToolApprovalCheck, + ToolCallOutputEvent, + ToolExecutionResult, + ToolExecutionResultUnion, + ToolHasApproval, + ToolPreliminaryResultEvent, + ToolResultEvent, + ToolStreamEvent, + ToolWithExecute, + ToolWithGenerator, + TurnContext, + TurnEndEvent, + TurnStartEvent, + TypedToolCall, + TypedToolCallUnion, + UnsentToolResult, + Warning, +} from './lib/tool-types.js'; export { hasApprovalRequiredTools, hasExecuteFunction, @@ -112,3 +109,5 @@ export { } from './lib/tool-types.js'; // Turn context helpers export { buildTurnContext, normalizeInputToArray } from './lib/turn-context.js'; +export type { SDKOptions } from './openrouter.js'; +export { OpenRouter } from './openrouter.js'; diff --git a/src/inner-loop/call-model.ts b/src/inner-loop/call-model.ts index ba12a32..8e933b6 100644 --- a/src/inner-loop/call-model.ts +++ b/src/inner-loop/call-model.ts @@ -2,10 +2,10 @@ import type { OpenRouterCore } from '@openrouter/sdk/core'; import type { RequestOptions } from '@openrouter/sdk/lib/sdks'; import type { $ZodObject, $ZodShape, infer as zodInfer } from 'zod/v4/core'; import type { CallModelInput } from '../lib/async-params.js'; -import type { Tool } from '../lib/tool-types.js'; - -import { type GetResponseOptions, ModelResult } from '../lib/model-result.js'; +import type { GetResponseOptions } from '../lib/model-result.js'; +import { ModelResult } from '../lib/model-result.js'; import { convertToolsToAPIFormat } from '../lib/tool-executor.js'; +import type { Tool } from '../lib/tool-types.js'; // Re-export CallModelInput for convenience export type { CallModelInput } from '../lib/async-params.js'; diff --git a/src/lib/anthropic-compat.test.ts b/src/lib/anthropic-compat.test.ts index 188c0d3..ac6173e 100644 --- a/src/lib/anthropic-compat.test.ts +++ b/src/lib/anthropic-compat.test.ts @@ -1,7 +1,6 @@ import type * as models from '@openrouter/sdk/models'; -import type { ClaudeMessageParam } from '../api-shape-helpers/claude-message.js'; - import { describe, expect, it } from 'vitest'; +import type { ClaudeMessageParam } from '../api-shape-helpers/claude-message.js'; import { fromClaudeMessages, toClaudeMessage } from './anthropic-compat.js'; /** diff --git a/src/lib/anthropic-compat.ts b/src/lib/anthropic-compat.ts index 3606173..a9b6846 100644 --- a/src/lib/anthropic-compat.ts +++ b/src/lib/anthropic-compat.ts @@ -1,4 +1,8 @@ import type * as models from '@openrouter/sdk/models'; +import { + EasyInputMessageRoleAssistant, + EasyInputMessageRoleUser, +} from '@openrouter/sdk/models/easyinputmessage'; import type { ClaudeImageBlockParam, ClaudeMessageParam, @@ -6,11 +10,6 @@ import type { ClaudeToolResultBlockParam, ClaudeToolUseBlockParam, } from '../api-shape-helpers/claude-message.js'; - -import { - EasyInputMessageRoleAssistant, - EasyInputMessageRoleUser, -} from '@openrouter/sdk/models/easyinputmessage'; import { convertToClaudeMessage } from './stream-transformers.js'; /** diff --git a/src/lib/claude-type-guards.ts b/src/lib/claude-type-guards.ts index 7fcaa2d..e5086a1 100644 --- a/src/lib/claude-type-guards.ts +++ b/src/lib/claude-type-guards.ts @@ -15,19 +15,25 @@ function isNonClaudeRole(role: unknown): boolean { } function isClaudeToolResultBlock(block: unknown): boolean { - if (!isRecord(block)) return false; + if (!isRecord(block)) { + return false; + } return block['type'] === ClaudeContentBlockType.ToolResult; } function isClaudeImageBlockWithSource(block: unknown): boolean { - if (!isRecord(block)) return false; + if (!isRecord(block)) { + return false; + } return ( block['type'] === ClaudeContentBlockType.Image && 'source' in block && isRecord(block['source']) ); } function isClaudeToolUseBlockWithId(block: unknown): boolean { - if (!isRecord(block)) return false; + if (!isRecord(block)) { + return false; + } return ( block['type'] === ClaudeContentBlockType.ToolUse && 'id' in block && @@ -37,9 +43,15 @@ function isClaudeToolUseBlockWithId(block: unknown): boolean { function hasClaudeSpecificBlocks(content: unknown[]): boolean { for (const block of content) { - if (isClaudeToolResultBlock(block)) return true; - if (isClaudeImageBlockWithSource(block)) return true; - if (isClaudeToolUseBlockWithId(block)) return true; + if (isClaudeToolResultBlock(block)) { + return true; + } + if (isClaudeImageBlockWithSource(block)) { + return true; + } + if (isClaudeToolUseBlockWithId(block)) { + return true; + } } return false; } @@ -57,9 +69,15 @@ export function isClaudeStyleMessages(input: unknown): input is ClaudeMessagePar } for (const msg of input) { - if (!isRecord(msg)) continue; - if (!('role' in msg)) continue; - if ('type' in msg) continue; // Claude messages don't have top-level "type" + if (!isRecord(msg)) { + continue; + } + if (!('role' in msg)) { + continue; + } + if ('type' in msg) { + continue; // Claude messages don't have top-level "type" + } // If we find a non-Claude role, it's not Claude format if (isNonClaudeRole(msg['role'])) { diff --git a/src/lib/conversation-state.ts b/src/lib/conversation-state.ts index 1f66590..16dbc6b 100644 --- a/src/lib/conversation-state.ts +++ b/src/lib/conversation-state.ts @@ -15,7 +15,9 @@ import { normalizeInputToArray } from './turn-context.js'; function isValidUnsentToolResult( obj: unknown, ): obj is UnsentToolResult { - if (typeof obj !== 'object' || obj === null) return false; + if (typeof obj !== 'object' || obj === null) { + return false; + } return ( 'callId' in obj && typeof obj.callId === 'string' && @@ -31,7 +33,9 @@ function isValidUnsentToolResult( function isValidParsedToolCall( obj: unknown, ): obj is ParsedToolCall { - if (typeof obj !== 'object' || obj === null) return false; + if (typeof obj !== 'object' || obj === null) { + return false; + } return ( 'id' in obj && typeof obj.id === 'string' && @@ -122,7 +126,9 @@ export async function toolRequiresApproval( // Fall back to tool-level setting const tool = tools.find((t) => t.function.name === toolCall.name); - if (!tool) return false; + if (!tool) { + return false; + } const requireApproval = tool.function.requireApproval; diff --git a/src/lib/model-result.ts b/src/lib/model-result.ts index 16e7387..6ff046e 100644 --- a/src/lib/model-result.ts +++ b/src/lib/model-result.ts @@ -1,33 +1,11 @@ import type { OpenRouterCore } from '@openrouter/sdk/core'; +import { betaResponsesSend } from '@openrouter/sdk/funcs/betaResponsesSend'; import type { EventStream } from '@openrouter/sdk/lib/event-streams'; import type { RequestOptions } from '@openrouter/sdk/lib/sdks'; import type * as models from '@openrouter/sdk/models'; import type { $ZodObject, $ZodShape } from 'zod/v4/core'; -import type { CallModelInput } from './async-params.js'; -import type { - ConversationState, - InferToolEventsUnion, - InferToolOutputsUnion, - ParsedToolCall, - ResponseStreamEvent, - StateAccessor, - StopWhen, - Tool, - ToolCallOutputEvent, - ToolContextMapWithShared, - ToolStreamEvent, - TurnContext, - TurnEndEvent, - TurnStartEvent, - UnsentToolResult, -} from './tool-types.js'; - -import { betaResponsesSend } from '@openrouter/sdk/funcs/betaResponsesSend'; -import { - hasAsyncFunctions, - type ResolvedCallModelInput, - resolveAsyncFunctions, -} from './async-params.js'; +import type { CallModelInput, ResolvedCallModelInput } from './async-params.js'; +import { hasAsyncFunctions, resolveAsyncFunctions } from './async-params.js'; import { appendToMessages, createInitialState, @@ -44,6 +22,7 @@ import { } from './next-turn-params.js'; import { ReusableReadableStream } from './reusable-stream.js'; import { isStopConditionMet, stepCountIs } from './stop-conditions.js'; +import type { ItemInProgress, StreamableOutputItem } from './stream-transformers.js'; import { buildItemsStream, buildResponsesMessageStream, @@ -55,9 +34,7 @@ import { extractTextFromResponse, extractToolCallsFromResponse, extractToolDeltas, - type ItemInProgress, itemsStreamHandlers, - type StreamableOutputItem, streamTerminationEvents, } from './stream-transformers.js'; import { @@ -69,9 +46,27 @@ import { isResponseFailedEvent, isResponseIncompleteEvent, } from './stream-type-guards.js'; -import { type ContextInput, resolveContext, ToolContextStore } from './tool-context.js'; +import type { ContextInput } from './tool-context.js'; +import { resolveContext, ToolContextStore } from './tool-context.js'; import { ToolEventBroadcaster } from './tool-event-broadcaster.js'; import { executeTool } from './tool-executor.js'; +import type { + ConversationState, + InferToolEventsUnion, + InferToolOutputsUnion, + ParsedToolCall, + ResponseStreamEvent, + StateAccessor, + StopWhen, + Tool, + ToolCallOutputEvent, + ToolContextMapWithShared, + ToolStreamEvent, + TurnContext, + TurnEndEvent, + TurnStartEvent, + UnsentToolResult, +} from './tool-types.js'; import { hasExecuteFunction, isToolCallOutputEvent } from './tool-types.js'; /** @@ -252,7 +247,9 @@ export class ModelResult< * Wraps the initial stream events with turn.start(0) / turn.end(0) delimiters. */ private startInitialStreamPipe(): void { - if (this.initialStreamPipeStarted) return; + if (this.initialStreamPipeStarted) { + return; + } this.initialStreamPipeStarted = true; const broadcaster = this.ensureTurnBroadcaster(); @@ -263,6 +260,7 @@ export class ModelResult< const stream = this.reusableStream; + // biome-ignore lint: IIFE used for fire-and-forget async pipe this.initialPipePromise = (async () => { broadcaster.push({ type: 'turn.start', @@ -450,7 +448,9 @@ export class ModelResult< * @param response - The API response to save */ private async saveResponseToState(response: models.OpenResponsesResult): Promise { - if (!this.stateAccessor || !this.currentState) return; + if (!this.stateAccessor || !this.currentState) { + return; + } const outputItems = Array.isArray(response.output) ? response.output @@ -486,7 +486,9 @@ export class ModelResult< private async saveToolResultsToState( toolResults: models.FunctionCallOutputItem[], ): Promise { - if (!this.currentState) return; + if (!this.currentState) { + return; + } await this.saveStateSafely({ messages: appendToMessages(this.currentState.messages, toolResults), }); @@ -502,10 +504,14 @@ export class ModelResult< private async checkForInterruption( currentResponse: models.OpenResponsesResult, ): Promise { - if (!this.stateAccessor) return false; + if (!this.stateAccessor) { + return false; + } const freshState = await this.stateAccessor.load(); - if (!freshState?.interruptedBy) return false; + if (!freshState?.interruptedBy) { + return false; + } // Save partial state if (this.currentState) { @@ -615,7 +621,9 @@ export class ModelResult< for (let i = 0; i < settledResults.length; i++) { const settled = settledResults[i]; const tc = toolCalls[i]; - if (!settled || !tc) continue; + if (!settled || !tc) { + continue; + } if (settled.status === 'rejected') { const errorMessage = @@ -649,7 +657,9 @@ export class ModelResult< currentRound: number, currentResponse: models.OpenResponsesResult, ): Promise { - if (!this.options.tools) return false; + if (!this.options.tools) { + return false; + } const turnContext: TurnContext = { numberOfTurns: currentRound, @@ -663,7 +673,9 @@ export class ModelResult< this.requireApprovalFn ?? undefined, ); - if (needsApproval.length === 0) return false; + if (needsApproval.length === 0) { + return false; + } // Validate: approval requires state accessor if (!this.stateAccessor) { @@ -717,7 +729,7 @@ export class ModelResult< const errorMessage = `Failed to parse tool call arguments for "${toolCall.name}": The model provided invalid JSON. ` + `Raw arguments received: "${rawArgs}". ` + - `Please provide valid JSON arguments for this tool call.`; + 'Please provide valid JSON arguments for this tool call.'; this.broadcastToolResult(toolCall.id, { error: errorMessage, @@ -772,7 +784,9 @@ export class ModelResult< for (let i = 0; i < settledResults.length; i++) { const settled = settledResults[i]; const originalToolCall = toolCalls[i]; - if (!settled || !originalToolCall) continue; + if (!settled || !originalToolCall) { + continue; + } if (settled.status === 'rejected') { const errorMessage = @@ -800,7 +814,9 @@ export class ModelResult< } const value = settled.value; - if (!value) continue; + if (!value) { + continue; + } if (value.type === 'parse_error') { toolResults.push(value.output); @@ -958,11 +974,11 @@ export class ModelResult< } return consumeStreamForCompletion(followUpStream); - } else if (this.isNonStreamingResponse(value)) { + } + if (this.isNonStreamingResponse(value)) { return value; - } else { - throw new Error('Unexpected response type from API'); } + throw new Error('Unexpected response type from API'); } /** @@ -1015,7 +1031,9 @@ export class ModelResult< private async saveStateSafely( updates?: Partial, 'id' | 'createdAt' | 'updatedAt'>>, ): Promise { - if (!this.stateAccessor || !this.currentState) return; + if (!this.stateAccessor || !this.currentState) { + return; + } if (updates) { this.currentState = updateState(this.currentState, updates); @@ -1038,7 +1056,9 @@ export class ModelResult< private clearOptionalStateProperties( props: Array<'pendingToolCalls' | 'unsentToolResults' | 'interruptedBy' | 'partialResponse'>, ): void { - if (!this.currentState) return; + if (!this.currentState) { + return; + } for (const prop of props) { delete this.currentState[prop]; } @@ -1057,6 +1077,7 @@ export class ModelResult< return this.initPromise; } + // biome-ignore lint: IIFE used for lazy initialization pattern this.initPromise = (async () => { // Load or create state if accessor provided if (this.stateAccessor) { @@ -1121,8 +1142,7 @@ export class ModelResult< // If we have state with existing messages, use those as input if ( - this.currentState && - this.currentState.messages && + this.currentState?.messages && Array.isArray(this.currentState.messages) && this.currentState.messages.length > 0 ) { @@ -1208,7 +1228,9 @@ export class ModelResult< // Process approvals - execute the approved tools for (const callId of this.approvedToolCalls) { const toolCall = pendingCalls.find((tc) => tc.id === callId); - if (!toolCall) continue; + if (!toolCall) { + continue; + } const tool = this.options.tools?.find((t) => t.function.name === toolCall.name); if (!tool || !hasExecuteFunction(tool)) { @@ -1240,7 +1262,9 @@ export class ModelResult< // Process rejections for (const callId of this.rejectedToolCalls) { const toolCall = pendingCalls.find((tc) => tc.id === callId); - if (!toolCall) continue; + if (!toolCall) { + continue; + } unsentResults.push(createRejectedResult(callId, String(toolCall.name), 'Rejected by user')); } @@ -1267,8 +1291,12 @@ export class ModelResult< // Clear optional properties if they should be empty const propsToClear: Array<'pendingToolCalls' | 'unsentToolResults'> = []; - if (remainingPending.length === 0) propsToClear.push('pendingToolCalls'); - if (unsentResults.length === 0) propsToClear.push('unsentToolResults'); + if (remainingPending.length === 0) { + propsToClear.push('pendingToolCalls'); + } + if (unsentResults.length === 0) { + propsToClear.push('unsentToolResults'); + } if (propsToClear.length > 0) { this.clearOptionalStateProperties(propsToClear); await this.saveStateSafely(); @@ -1287,10 +1315,14 @@ export class ModelResult< * Continue execution with unsent tool results */ private async continueWithUnsentResults(): Promise { - if (!this.currentState || !this.stateAccessor) return; + if (!this.currentState || !this.stateAccessor) { + return; + } const unsentResults = this.currentState.unsentToolResults ?? []; - if (unsentResults.length === 0) return; + if (unsentResults.length === 0) { + return; + } // Convert to API format const toolOutputs = unsentResultsToAPIFormat(unsentResults); @@ -1357,6 +1389,7 @@ export class ModelResult< return this.toolExecutionPromise; } + // biome-ignore lint: IIFE used for lazy initialization pattern this.toolExecutionPromise = (async () => { await this.initStream(); @@ -1696,7 +1729,11 @@ export class ModelResult< // Yield manual tool function_call items from finalResponse, skipping duplicates if (this.finalResponse) { for (const item of this.finalResponse.output) { - if (isFunctionCallItem(item) && this.isManualToolCall(item) && !yieldedCallIds.has(item.callId)) { + if ( + isFunctionCallItem(item) && + this.isManualToolCall(item) && + !yieldedCallIds.has(item.callId) + ) { yieldedCallIds.add(item.callId); yield item; } diff --git a/src/lib/next-turn-params.test.ts b/src/lib/next-turn-params.test.ts index 04fd758..b6a2a8b 100644 --- a/src/lib/next-turn-params.test.ts +++ b/src/lib/next-turn-params.test.ts @@ -6,12 +6,15 @@ import { applyNextTurnParamsToRequest } from './next-turn-params.js'; /** * Creates a minimal ResponsesRequest for testing applyNextTurnParamsToRequest. */ -function createBaseRequest( - overrides?: Partial, -): models.ResponsesRequest { +function createBaseRequest(overrides?: Partial): models.ResponsesRequest { return { model: 'openai/gpt-4', - input: [{ role: 'user', content: 'hello' }], + input: [ + { + role: 'user', + content: 'hello', + }, + ], ...overrides, }; } @@ -31,7 +34,9 @@ describe('applyNextTurnParamsToRequest', () => { }); it('should convert null values to undefined', () => { - const request = createBaseRequest({ temperature: 0.7 }); + const request = createBaseRequest({ + temperature: 0.7, + }); const result = applyNextTurnParamsToRequest(request, { temperature: null, maxOutputTokens: null, @@ -60,7 +65,9 @@ describe('applyNextTurnParamsToRequest', () => { }); it('should handle empty computedParams without changing the request', () => { - const request = createBaseRequest({ temperature: 0.5 }); + const request = createBaseRequest({ + temperature: 0.5, + }); const result = applyNextTurnParamsToRequest(request, {}); expect(result.temperature).toBe(0.5); diff --git a/src/lib/next-turn-params.ts b/src/lib/next-turn-params.ts index ce0902f..39c31d3 100644 --- a/src/lib/next-turn-params.ts +++ b/src/lib/next-turn-params.ts @@ -91,6 +91,7 @@ export async function executeNextTurnParamsFunctions( /** * Process nextTurnParams for a single tool call with full type safety */ +// biome-ignore lint: parameters are distinct concerns, not a single options object async function processNextTurnParamsForCall( nextParams: Record, params: Record, @@ -112,7 +113,7 @@ async function processNextTurnParamsForCall( if (process.env['NODE_ENV'] !== 'production') { console.warn( `Invalid nextTurnParams key "${paramKey}" in tool "${toolName}". ` + - `Valid keys: input, model, models, temperature, maxOutputTokens, topP, topK, instructions`, + 'Valid keys: input, model, models, temperature, maxOutputTokens, topP, topK, instructions', ); } continue; diff --git a/src/lib/reusable-stream.ts b/src/lib/reusable-stream.ts index 5887160..7b5e2c1 100644 --- a/src/lib/reusable-stream.ts +++ b/src/lib/reusable-stream.ts @@ -145,6 +145,7 @@ export class ReusableReadableStream { this.pumpStarted = true; this.sourceReader = this.sourceStream.getReader(); + // biome-ignore lint: IIFE used for fire-and-forget stream pump void (async () => { try { while (true) { diff --git a/src/lib/stream-transformers.ts b/src/lib/stream-transformers.ts index 3d3a74d..e64e648 100644 --- a/src/lib/stream-transformers.ts +++ b/src/lib/stream-transformers.ts @@ -7,8 +7,6 @@ import type { UnsupportedContent, } from '../api-shape-helpers/claude-message.js'; import type { ReusableReadableStream } from './reusable-stream.js'; -import type { ParsedToolCall, Tool } from './tool-types.js'; - import { isFileCitationAnnotation, isFilePathAnnotation, @@ -31,6 +29,7 @@ import { isURLCitationAnnotation, isWebSearchCallOutputItem, } from './stream-type-guards.js'; +import type { ParsedToolCall, Tool } from './tool-types.js'; /** * Extract text deltas from responses stream events diff --git a/src/lib/tool-context.ts b/src/lib/tool-context.ts index d5888f0..618def1 100644 --- a/src/lib/tool-context.ts +++ b/src/lib/tool-context.ts @@ -1,7 +1,6 @@ +import * as z4 from 'zod/v4'; import type { $ZodObject, $ZodShape } from 'zod/v4/core'; import type { ToolExecuteContext, TurnContext } from './tool-types.js'; - -import * as z4 from 'zod/v4'; import { SHARED_CONTEXT_KEY } from './tool-types.js'; //#region Types diff --git a/src/lib/tool-executor.ts b/src/lib/tool-executor.ts index 7a4a363..a7c3f19 100644 --- a/src/lib/tool-executor.ts +++ b/src/lib/tool-executor.ts @@ -1,4 +1,7 @@ +import * as z4 from 'zod/v4'; import type { $ZodObject, $ZodShape, $ZodType } from 'zod/v4/core'; +import type { ToolContextStore } from './tool-context.js'; +import { buildToolExecuteContext } from './tool-context.js'; import type { APITool, ParsedToolCall, @@ -7,9 +10,6 @@ import type { ToolExecutionResult, TurnContext, } from './tool-types.js'; - -import * as z4 from 'zod/v4'; -import { buildToolExecuteContext, type ToolContextStore } from './tool-context.js'; import { hasExecuteFunction, isGeneratorTool, isRegularExecuteTool } from './tool-types.js'; // Re-export ZodError for convenience @@ -154,6 +154,7 @@ export function parseToolCallArguments(argumentsString: string): unknown { /** * Build a ToolExecuteContext for a tool from a TurnContext and optional context store */ +// biome-ignore lint: parameters match the internal API shape function buildExecuteCtx( tool: Tool, turnContext: TurnContext, @@ -172,6 +173,7 @@ function buildExecuteCtx( /** * Execute a regular (non-generator) tool */ +// biome-ignore lint: parameters match the internal API shape export async function executeRegularTool( tool: Tool, toolCall: ParsedToolCall, @@ -224,6 +226,7 @@ export async function executeRegularTool( * - Last yield is validated against outputSchema (final result sent to model) * - Generator must emit at least one value */ +// biome-ignore lint: parameters match the internal API shape export async function executeGeneratorTool( tool: Tool, toolCall: ParsedToolCall, @@ -305,6 +308,7 @@ export async function executeGeneratorTool( * Execute a tool call * Automatically detects if it's a regular or generator tool */ +// biome-ignore lint: parameters match the internal API shape export async function executeTool( tool: Tool, toolCall: ParsedToolCall, diff --git a/src/lib/tool-orchestrator.ts b/src/lib/tool-orchestrator.ts index acec1a1..cd0df4a 100644 --- a/src/lib/tool-orchestrator.ts +++ b/src/lib/tool-orchestrator.ts @@ -1,6 +1,4 @@ import type * as models from '@openrouter/sdk/models'; -import type { APITool, Tool, ToolExecutionResult } from './tool-types.js'; - import { applyNextTurnParamsToRequest, executeNextTurnParamsFunctions, @@ -8,6 +6,7 @@ import { import { extractToolCallsFromResponse, responseHasToolCalls } from './stream-transformers.js'; import { isFunctionCallItem } from './stream-type-guards.js'; import { executeTool, findToolByName } from './tool-executor.js'; +import type { APITool, Tool, ToolExecutionResult } from './tool-types.js'; import { hasExecuteFunction } from './tool-types.js'; import { buildTurnContext } from './turn-context.js'; @@ -40,6 +39,7 @@ export interface ToolOrchestrationResult { * @param options - Execution options * @returns Result containing final response and all execution data */ +// biome-ignore lint: parameters match the public API shape export async function executeToolLoop( sendRequest: (input: models.InputsUnion, tools: APITool[]) => Promise, initialInput: models.InputsUnion, @@ -143,7 +143,9 @@ export async function executeToolLoop( const roundResults: ToolExecutionResult[] = []; settledResults.forEach((settled, i) => { const toolCall = toolCalls[i]; - if (!toolCall) return; + if (!toolCall) { + return; + } if (settled.status === 'fulfilled') { if (settled.value !== null) { diff --git a/src/lib/tool-types.ts b/src/lib/tool-types.ts index fe5d9d6..0ea81ef 100644 --- a/src/lib/tool-types.ts +++ b/src/lib/tool-types.ts @@ -92,7 +92,8 @@ export type ToolContextMapWithShared< TShared extends Record = Record, > = ToolContextMap & (TShared extends Record - ? {} + ? // biome-ignore lint/complexity/noBannedTypes: empty object is intentional for conditional type + {} : { shared: TShared; }); @@ -220,7 +221,7 @@ export interface ToolFunctionWithGenerator< execute: ( params: zodInfer, context?: ToolExecuteContext, - ) => AsyncGenerator | zodInfer, zodInfer | void>; + ) => AsyncGenerator | zodInfer, zodInfer | undefined>; } /** diff --git a/src/lib/tool.ts b/src/lib/tool.ts index e3285bb..382d297 100644 --- a/src/lib/tool.ts +++ b/src/lib/tool.ts @@ -1,16 +1,14 @@ import type { $ZodObject, $ZodShape, $ZodType, infer as zodInfer } from 'zod/v4/core'; - -import { - type ManualTool, - type NextTurnParamsFunctions, - SHARED_CONTEXT_KEY, - type Tool, - type ToolApprovalCheck, - type ToolExecuteContext, - ToolType, - type ToolWithExecute, - type ToolWithGenerator, +import type { + ManualTool, + NextTurnParamsFunctions, + Tool, + ToolApprovalCheck, + ToolExecuteContext, + ToolWithExecute, + ToolWithGenerator, } from './tool-types.js'; +import { SHARED_CONTEXT_KEY, ToolType } from './tool-types.js'; //#region Config Types diff --git a/src/lib/turn-context.ts b/src/lib/turn-context.ts index 2dc4584..04aa387 100644 --- a/src/lib/turn-context.ts +++ b/src/lib/turn-context.ts @@ -1,6 +1,5 @@ -import type { TurnContext } from './tool-types.js'; - import * as models from '@openrouter/sdk/models'; +import type { TurnContext } from './tool-types.js'; /** * Options for building a turn context diff --git a/src/openrouter.ts b/src/openrouter.ts index 44efa0d..ac2dca5 100644 --- a/src/openrouter.ts +++ b/src/openrouter.ts @@ -16,12 +16,11 @@ export type { SDKOptions } from '@openrouter/sdk/lib/config'; * The underlying `ClientSDK` accepts hooks at runtime but its constructor type * (`SDKOptions`) does not include them. This type bridges that gap. */ -export type OpenRouterOptions = SDKOptions & { hooks?: SDKHooks }; +export type OpenRouterOptions = SDKOptions & { + hooks?: SDKHooks; +}; export class OpenRouter extends OpenRouterCore { - constructor(options?: OpenRouterOptions) { - super(options); - } callModel = < TTools extends readonly Tool[], TSharedSchema extends $ZodObject<$ZodShape> | undefined = undefined, diff --git a/tests/e2e/call-model-state.test.ts b/tests/e2e/call-model-state.test.ts index 1c33af5..a7d0135 100644 --- a/tests/e2e/call-model-state.test.ts +++ b/tests/e2e/call-model-state.test.ts @@ -1,14 +1,8 @@ import * as dotenv from 'dotenv'; import { beforeAll, describe, expect, it } from 'vitest'; import { z } from 'zod/v4'; -import { - type ConversationState, - createInitialState, - OpenRouter, - type StateAccessor, - stepCountIs, - tool, -} from '../../src/index.js'; +import type { ConversationState, StateAccessor } from '../../src/index.js'; +import { OpenRouter, stepCountIs, tool } from '../../src/index.js'; dotenv.config(); diff --git a/tests/e2e/call-model.test.ts b/tests/e2e/call-model.test.ts index fcfcc65..3c268ed 100644 --- a/tests/e2e/call-model.test.ts +++ b/tests/e2e/call-model.test.ts @@ -1,12 +1,10 @@ +import { OpenRouter, ToolType } from '@openrouter/sdk'; import type { ClaudeMessageParam } from '@openrouter/sdk/models/claude-message'; import type { OpenResponsesFunctionCallOutput } from '@openrouter/sdk/models/openresponsesfunctioncalloutput'; +import type { OpenResponsesNonStreamingResponse } from '@openrouter/sdk/models/openresponsesnonstreamingresponse'; +import type { OpenResponsesStreamEvent } from '@openrouter/sdk/models/openresponsesstreamevent'; import type { OutputFunctionCallItem } from '@openrouter/sdk/models/outputfunctioncallitem'; import type { ResponsesOutputMessage } from '@openrouter/sdk/models/responsesoutputmessage'; -import type { ChatStreamEvent, ResponseStreamEvent } from '../../src/lib/tool-types.js'; - -import { OpenRouter, ToolType } from '@openrouter/sdk'; -import { OpenResponsesNonStreamingResponse } from '@openrouter/sdk/models/openresponsesnonstreamingresponse'; -import { OpenResponsesStreamEvent } from '@openrouter/sdk/models/openresponsesstreamevent'; import { beforeAll, describe, expect, it } from 'vitest'; import { z } from 'zod/v4'; import { fromClaudeMessages } from '../../src/lib/anthropic-compat.js'; @@ -17,6 +15,7 @@ import { isResponseCompletedEvent, isResponseIncompleteEvent, } from '../../src/lib/stream-type-guards.js'; +import type { ChatStreamEvent, ResponseStreamEvent } from '../../src/lib/tool-types.js'; import { isToolPreliminaryResultEvent } from '../../src/lib/tool-types.js'; /** @@ -26,25 +25,26 @@ function transformToChatStreamEvent(event: ResponseStreamEvent): ChatStreamEvent if (isToolPreliminaryResultEvent(event)) { // Pass through tool preliminary results as-is return event; - } else if (isOutputTextDeltaEvent(event)) { + } + if (isOutputTextDeltaEvent(event)) { // Transform text deltas to content.delta return { type: 'content.delta', delta: event.delta, }; - } else if (isResponseCompletedEvent(event) || isResponseIncompleteEvent(event)) { + } + if (isResponseCompletedEvent(event) || isResponseIncompleteEvent(event)) { // Transform completion events to message.complete return { type: 'message.complete', response: event.response, }; - } else { - // Pass-through all other events with original event wrapped - return { - type: event.type, - event, - }; } + // Pass-through all other events with original event wrapped + return { + type: event.type, + event, + }; } describe('callModel E2E Tests', () => { @@ -819,7 +819,11 @@ describe('callModel E2E Tests', () => { ], }); - const items: (ResponsesOutputMessage | OpenResponsesFunctionCallOutput | OutputFunctionCallItem)[] = []; + const items: ( + | ResponsesOutputMessage + | OpenResponsesFunctionCallOutput + | OutputFunctionCallItem + )[] = []; for await (const item of response.getNewMessagesStream()) { items.push(item); @@ -870,7 +874,11 @@ describe('callModel E2E Tests', () => { ], }); - const items: (ResponsesOutputMessage | OpenResponsesFunctionCallOutput | OutputFunctionCallItem)[] = []; + const items: ( + | ResponsesOutputMessage + | OpenResponsesFunctionCallOutput + | OutputFunctionCallItem + )[] = []; for await (const item of response.getNewMessagesStream()) { items.push(item); @@ -1351,6 +1359,7 @@ describe('callModel E2E Tests', () => { // Get full text and stream concurrently const textPromise = response.getText(); + // biome-ignore lint: IIFE used in test for async consumption const streamPromise = (async () => { const deltas: string[] = []; for await (const delta of response.getTextStream()) { @@ -1385,6 +1394,7 @@ describe('callModel E2E Tests', () => { }); // Start two concurrent stream consumers + // biome-ignore lint: IIFE used in test for async consumption const textStreamPromise = (async () => { const deltas: string[] = []; for await (const delta of response.getTextStream()) { @@ -1393,6 +1403,7 @@ describe('callModel E2E Tests', () => { return deltas; })(); + // biome-ignore lint: IIFE used in test for async consumption const newMessagesStreamPromise = (async () => { const messages: (ResponsesOutputMessage | OpenResponsesFunctionCallOutput)[] = []; for await (const message of response.getNewMessagesStream()) { diff --git a/tests/e2e/multi-turn-tool-state.test.ts b/tests/e2e/multi-turn-tool-state.test.ts index e9c04a5..35af4ff 100644 --- a/tests/e2e/multi-turn-tool-state.test.ts +++ b/tests/e2e/multi-turn-tool-state.test.ts @@ -2,21 +2,18 @@ import type { OpenResponsesInputUnion, OpenResponsesInputUnion1, } from '@openrouter/sdk/models/openresponsesinputunion'; -import type { ModelResult } from '../../src/lib/model-result.js'; -import type { Tool } from '../../src/lib/tool-types.js'; - import * as dotenv from 'dotenv'; import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { z } from 'zod/v4'; -import { - type BeforeRequestContext, - type BeforeRequestHook, - OpenRouter, - type StreamableOutputItem, - stepCountIs, - type TurnContext, - tool, +import type { + BeforeRequestContext, + BeforeRequestHook, + StreamableOutputItem, + TurnContext, } from '../../src/index.js'; +import { OpenRouter, stepCountIs, tool } from '../../src/index.js'; +import type { ModelResult } from '../../src/lib/model-result.js'; +import type { Tool } from '../../src/lib/tool-types.js'; dotenv.config(); @@ -468,7 +465,7 @@ describe('Multi-Turn Tool State E2E Tests', () => { const functionCallKeys = [ ...allItemsMap.keys(), ].filter((k) => k.startsWith('function_call:')); - const functionCallOutputKeys = [ + const _functionCallOutputKeys = [ ...allItemsMap.keys(), ].filter((k) => k.startsWith('function_call_output:')); @@ -801,7 +798,7 @@ describe('Multi-Turn Tool State E2E Tests', () => { stopWhen: stepCountIs(10), }); - const text = await consumeAndTrackItems(result, allItemsMap); + const _text = await consumeAndTrackItems(result, allItemsMap); // Verify input function was called expect(turnHistory.length).toBeGreaterThan(0); diff --git a/tests/unit/get-items-stream-manual-tools.test.ts b/tests/unit/get-items-stream-manual-tools.test.ts index 5f4e630..dfdb32d 100644 --- a/tests/unit/get-items-stream-manual-tools.test.ts +++ b/tests/unit/get-items-stream-manual-tools.test.ts @@ -1,3 +1,4 @@ +import type { OpenRouterCore } from '@openrouter/sdk/core'; import type { OpenResponsesStreamEvent, OpenResponsesStreamEventResponseCompleted, @@ -7,15 +8,13 @@ import type { OpenResponsesStreamEventResponseOutputItemDone, } from '@openrouter/sdk/models/openresponsesstreamevent'; import type { OutputFunctionCallItem } from '@openrouter/sdk/models/outputfunctioncallitem'; -import type { OpenRouterCore } from '@openrouter/sdk/core'; -import type { StreamableOutputItem } from '../../src/lib/stream-transformers.js'; - import { describe, expect, it } from 'vitest'; import { z } from 'zod/v4'; -import { ReusableReadableStream } from '../../src/lib/reusable-stream.js'; import { ModelResult } from '../../src/lib/model-result.js'; -import { ToolType } from '../../src/lib/tool-types.js'; +import { ReusableReadableStream } from '../../src/lib/reusable-stream.js'; +import type { StreamableOutputItem } from '../../src/lib/stream-transformers.js'; import { isFunctionCallItem } from '../../src/lib/stream-type-guards.js'; +import { ToolType } from '../../src/lib/tool-types.js'; // ============================================================================ // Synthetic event factories @@ -83,6 +82,7 @@ function functionCallArgsDoneEvent( }; } +// biome-ignore lint: test helper function function outputItemDoneFunctionCallEvent( callId: string, name: string, @@ -116,7 +116,9 @@ function responseCompletedEvent( model: 'test-model', status: 'completed', completedAt: 0, - output: [functionCallItem], + output: [ + functionCallItem, + ], error: null, incompleteDetails: null, temperature: null, @@ -167,15 +169,24 @@ describe('getItemsStream with manual tools — duplicate detection', () => { function: { name: TOOL_NAME, description: 'Submit a weather report summary.', - inputSchema: z.object({ summary: z.string() }), - outputSchema: z.object({ success: z.boolean() }), + inputSchema: z.object({ + summary: z.string(), + }), + outputSchema: z.object({ + success: z.boolean(), + }), }, } as const; const modelResult = new ModelResult({ - request: { model: 'test-model', input: 'test' }, + request: { + model: 'test-model', + input: 'test', + }, client: {} as unknown as OpenRouterCore, - tools: [manualTool], + tools: [ + manualTool, + ], }); // Bypass initStream by directly setting private fields. diff --git a/tests/unit/manual-tool-calls-adversarial.test.ts b/tests/unit/manual-tool-calls-adversarial.test.ts index 29fab7a..cde40f0 100644 --- a/tests/unit/manual-tool-calls-adversarial.test.ts +++ b/tests/unit/manual-tool-calls-adversarial.test.ts @@ -1,3 +1,5 @@ +import type { OpenRouterCore } from '@openrouter/sdk/core'; +import type { OpenResponsesResult } from '@openrouter/sdk/models/openresponsesresult'; import type { OpenResponsesStreamEvent, OpenResponsesStreamEventResponseCompleted, @@ -7,16 +9,13 @@ import type { OpenResponsesStreamEventResponseOutputItemDone, } from '@openrouter/sdk/models/openresponsesstreamevent'; import type { OutputFunctionCallItem } from '@openrouter/sdk/models/outputfunctioncallitem'; -import type { OpenRouterCore } from '@openrouter/sdk/core'; -import type { StreamableOutputItem } from '../../src/lib/stream-transformers.js'; -import type { OpenResponsesResult } from '@openrouter/sdk/models/openresponsesresult'; - import { describe, expect, it } from 'vitest'; import { z } from 'zod/v4'; -import { ReusableReadableStream } from '../../src/lib/reusable-stream.js'; import { ModelResult } from '../../src/lib/model-result.js'; -import { ToolType } from '../../src/lib/tool-types.js'; +import { ReusableReadableStream } from '../../src/lib/reusable-stream.js'; +import type { StreamableOutputItem } from '../../src/lib/stream-transformers.js'; import { isFunctionCallItem } from '../../src/lib/stream-type-guards.js'; +import { ToolType } from '../../src/lib/tool-types.js'; // ============================================================================ // Helpers @@ -36,6 +35,7 @@ function createImmediateStream( return new ReusableReadableStream(readable); } +// biome-ignore lint: test helper function function makeFunctionCallItem( callId: string, name: string, @@ -43,7 +43,14 @@ function makeFunctionCallItem( itemId: string, status: 'completed' | 'in_progress' = 'completed', ): OutputFunctionCallItem { - return { type: 'function_call', id: itemId, callId, name, arguments: args, status }; + return { + type: 'function_call', + id: itemId, + callId, + name, + arguments: args, + status, + }; } function outputItemAddedEvent( @@ -54,7 +61,14 @@ function outputItemAddedEvent( return { type: 'response.output_item.added', outputIndex: 0, - item: { type: 'function_call', id: itemId, callId, name, arguments: '', status: 'in_progress' }, + item: { + type: 'function_call', + id: itemId, + callId, + name, + arguments: '', + status: 'in_progress', + }, sequenceNumber: 0, }; } @@ -87,6 +101,7 @@ function argsDoneEvent( }; } +// biome-ignore lint: test helper function function outputItemDoneEvent( callId: string, name: string, @@ -96,7 +111,14 @@ function outputItemDoneEvent( return { type: 'response.output_item.done', outputIndex: 0, - item: { type: 'function_call', id: itemId, callId, name, arguments: args, status: 'completed' }, + item: { + type: 'function_call', + id: itemId, + callId, + name, + arguments: args, + status: 'completed', + }, sequenceNumber: 0, }; } @@ -159,8 +181,12 @@ function manualTool(name: string) { function: { name, description: `Manual tool: ${name}`, - inputSchema: z.object({ data: z.string() }), - outputSchema: z.object({ ok: z.boolean() }), + inputSchema: z.object({ + data: z.string(), + }), + outputSchema: z.object({ + ok: z.boolean(), + }), }, } as const; } @@ -171,9 +197,15 @@ function autoTool(name: string) { function: { name, description: `Auto tool: ${name}`, - inputSchema: z.object({ data: z.string() }), - outputSchema: z.object({ ok: z.boolean() }), - execute: async () => ({ ok: true }), + inputSchema: z.object({ + data: z.string(), + }), + outputSchema: z.object({ + ok: z.boolean(), + }), + execute: async () => ({ + ok: true, + }), }, } as const; } @@ -187,14 +219,25 @@ function setupModelResult( finalResponse?: OpenResponsesResult | null; allToolExecutionRounds?: Array<{ round: number; - toolCalls: Array<{ id: string; name: string; arguments: string }>; + toolCalls: Array<{ + id: string; + name: string; + arguments: string; + }>; response: OpenResponsesResult; - toolResults: Array<{ type: 'function_call_output'; callId: string; output: string }>; + toolResults: Array<{ + type: 'function_call_output'; + callId: string; + output: string; + }>; }>; }, ) { const modelResult = new ModelResult({ - request: { model: 'test-model', input: 'test' }, + request: { + model: 'test-model', + input: 'test', + }, client: {} as unknown as OpenRouterCore, tools, }); @@ -215,6 +258,7 @@ function setupModelResult( return modelResult; } +// biome-ignore lint: test helper function function streamEventsForToolCall(callId: string, name: string, args: string, itemId: string) { return [ outputItemAddedEvent(callId, name, itemId), @@ -243,11 +287,19 @@ describe('manual tool calls — adversarial edge cases', () => { const manualFc = makeFunctionCallItem('call_m1', 'submit', '{"data":"x"}', 'fc_m1'); const autoFc = makeFunctionCallItem('call_a1', 'fetch_data', '{"data":"y"}', 'fc_a1'); - const roundResponse = makeResponse([autoFc, manualFc]); - const finalResp = makeResponse([manualFc]); + const roundResponse = makeResponse([ + autoFc, + manualFc, + ]); + const finalResp = makeResponse([ + manualFc, + ]); const modelResult = setupModelResult( - [autoTool('fetch_data'), manualTool('submit')], + [ + autoTool('fetch_data'), + manualTool('submit'), + ], [], // no stream events needed — we drive via rounds + finalResponse { finalResponse: finalResp, @@ -255,11 +307,19 @@ describe('manual tool calls — adversarial edge cases', () => { { round: 1, toolCalls: [ - { id: 'call_a1', name: 'fetch_data', arguments: '{"data":"y"}' }, + { + id: 'call_a1', + name: 'fetch_data', + arguments: '{"data":"y"}', + }, ], response: roundResponse, toolResults: [ - { type: 'function_call_output', callId: 'call_a1', output: '{"ok":true}' }, + { + type: 'function_call_output', + callId: 'call_a1', + output: '{"ok":true}', + }, ], }, ], @@ -275,7 +335,10 @@ describe('manual tool calls — adversarial edge cases', () => { // PLUS yieldManualToolCalls may try to yield the manual one again from finalResponse. // Count how many times the manual tool call appears. const manualCalls = items.filter( - (i) => typeof i === 'object' && i !== null && 'type' in i && + (i) => + typeof i === 'object' && + i !== null && + 'type' in i && (i as OutputFunctionCallItem).type === 'function_call' && (i as OutputFunctionCallItem).name === 'submit', ); @@ -286,10 +349,14 @@ describe('manual tool calls — adversarial edge cases', () => { it('should yield manual tool calls from finalResponse when no rounds executed (pure manual scenario)', async () => { const manualFc = makeFunctionCallItem('call_m1', 'submit', '{"data":"x"}', 'fc_m1'); - const finalResp = makeResponse([manualFc]); + const finalResp = makeResponse([ + manualFc, + ]); const modelResult = setupModelResult( - [manualTool('submit')], + [ + manualTool('submit'), + ], [], // no stream events — finalResponse only { finalResponse: finalResp, @@ -303,7 +370,10 @@ describe('manual tool calls — adversarial edge cases', () => { } const manualCalls = items.filter( - (i) => typeof i === 'object' && i !== null && 'type' in i && + (i) => + typeof i === 'object' && + i !== null && + 'type' in i && (i as OutputFunctionCallItem).type === 'function_call' && (i as OutputFunctionCallItem).name === 'submit', ); @@ -321,12 +391,23 @@ describe('manual tool calls — adversarial edge cases', () => { const fc2 = makeFunctionCallItem('call_2', 'tool_b', '{"data":"b"}', 'fc_2'); const fc3 = makeFunctionCallItem('call_3', 'tool_c', '{"data":"c"}', 'fc_3'); - const finalResp = makeResponse([fc1, fc2, fc3]); + const finalResp = makeResponse([ + fc1, + fc2, + fc3, + ]); const modelResult = setupModelResult( - [manualTool('tool_a'), manualTool('tool_b'), manualTool('tool_c')], + [ + manualTool('tool_a'), + manualTool('tool_b'), + manualTool('tool_c'), + ], [], - { finalResponse: finalResp, allToolExecutionRounds: [] }, + { + finalResponse: finalResp, + allToolExecutionRounds: [], + }, ); const items: unknown[] = []; @@ -335,13 +416,20 @@ describe('manual tool calls — adversarial edge cases', () => { } const calls = items.filter( - (i) => typeof i === 'object' && i !== null && 'type' in i && + (i) => + typeof i === 'object' && + i !== null && + 'type' in i && (i as OutputFunctionCallItem).type === 'function_call', ); expect(calls).toHaveLength(3); const names = calls.map((c) => (c as OutputFunctionCallItem).name); - expect(names).toEqual(['tool_a', 'tool_b', 'tool_c']); + expect(names).toEqual([ + 'tool_a', + 'tool_b', + 'tool_c', + ]); }); }); @@ -351,13 +439,25 @@ describe('manual tool calls — adversarial edge cases', () => { describe('unknown tool name handling', () => { it('should NOT yield function_call for a tool name that does not exist in the tools list', async () => { // Model hallucinated a tool name that isn't registered - const unknownFc = makeFunctionCallItem('call_u1', 'nonexistent_tool', '{"data":"x"}', 'fc_u1'); - const finalResp = makeResponse([unknownFc]); + const unknownFc = makeFunctionCallItem( + 'call_u1', + 'nonexistent_tool', + '{"data":"x"}', + 'fc_u1', + ); + const finalResp = makeResponse([ + unknownFc, + ]); const modelResult = setupModelResult( - [manualTool('real_tool')], + [ + manualTool('real_tool'), + ], [], - { finalResponse: finalResp, allToolExecutionRounds: [] }, + { + finalResponse: finalResp, + allToolExecutionRounds: [], + }, ); const items: unknown[] = []; @@ -366,7 +466,10 @@ describe('manual tool calls — adversarial edge cases', () => { } const calls = items.filter( - (i) => typeof i === 'object' && i !== null && 'type' in i && + (i) => + typeof i === 'object' && + i !== null && + 'type' in i && (i as OutputFunctionCallItem).type === 'function_call', ); @@ -380,21 +483,37 @@ describe('manual tool calls — adversarial edge cases', () => { // We set allToolExecutionRounds to simulate that the auto tool already ran, // and finalResponse contains the auto tool's function_call. const autoFc = makeFunctionCallItem('call_a1', 'auto_fetch', '{"data":"x"}', 'fc_a1'); - const roundResponse = makeResponse([autoFc]); - const finalResp = makeResponse([autoFc]); + const roundResponse = makeResponse([ + autoFc, + ]); + const finalResp = makeResponse([ + autoFc, + ]); const modelResult = setupModelResult( - [autoTool('auto_fetch')], + [ + autoTool('auto_fetch'), + ], [], { finalResponse: finalResp, allToolExecutionRounds: [ { round: 1, - toolCalls: [{ id: 'call_a1', name: 'auto_fetch', arguments: '{"data":"x"}' }], + toolCalls: [ + { + id: 'call_a1', + name: 'auto_fetch', + arguments: '{"data":"x"}', + }, + ], response: roundResponse, toolResults: [ - { type: 'function_call_output', callId: 'call_a1', output: '{"ok":true}' }, + { + type: 'function_call_output', + callId: 'call_a1', + output: '{"ok":true}', + }, ], }, ], @@ -409,7 +528,10 @@ describe('manual tool calls — adversarial edge cases', () => { // yieldManualToolCalls should NOT yield auto_fetch because it has an execute function. // The only function_calls should come from the round iteration, not from yieldManualToolCalls. const manualCalls = items.filter( - (i) => typeof i === 'object' && i !== null && 'type' in i && + (i) => + typeof i === 'object' && + i !== null && + 'type' in i && (i as OutputFunctionCallItem).type === 'function_call' && (i as OutputFunctionCallItem).name === 'auto_fetch', ); @@ -428,9 +550,16 @@ describe('manual tool calls — adversarial edge cases', () => { const completionEvent = responseCompletedEvent([]); const modelResult = setupModelResult( - [manualTool('submit')], - [completionEvent], - { finalResponse: null, allToolExecutionRounds: [] }, + [ + manualTool('submit'), + ], + [ + completionEvent, + ], + { + finalResponse: null, + allToolExecutionRounds: [], + }, ); const items: unknown[] = []; @@ -445,9 +574,14 @@ describe('manual tool calls — adversarial edge cases', () => { const finalResp = makeResponse([]); const modelResult = setupModelResult( - [manualTool('submit')], + [ + manualTool('submit'), + ], [], - { finalResponse: finalResp, allToolExecutionRounds: [] }, + { + finalResponse: finalResp, + allToolExecutionRounds: [], + }, ); const items: unknown[] = []; @@ -470,15 +604,29 @@ describe('manual tool calls — adversarial edge cases', () => { id: 'msg_1', role: 'assistant' as const, status: 'completed' as const, - content: [{ type: 'output_text' as const, text: 'Here is your result', annotations: [] }], + content: [ + { + type: 'output_text' as const, + text: 'Here is your result', + annotations: [], + }, + ], }; - const finalResp = makeResponse([messageItem, manualFc]); + const finalResp = makeResponse([ + messageItem, + manualFc, + ]); const modelResult = setupModelResult( - [manualTool('submit')], + [ + manualTool('submit'), + ], [], - { finalResponse: finalResp, allToolExecutionRounds: [] }, + { + finalResponse: finalResp, + allToolExecutionRounds: [], + }, ); const items: unknown[] = []; @@ -487,7 +635,10 @@ describe('manual tool calls — adversarial edge cases', () => { } const calls = items.filter( - (i) => typeof i === 'object' && i !== null && 'type' in i && + (i) => + typeof i === 'object' && + i !== null && + 'type' in i && (i as OutputFunctionCallItem).type === 'function_call', ); @@ -510,11 +661,15 @@ describe('manual tool calls — adversarial edge cases', () => { const events: OpenResponsesStreamEvent[] = [ ...streamEventsForToolCall('call_m1', manualName, manualArgs, 'fc_m1'), - responseCompletedEvent([manualFc]), + responseCompletedEvent([ + manualFc, + ]), ]; const modelResult = setupModelResult( - [manualTool(manualName)], + [ + manualTool(manualName), + ], events, ); @@ -541,11 +696,17 @@ describe('manual tool calls — adversarial edge cases', () => { const events: OpenResponsesStreamEvent[] = [ ...streamEventsForToolCall('call_1', 'tool_a', '{"data":"a"}', 'fc_1'), ...streamEventsForToolCall('call_2', 'tool_b', '{"data":"b"}', 'fc_2'), - responseCompletedEvent([fc1, fc2]), + responseCompletedEvent([ + fc1, + fc2, + ]), ]; const modelResult = setupModelResult( - [manualTool('tool_a'), manualTool('tool_b')], + [ + manualTool('tool_a'), + manualTool('tool_b'), + ], events, ); @@ -559,9 +720,7 @@ describe('manual tool calls — adversarial edge cases', () => { ); expect(completedFunctionCalls).toHaveLength(2); - const names = completedFunctionCalls.map( - (fc) => (fc as OutputFunctionCallItem).name, - ); + const names = completedFunctionCalls.map((fc) => (fc as OutputFunctionCallItem).name); expect(names).toContain('tool_a'); expect(names).toContain('tool_b'); }); @@ -573,12 +732,19 @@ describe('manual tool calls — adversarial edge cases', () => { describe('edge case: empty arguments string', () => { it('should yield manual tool call even when arguments is an empty string', async () => { const fc = makeFunctionCallItem('call_1', 'no_args_tool', '', 'fc_1'); - const finalResp = makeResponse([fc]); + const finalResp = makeResponse([ + fc, + ]); const modelResult = setupModelResult( - [manualTool('no_args_tool')], + [ + manualTool('no_args_tool'), + ], [], - { finalResponse: finalResp, allToolExecutionRounds: [] }, + { + finalResponse: finalResp, + allToolExecutionRounds: [], + }, ); const items: unknown[] = []; @@ -587,7 +753,10 @@ describe('manual tool calls — adversarial edge cases', () => { } const calls = items.filter( - (i) => typeof i === 'object' && i !== null && 'type' in i && + (i) => + typeof i === 'object' && + i !== null && + 'type' in i && (i as OutputFunctionCallItem).type === 'function_call', ); @@ -603,12 +772,19 @@ describe('manual tool calls — adversarial edge cases', () => { it('should still yield manual tool calls even with in_progress status', async () => { // Sometimes the API may return items with unexpected statuses const fc = makeFunctionCallItem('call_1', 'submit', '{"data":"x"}', 'fc_1', 'in_progress'); - const finalResp = makeResponse([fc]); + const finalResp = makeResponse([ + fc, + ]); const modelResult = setupModelResult( - [manualTool('submit')], + [ + manualTool('submit'), + ], [], - { finalResponse: finalResp, allToolExecutionRounds: [] }, + { + finalResponse: finalResp, + allToolExecutionRounds: [], + }, ); const items: unknown[] = []; @@ -617,7 +793,10 @@ describe('manual tool calls — adversarial edge cases', () => { } const calls = items.filter( - (i) => typeof i === 'object' && i !== null && 'type' in i && + (i) => + typeof i === 'object' && + i !== null && + 'type' in i && (i as OutputFunctionCallItem).type === 'function_call', ); @@ -635,10 +814,15 @@ describe('manual tool calls — adversarial edge cases', () => { describe('edge case: no tools configured', () => { it('should not crash when tools array is empty and finalResponse has function_calls', async () => { const fc = makeFunctionCallItem('call_1', 'ghost_tool', '{"data":"x"}', 'fc_1'); - const finalResp = makeResponse([fc]); + const finalResp = makeResponse([ + fc, + ]); const modelResult = new ModelResult({ - request: { model: 'test-model', input: 'test' }, + request: { + model: 'test-model', + input: 'test', + }, client: {} as unknown as OpenRouterCore, tools: [], }); @@ -656,7 +840,10 @@ describe('manual tool calls — adversarial edge cases', () => { // No tools registered, so isManualToolCall returns false for everything const calls = items.filter( - (i) => typeof i === 'object' && i !== null && 'type' in i && + (i) => + typeof i === 'object' && + i !== null && + 'type' in i && (i as OutputFunctionCallItem).type === 'function_call', ); expect(calls).toHaveLength(0); @@ -675,24 +862,48 @@ describe('manual tool calls — adversarial edge cases', () => { id: 'msg_1', role: 'assistant' as const, status: 'completed' as const, - content: [{ type: 'output_text' as const, text: 'Done!', annotations: [] }], + content: [ + { + type: 'output_text' as const, + text: 'Done!', + annotations: [], + }, + ], }; - const roundResponse = makeResponse([autoFc]); - const finalResp = makeResponse([manualFc, messageItem]); + const roundResponse = makeResponse([ + autoFc, + ]); + const finalResp = makeResponse([ + manualFc, + messageItem, + ]); const modelResult = setupModelResult( - [autoTool('fetch_data'), manualTool('submit')], + [ + autoTool('fetch_data'), + manualTool('submit'), + ], [], { finalResponse: finalResp, allToolExecutionRounds: [ { round: 1, - toolCalls: [{ id: 'call_a1', name: 'fetch_data', arguments: '{"data":"y"}' }], + toolCalls: [ + { + id: 'call_a1', + name: 'fetch_data', + arguments: '{"data":"y"}', + }, + ], response: roundResponse, toolResults: [ - { type: 'function_call_output', callId: 'call_a1', output: '{"ok":true}' }, + { + type: 'function_call_output', + callId: 'call_a1', + output: '{"ok":true}', + }, ], }, ], @@ -706,13 +917,23 @@ describe('manual tool calls — adversarial edge cases', () => { // Find indices of manual function_call and message const manualIdx = items.findIndex( - (i) => typeof i === 'object' && i !== null && 'type' in i && + (i) => + typeof i === 'object' && + i !== null && + 'type' in i && (i as OutputFunctionCallItem).type === 'function_call' && (i as OutputFunctionCallItem).name === 'submit', ); const messageIdx = items.findIndex( - (i) => typeof i === 'object' && i !== null && 'type' in i && - (i as { type: string }).type === 'message', + (i) => + typeof i === 'object' && + i !== null && + 'type' in i && + ( + i as { + type: string; + } + ).type === 'message', ); // Manual tool calls must come before the message diff --git a/tests/unit/openrouter.test.ts b/tests/unit/openrouter.test.ts index 4a9f7b8..839d960 100644 --- a/tests/unit/openrouter.test.ts +++ b/tests/unit/openrouter.test.ts @@ -1,7 +1,7 @@ -import { describe, expect, it } from 'vitest'; import { OpenRouterCore } from '@openrouter/sdk/core'; -import { OpenRouter } from '../../src/openrouter.js'; +import { describe, expect, it } from 'vitest'; import { ModelResult } from '../../src/lib/model-result.js'; +import { OpenRouter } from '../../src/openrouter.js'; describe('OpenRouter', () => { it('should instantiate without options', () => { @@ -10,12 +10,16 @@ describe('OpenRouter', () => { }); it('should instantiate with apiKey option', () => { - const openrouter = new OpenRouter({ apiKey: 'test-key' }); + const openrouter = new OpenRouter({ + apiKey: 'test-key', + }); expect(openrouter).toBeInstanceOf(OpenRouter); }); it('should return a ModelResult from callModel', () => { - const openrouter = new OpenRouter({ apiKey: 'test-key' }); + const openrouter = new OpenRouter({ + apiKey: 'test-key', + }); const result = openrouter.callModel({ model: 'openai/gpt-4o', input: 'Hello', @@ -24,7 +28,9 @@ describe('OpenRouter', () => { }); it('should preserve this binding when callModel is destructured', () => { - const openrouter = new OpenRouter({ apiKey: 'test-key' }); + const openrouter = new OpenRouter({ + apiKey: 'test-key', + }); const { callModel } = openrouter; const result = callModel({ model: 'openai/gpt-4o', @@ -42,7 +48,9 @@ describe('OpenRouter', () => { }); it('should be an instance of OpenRouterCore', () => { - const openrouter = new OpenRouter({ apiKey: 'test-key' }); + const openrouter = new OpenRouter({ + apiKey: 'test-key', + }); expect(openrouter).toBeInstanceOf(OpenRouterCore); }); @@ -57,7 +65,9 @@ describe('OpenRouter', () => { }); it('should be usable as both OpenRouter and OpenRouterCore', () => { - const openrouter = new OpenRouter({ apiKey: 'test-key' }); + const openrouter = new OpenRouter({ + apiKey: 'test-key', + }); expect(openrouter).toBeInstanceOf(OpenRouter); expect(openrouter).toBeInstanceOf(OpenRouterCore); // Calling callModel should still work diff --git a/tests/unit/shared-context.test.ts b/tests/unit/shared-context.test.ts index 6362a58..cba93a0 100644 --- a/tests/unit/shared-context.test.ts +++ b/tests/unit/shared-context.test.ts @@ -1,8 +1,7 @@ -import type { TurnContext } from '../../src/lib/tool-types.js'; - import { describe, expect, it } from 'vitest'; import * as z4 from 'zod/v4'; import { buildToolExecuteContext, ToolContextStore } from '../../src/lib/tool-context.js'; +import type { TurnContext } from '../../src/lib/tool-types.js'; import { SHARED_CONTEXT_KEY } from '../../src/lib/tool-types.js'; //#region Shared context via buildToolExecuteContext diff --git a/tests/unit/tool-context.test.ts b/tests/unit/tool-context.test.ts index cff8ecc..3b743c6 100644 --- a/tests/unit/tool-context.test.ts +++ b/tests/unit/tool-context.test.ts @@ -1,5 +1,3 @@ -import type { TurnContext } from '../../src/lib/tool-types.js'; - import { describe, expect, it } from 'vitest'; import * as z4 from 'zod/v4'; import { tool } from '../../src/lib/tool.js'; @@ -9,6 +7,7 @@ import { resolveContext, ToolContextStore, } from '../../src/lib/tool-context.js'; +import type { TurnContext } from '../../src/lib/tool-types.js'; //#region ToolContextStore diff --git a/tests/unit/tool-event-broadcaster.test.ts b/tests/unit/tool-event-broadcaster.test.ts index 2c99e62..290f13f 100644 --- a/tests/unit/tool-event-broadcaster.test.ts +++ b/tests/unit/tool-event-broadcaster.test.ts @@ -73,11 +73,17 @@ describe('ToolEventBroadcaster', () => { const results2: string[] = []; await Promise.all([ + // biome-ignore lint: IIFE used in test for async consumption (async () => { - for await (const e of consumer1) results1.push(e); + for await (const e of consumer1) { + results1.push(e); + } })(), + // biome-ignore lint: IIFE used in test for async consumption (async () => { - for await (const e of consumer2) results2.push(e); + for await (const e of consumer2) { + results2.push(e); + } })(), ]); @@ -110,7 +116,9 @@ describe('ToolEventBroadcaster', () => { // Consumer 1 continues from position 1 const remaining1: number[] = []; - for await (const e of consumer1) remaining1.push(e); + for await (const e of consumer1) { + remaining1.push(e); + } expect(remaining1).toEqual([ 2, 3, @@ -118,7 +126,9 @@ describe('ToolEventBroadcaster', () => { // Consumer 2 gets all events from position 0 const all2: number[] = []; - for await (const e of consumer2) all2.push(e); + for await (const e of consumer2) { + all2.push(e); + } expect(all2).toEqual([ 1, 2, @@ -133,6 +143,7 @@ describe('ToolEventBroadcaster', () => { const consumer = broadcaster.createConsumer(); // Start consuming before events arrive + // biome-ignore lint: IIFE used in test for async consumption const consumePromise = (async () => { const results: number[] = []; for await (const event of consumer) { @@ -160,6 +171,7 @@ describe('ToolEventBroadcaster', () => { const consumer = broadcaster.createConsumer(); const received: number[] = []; + // biome-ignore lint: IIFE used in test for async consumption const consumePromise = (async () => { for await (const event of consumer) { received.push(event); @@ -220,6 +232,7 @@ describe('ToolEventBroadcaster', () => { const consumer = broadcaster.createConsumer(); // Start consuming (will wait) + // biome-ignore lint: IIFE used in test for async consumption const consumePromise = (async () => { const results: number[] = []; for await (const event of consumer) { diff --git a/tests/unit/tool-executor-return.test.ts b/tests/unit/tool-executor-return.test.ts index 5bc8864..b7f7ce8 100644 --- a/tests/unit/tool-executor-return.test.ts +++ b/tests/unit/tool-executor-return.test.ts @@ -1,9 +1,8 @@ -import type { ParsedToolCall, Tool, TurnContext } from '../../src/lib/tool-types.js'; - import { describe, expect, it } from 'vitest'; import { z } from 'zod/v4'; import { tool } from '../../src/lib/tool.js'; import { executeGeneratorTool } from '../../src/lib/tool-executor.js'; +import type { ParsedToolCall, Tool, TurnContext } from '../../src/lib/tool-types.js'; describe('executeGeneratorTool - return value capture', () => { const mockContext: TurnContext = { diff --git a/tests/unit/tool-type-compat.test.ts b/tests/unit/tool-type-compat.test.ts index 1dfa2fb..0e3103d 100644 --- a/tests/unit/tool-type-compat.test.ts +++ b/tests/unit/tool-type-compat.test.ts @@ -378,7 +378,7 @@ describe('Tool Type Compatibility with plain zod imports', () => { any: z.any(), unknown: z.unknown(), }), - execute: async (params) => ({ + execute: async (_params) => ({ received: true, }), }); diff --git a/tests/unit/turn-end-race-condition.test.ts b/tests/unit/turn-end-race-condition.test.ts index 88c19fe..164275a 100644 --- a/tests/unit/turn-end-race-condition.test.ts +++ b/tests/unit/turn-end-race-condition.test.ts @@ -109,6 +109,7 @@ describe('turn.end race condition', () => { const reusableStream = new ReusableReadableStream(stream); // Fire-and-forget pipe (the buggy pattern) + // biome-ignore lint: IIFE used in test to reproduce race condition const pipePromise = (async () => { broadcaster.push({ type: 'turn.start', @@ -129,6 +130,7 @@ describe('turn.end race condition', () => { const broadcastConsumer = broadcaster.createConsumer(); // Simulate executeToolsIfNeeded completing before the pipe finishes + // biome-ignore lint: IIFE used in test to reproduce race condition const executionPromise = (async () => { await new Promise((r) => setTimeout(r, 5)); })().finally(() => { @@ -182,6 +184,7 @@ describe('turn.end race condition', () => { ); const reusableStream = new ReusableReadableStream(stream); + // biome-ignore lint: IIFE used in test to reproduce race condition const pipePromise = (async () => { broadcaster.push({ type: 'turn.start', @@ -202,6 +205,7 @@ describe('turn.end race condition', () => { const broadcastConsumer = broadcaster.createConsumer(); // FIX: await pipePromise before calling complete() + // biome-ignore lint: IIFE used in test to reproduce race condition const executionPromise = (async () => { await new Promise((r) => setTimeout(r, 5)); })().finally(async () => {