diff --git a/package-lock.json b/package-lock.json index e1b9794..c036088 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,17 +17,31 @@ "devDependencies": { "@ai-sdk/provider": "^3.0.13", "@opencode-ai/sdk": "^1.17.14", + "@opentui/core": "^0.4.3", + "@opentui/solid": "^0.4.3", "@types/node": "^26.0.0", "@types/semver": "^7.7.1", + "solid-js": "^1.9.12", "tsup": "^8.5.1", "typescript": "^6.0.3", "vitest": "^4.1.8" }, "engines": { - "node": ">=22.0.0" + "node": ">=22.0.0", + "opencode": ">=1.17.0" }, "peerDependencies": { - "@ai-sdk/provider": "^3.0.0" + "@ai-sdk/provider": "^3.0.0", + "@opentui/core": "*", + "@opentui/solid": "*" + }, + "peerDependenciesMeta": { + "@opentui/core": { + "optional": true + }, + "@opentui/solid": { + "optional": true + } } }, "node_modules/@ai-sdk/provider": { @@ -43,6 +57,483 @@ "node": ">=18" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@bufbuild/protobuf": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.12.1.tgz", @@ -698,7 +1189,7 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -709,7 +1200,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -719,14 +1210,14 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -818,74 +1309,255 @@ "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } + "dependencies": { + "@tybys/wasm-util": "^0.10.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@opencode-ai/plugin": { + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.17.14.tgz", + "integrity": "sha512-upKf4QHZqjr2cqHJcJiGTUSGJFFVR26Nu8Y2QRThQ2NgXcQ44T1cRvI80nhK87wQsFcHPI842d+cPYERV+lB4w==", + "license": "MIT", + "dependencies": { + "@ai-sdk/provider": "3.0.8", + "@opencode-ai/sdk": "1.17.14", + "effect": "4.0.0-beta.83", + "zod": "4.1.8" + }, + "peerDependencies": { + "@opentui/core": ">=0.4.3", + "@opentui/keymap": ">=0.4.3", + "@opentui/solid": ">=0.4.3" + }, + "peerDependenciesMeta": { + "@opentui/core": { + "optional": true + }, + "@opentui/keymap": { + "optional": true + }, + "@opentui/solid": { + "optional": true + } + } + }, + "node_modules/@opencode-ai/plugin/node_modules/@ai-sdk/provider": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz", + "integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@opencode-ai/plugin/node_modules/zod": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", + "integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@opencode-ai/sdk": { + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.17.14.tgz", + "integrity": "sha512-ycSYSF0kuJmNUP38VYnsFEoUWPyjlLjAZFPtYuMt+32Tz+NqVsAPcqK3MPBRd5v+GChjAyApB/y56i7Ub08IAg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "7.0.6" + } + }, + "node_modules/@opentui/core": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@opentui/core/-/core-0.4.3.tgz", + "integrity": "sha512-rrJfAk13tALDqldYjhc78eWQ+aKq1iknJgffIOg3OwyZoqQo+p6gtuqyhmWvXIfQzlNUbpgpCPcxbXlhMnlaHQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "bun-ffi-structs": "0.2.4", + "diff": "9.0.0", + "marked": "17.0.1", + "string-width": "7.2.0", + "strip-ansi": "7.1.2" + }, + "optionalDependencies": { + "@opentui/core-darwin-arm64": "0.4.3", + "@opentui/core-darwin-x64": "0.4.3", + "@opentui/core-linux-arm64": "0.4.3", + "@opentui/core-linux-arm64-musl": "0.4.3", + "@opentui/core-linux-x64": "0.4.3", + "@opentui/core-linux-x64-musl": "0.4.3", + "@opentui/core-win32-arm64": "0.4.3", + "@opentui/core-win32-x64": "0.4.3" + }, + "peerDependencies": { + "web-tree-sitter": "0.25.10" + } + }, + "node_modules/@opentui/core-darwin-arm64": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-arm64/-/core-darwin-arm64-0.4.3.tgz", + "integrity": "sha512-p5+7AAxpxGuDGagyQfewKtmTFnN7THvTVY4FyKqUtJomNaHdQXPHztapNNzMx0DGWbwOUbVKzpL+yc3CZY3chQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@opentui/core-darwin-x64": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-x64/-/core-darwin-x64-0.4.3.tgz", + "integrity": "sha512-+fh0vEUE0lwVC7RW5ijYLRlTLp5NfvCRj8SzxDVd7IL2j2ssB6YXcfIbXq2EW7UGnrejwPRXf1tgUrIXW9KmOw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@opentui/core-linux-arm64": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64/-/core-linux-arm64-0.4.3.tgz", + "integrity": "sha512-gl6qA5QJy6u8Cbt7gOtHbhhfMZ4qQDb0kEwFXHcMGmbnKzz4OHoq74D6tNjyvSQB9saoC7C6C0tvn2DcJOuNog==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opentui/core-linux-arm64-musl": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64-musl/-/core-linux-arm64-musl-0.4.3.tgz", + "integrity": "sha512-8p8g8/AEq/xFGpQ7XcIFKcAqjc0QwsZcv+Ll9RbCDpUA56FGH6jfLDir0KYTNTgYXJTIrBIENI9K46VuxMUMQA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opentui/core-linux-x64": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64/-/core-linux-x64-0.4.3.tgz", + "integrity": "sha512-dXpJitiZdYE3hq2Pvx6e9I0uPQSOcnaLLp1pDgWAHv+3kvKSHEX//9Yr/pV/Ua6qqT7p+2D/K4vXNap/NKVo2w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opentui/core-linux-x64-musl": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64-musl/-/core-linux-x64-musl-0.4.3.tgz", + "integrity": "sha512-/QiFpCrpU2O7vy8QYmLIQYbvAtKDgmqcVjR7dGtqSzkiQk3ktNJoo5RozG7ueXnjung1Wp0nKldKxo2Csg/OrA==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opentui/core-win32-arm64": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-arm64/-/core-win32-arm64-0.4.3.tgz", + "integrity": "sha512-Mx2zuOjrhm/z2SDS6RExIyjP/SnN/8QhhagxURUw0jQi/NssGSeAllu1cBAFFnhobJL5QLTE4FU4CRhUK9svgg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@opentui/core-win32-x64": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-x64/-/core-win32-x64-0.4.3.tgz", + "integrity": "sha512-NuoqvWKGXaYnmlqvu7Gg2lLI6yVMnS9OfWBvxp+7Q+McSgHFSTQmYBXaPpvQ8HikpQXE1nCeMPtuSG4PdZHe2w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@opencode-ai/plugin": { - "version": "1.17.14", - "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.17.14.tgz", - "integrity": "sha512-upKf4QHZqjr2cqHJcJiGTUSGJFFVR26Nu8Y2QRThQ2NgXcQ44T1cRvI80nhK87wQsFcHPI842d+cPYERV+lB4w==", + "node_modules/@opentui/core/node_modules/bun-ffi-structs": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/bun-ffi-structs/-/bun-ffi-structs-0.2.4.tgz", + "integrity": "sha512-AJzsqoVFs1KBbJbWHIYrVZLDC3NhTqqh25awRXqzoLzmBAKr5oqk6+CwuYHAekKx+VBCYVohBoKuRq40dV+TYg==", + "devOptional": true, "license": "MIT", - "dependencies": { - "@ai-sdk/provider": "3.0.8", - "@opencode-ai/sdk": "1.17.14", - "effect": "4.0.0-beta.83", - "zod": "4.1.8" - }, "peerDependencies": { - "@opentui/core": ">=0.4.3", - "@opentui/keymap": ">=0.4.3", - "@opentui/solid": ">=0.4.3" - }, - "peerDependenciesMeta": { - "@opentui/core": { - "optional": true - }, - "@opentui/keymap": { - "optional": true - }, - "@opentui/solid": { - "optional": true - } + "typescript": "^5" } }, - "node_modules/@opencode-ai/plugin/node_modules/@ai-sdk/provider": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz", - "integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==", + "node_modules/@opentui/core/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": ">=18" - } - }, - "node_modules/@opencode-ai/plugin/node_modules/zod": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", - "integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" + "node": ">=14.17" } }, - "node_modules/@opencode-ai/sdk": { - "version": "1.17.14", - "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.17.14.tgz", - "integrity": "sha512-ycSYSF0kuJmNUP38VYnsFEoUWPyjlLjAZFPtYuMt+32Tz+NqVsAPcqK3MPBRd5v+GChjAyApB/y56i7Ub08IAg==", + "node_modules/@opentui/solid": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@opentui/solid/-/solid-0.4.3.tgz", + "integrity": "sha512-RcV0+S8HMdXOASyr7HmJUBuTUIaFPzAxMDa44VftS5C2JUgrmAuWo0Njv1q3TWRB1owjHnyKhEfWGKq7A82wxw==", + "devOptional": true, "license": "MIT", "dependencies": { - "cross-spawn": "7.0.6" + "@babel/core": "7.28.0", + "@babel/preset-typescript": "7.27.1", + "@opentui/core": "0.4.3", + "babel-plugin-module-resolver": "5.0.2", + "babel-preset-solid": "1.9.12", + "entities": "7.0.1", + "s-js": "^0.4.9" + }, + "peerDependencies": { + "solid-js": "1.9.12" } }, "node_modules/@oxc-project/types": { @@ -1769,6 +2441,19 @@ "node": ">=0.4.0" } }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -1786,6 +2471,133 @@ "node": ">=12" } }, + "node_modules/babel-plugin-jsx-dom-expressions": { + "version": "0.40.7", + "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.7.tgz", + "integrity": "sha512-/O6JWUmjv03OI9lL2ry9bUjpD5S3PclM55RRJEyCdcFZ5W2SEA/59d+l2hNsk3gI6kiWRdRPdOtqZmsQzFN1pQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "7.18.6", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.20.7", + "html-entities": "2.3.3", + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.20.12" + } + }, + "node_modules/babel-plugin-jsx-dom-expressions/node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/babel-plugin-module-resolver": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-5.0.2.tgz", + "integrity": "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "find-babel-config": "^2.1.1", + "glob": "^9.3.3", + "pkg-up": "^3.1.0", + "reselect": "^4.1.7", + "resolve": "^1.22.8" + } + }, + "node_modules/babel-preset-solid": { + "version": "1.9.12", + "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.12.tgz", + "integrity": "sha512-LLqnuKVDlKpyBlMPcH6qEvs/wmS9a+NczppxJ3ryS/c0O5IiSFOIBQi9GzyiGDSbcJpx4Gr87jyFTos1MyEuWg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jsx-dom-expressions": "^0.40.6" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "solid-js": "^1.9.12" + }, + "peerDependenciesMeta": { + "solid-js": { + "optional": true + } + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.42", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.42.tgz", + "integrity": "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.5.tgz", + "integrity": "sha512-Cu2E6QejHWzuDMTkuwgpABFgDfZrXLQq5V13YOACZx4mFAG4IwGTbTfHPMr4WtxlHoXSM8FIuRwYYCz5XiabaQ==", + "devOptional": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001800", + "electron-to-chromium": "^1.5.387", + "node-releases": "^2.0.50", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/bundle-require": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", @@ -1812,6 +2624,27 @@ "node": ">=8" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001803", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001803.tgz", + "integrity": "sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg==", + "devOptional": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -1869,7 +2702,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/cross-spawn": { @@ -1886,11 +2719,18 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1914,6 +2754,16 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/effect": { "version": "4.0.0-beta.83", "resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-beta.83.tgz", @@ -1932,6 +2782,43 @@ "yaml": "^2.9.0" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.389", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.389.tgz", + "integrity": "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", @@ -1981,6 +2868,16 @@ "@esbuild/win32-x64": "0.27.7" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -2041,12 +2938,35 @@ } } }, + "node_modules/find-babel-config": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-2.1.2.tgz", + "integrity": "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.3" + } + }, "node_modules/find-my-way-ts": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/find-my-way-ts/-/find-my-way-ts-0.1.6.tgz", "integrity": "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==", "license": "MIT" }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/fix-dts-default-cjs-exports": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", @@ -2059,6 +2979,13 @@ "rollup": "^4.34.8" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "devOptional": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2066,14 +2993,87 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "devOptional": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 0.4" } }, + "node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/ini": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/ini/-/ini-7.0.0.tgz", @@ -2083,6 +3083,22 @@ "node": "^22.22.2 || ^24.15.0 || >=26.0.0" } }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2099,12 +3115,45 @@ "node": ">=10" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "devOptional": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", "license": "(AFL-2.1 OR BSD-3-Clause)" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "devOptional": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/kubernetes-types": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/kubernetes-types/-/kubernetes-types-1.30.0.tgz", @@ -2414,6 +3463,30 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -2424,6 +3497,45 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/marked": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", + "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", + "devOptional": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/minimatch": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", + "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, "node_modules/mlly": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", @@ -2441,7 +3553,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/msgpackr": { @@ -2527,6 +3639,16 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2551,6 +3673,81 @@ "node": ">=12.20.0" } }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2560,6 +3757,47 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "devOptional": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "devOptional": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -2571,7 +3809,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/picomatch": { @@ -2609,6 +3847,19 @@ "pathe": "^2.0.1" } }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/postcss": { "version": "8.5.15", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", @@ -2711,6 +3962,35 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -2800,6 +4080,13 @@ "fsevents": "~2.3.2" } }, + "node_modules/s-js": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/s-js/-/s-js-0.4.9.tgz", + "integrity": "sha512-RtpOm+cM6O0sHg6IA70wH+UC3FZcND+rccBZpBAHzlUgNO2Bm5BN+FnM8+OBxzXdwpKWFwX11JGF0MFRkhSoIQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/semver": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", @@ -2812,6 +4099,29 @@ "node": ">=10" } }, + "node_modules/seroval": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.5.tgz", + "integrity": "sha512-bSjOuPcwPKLSJNhr9+bZxA20nQxVle5J5MNsYRVE6cIg7KpRLXGupymePavu0jrxlPiPsr4xGZSB8yUY2sH2sw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/seroval-plugins": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.5.tgz", + "integrity": "sha512-+BDhqYM6CEn3x09v44dpa9p6974FuUB2dxk+Ctn04k0cO1Zt6QODTXfmEZK0eBaTe/fJBvP4NMGuNJ+R8T+QMg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "seroval": "^1.0" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2840,6 +4150,18 @@ "dev": true, "license": "ISC" }, + "node_modules/solid-js": { + "version": "1.9.12", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.12.tgz", + "integrity": "sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.1.0", + "seroval": "~1.5.0", + "seroval-plugins": "~1.5.0" + } + }, "node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -2874,6 +4196,40 @@ "dev": true, "license": "MIT" }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", @@ -2897,6 +4253,19 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -3085,6 +4454,37 @@ "dev": true, "license": "MIT" }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "devOptional": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uuid": { "version": "14.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", @@ -3276,6 +4676,22 @@ "node": ">=18" } }, + "node_modules/web-tree-sitter": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.25.10.tgz", + "integrity": "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA==", + "devOptional": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@types/emscripten": "^1.40.0" + }, + "peerDependenciesMeta": { + "@types/emscripten": { + "optional": true + } + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3308,6 +4724,13 @@ "node": ">=8" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "devOptional": true, + "license": "ISC" + }, "node_modules/yaml": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", diff --git a/package.json b/package.json index 128bde5..b7aaaba 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "url": "https://github.com/stablekernel/opencode-cursor/issues" }, "engines": { - "node": ">=22.0.0" + "node": ">=22.0.0", + "opencode": ">=1.17.0" }, "keywords": [ "opencode", @@ -39,11 +40,18 @@ }, "./server": { "types": "./dist/plugin/index.d.ts", - "import": "./dist/plugin/index.js" + "import": "./dist/plugin/index.js", + "config": { + "custom": true + } }, "./plugin": { "types": "./dist/plugin/index.d.ts", "import": "./dist/plugin/index.js" + }, + "./tui": { + "types": "./dist/tui/index.d.ts", + "import": "./dist/tui/index.js" } }, "scripts": { @@ -66,13 +74,30 @@ "node-gyp": "^12.4.0" }, "peerDependencies": { - "@ai-sdk/provider": "^3.0.0" + "@ai-sdk/provider": "^3.0.0", + "@opentui/core": "*", + "@opentui/solid": "*", + "solid-js": "*" + }, + "peerDependenciesMeta": { + "@opentui/core": { + "optional": true + }, + "@opentui/solid": { + "optional": true + }, + "solid-js": { + "optional": true + } }, "devDependencies": { "@ai-sdk/provider": "^3.0.13", "@opencode-ai/sdk": "^1.17.14", + "@opentui/core": "^0.4.3", + "@opentui/solid": "^0.4.3", "@types/node": "^26.0.0", "@types/semver": "^7.7.1", + "solid-js": "^1.9.12", "tsup": "^8.5.1", "typescript": "^6.0.3", "vitest": "^4.1.8" diff --git a/src/fallback-models.ts b/src/fallback-models.ts index 067952e..c33ad9a 100644 --- a/src/fallback-models.ts +++ b/src/fallback-models.ts @@ -15,6 +15,10 @@ export const FALLBACK_MODELS: ModelListItem[] = [ parameters: [ { id: "thinking", displayName: "Thinking", values: [{ value: "off" }, { value: "on" }] }, ], + variants: [ + { params: [{ id: "thinking", value: "off" }], displayName: "Composer 2.5", isDefault: true }, + { params: [{ id: "thinking", value: "on" }], displayName: "Composer 2.5" }, + ], }, { id: "claude-opus-4-8", displayName: "Claude Opus 4.8 (via Cursor)" }, { id: "claude-sonnet-4-6", displayName: "Claude Sonnet 4.6 (via Cursor)" }, diff --git a/src/model-axes.ts b/src/model-axes.ts new file mode 100644 index 0000000..bafd65f --- /dev/null +++ b/src/model-axes.ts @@ -0,0 +1,156 @@ +import type { ModelListItem } from "@cursor/sdk"; + +export interface ModelAxis { + /** The Cursor param id: "effort" | "reasoning" | "thinking" | "fast" | "context" | ... */ + id: string; + /** Human label for UI, e.g. "Effort". */ + label: string; + /** Distinct values in stable, meaningful order. */ + values: string[]; + /** Seed value: from the isDefault variant, else values[0]. */ + default: string; + /** ">2 values" = cycle, exactly 2 = toggle. */ + kind: "cycle" | "toggle"; +} + +// Preferred value orderings so cyclers advance in an intuitive direction. +// Any value not listed keeps first-seen order, appended after known ones. +const VALUE_ORDER: Record = { + effort: ["low", "medium", "high", "xhigh", "max"], + reasoning: ["none", "low", "medium", "high", "extra-high", "xhigh", "max"], + context: ["200k", "272k", "300k", "1m"], + thinking: ["false", "true"], + fast: ["false", "true"], +}; + +const LABELS: Record = { + effort: "Effort", + reasoning: "Reasoning", + thinking: "Thinking", + fast: "Fast", + context: "Context", +}; + +function orderValues(id: string, seen: string[]): string[] { + const pref = VALUE_ORDER[id]; + if (!pref) return seen; + const known = pref.filter((v) => seen.includes(v)); + const extra = seen.filter((v) => !pref.includes(v)); + return [...known, ...extra]; +} + +/** + * Group the model's enumerated variants by param id into independent axes. + * A param that only ever has one value across all variants is pinned (not an + * axis). Default per axis is taken from the variant flagged isDefault. + */ +export function buildModelAxes(item: ModelListItem): ModelAxis[] { + const variants = item.variants ?? []; + if (variants.length <= 1) return []; + + // Collect distinct values per param id, and the default variant's value. + const valuesById = new Map>(); + const defaultVariant = variants.find((v) => v.isDefault); + const defaultById = new Map(); + + for (const v of variants) { + for (const p of v.params ?? []) { + let set = valuesById.get(p.id); + if (!set) { + set = new Set(); + valuesById.set(p.id, set); + } + set.add(p.value); + } + } + for (const p of defaultVariant?.params ?? []) { + defaultById.set(p.id, p.value); + } + + const axes: ModelAxis[] = []; + for (const [id, set] of valuesById) { + if (set.size <= 1) continue; // pinned param (e.g. cyber=false) -> not an axis + const values = orderValues(id, [...set]); + const fallback = values[0]; + if (fallback === undefined) continue; // satisfies noUncheckedIndexedAccess + const def = defaultById.get(id) ?? fallback; + axes.push({ + id, + label: LABELS[id] ?? id, + values, + default: def, + kind: values.length === 2 ? "toggle" : "cycle", + }); + } + return axes; +} + +/** Serialize a param map to a stable key for set membership. */ +function comboKey(params: Record): string { + return Object.keys(params) + .sort() + .map((k) => `${k}=${params[k]}`) + .join("|"); +} + +/** Build the set of all offered variant combos (over the axis param ids). */ +function offeredCombos(item: ModelListItem, axisIds: string[]): Set { + const out = new Set(); + for (const v of item.variants ?? []) { + const proj: Record = {}; + for (const p of v.params ?? []) { + if (axisIds.includes(p.id)) proj[p.id] = p.value; + } + out.add(comboKey(proj)); + } + return out; +} + +/** + * True if `params` (restricted to the model's axis ids) matches an offered + * variant. Params outside the axis set are ignored. + */ +export function isValidCombo(item: ModelListItem, params: Record): boolean { + const axisIds = buildModelAxes(item).map((a) => a.id); + if (axisIds.length === 0) return true; + const proj: Record = {}; + for (const id of axisIds) { + const val = params[id]; + if (val !== undefined) proj[id] = val; + } + return offeredCombos(item, axisIds).has(comboKey(proj)); +} + +/** + * If `params` is invalid after changing `changedAxisId`, adjust the OTHER axes + * to the nearest offered combo that preserves the changed axis's value. Picks + * the first offered variant whose changed-axis value matches; falls back to the + * default variant if none match. Never mutates the changed axis. + */ +export function snapCombo( + item: ModelListItem, + params: Record, + changedAxisId: string, +): Record { + if (isValidCombo(item, params)) return params; + const axisIds = buildModelAxes(item).map((a) => a.id); + const target = params[changedAxisId]; + + // Prefer an offered variant that keeps the changed axis value. + for (const v of item.variants ?? []) { + const proj: Record = {}; + for (const p of v.params ?? []) { + if (axisIds.includes(p.id)) proj[p.id] = p.value; + } + if (proj[changedAxisId] === target) return proj; + } + + // No variant supports the changed value together with anything: fall back to + // the default variant's projection. + const def = (item.variants ?? []).find((v) => v.isDefault) ?? (item.variants ?? [])[0]; + const proj: Record = {}; + for (const p of def?.params ?? []) { + if (axisIds.includes(p.id)) proj[p.id] = p.value; + } + return proj; +} diff --git a/src/model-discovery.ts b/src/model-discovery.ts index f741c3c..c1e9e9e 100644 --- a/src/model-discovery.ts +++ b/src/model-discovery.ts @@ -78,6 +78,15 @@ export async function discoverModels(options: DiscoverOptions = {}): Promise /think|reason/i.test(p.id)); diff --git a/src/model-variants.ts b/src/model-variants.ts index 1f696ce..9dd8174 100644 --- a/src/model-variants.ts +++ b/src/model-variants.ts @@ -1,4 +1,5 @@ import type { ModelListItem } from "@cursor/sdk"; +import { buildModelAxes } from "./model-axes.js"; /** * A Cursor model "variant" as opencode stores it: an options object that, when @@ -10,109 +11,48 @@ export interface CursorVariant { mode?: "agent" | "plan"; } -const REASONING_PARAM = /think|reason|effort/i; -const BOOLEAN_VALUES = new Set(["true", "false"]); - -function paramValues(param: NonNullable[number]): string[] { - return (param.values ?? []).map((v) => v.value); -} - -function isBooleanParam(values: string[]): boolean { - return values.length > 0 && values.every((v) => BOOLEAN_VALUES.has(v)); -} - /** - * Params opencode must send by DEFAULT for this model — i.e. when the user has - * NOT picked a variant. Non-reasoning boolean toggles (notably Cursor's `fast`) - * are pinned OFF here so the provider never silently inherits Cursor's - * server-side default, which is `fast: true` for several models (composer-*, - * gpt-*-codex). The user opts back IN via the matching picker variant. - * - * Seeded into each model's opencode `options.params` (see `toOpencodeModels` / - * `buildModelV2Map`); {@link resolveControls} merges it into the request. + * Curated flat variant map for opencode's NATIVE variant_cycle (the --pure / + * no-tui fallback). One entry per non-default axis value, varying ONE axis at a + * time from the model defaults — a short list, never the full cartesian product. */ -export function defaultModelParams(item: ModelListItem): Record { - const out: Record = {}; - for (const param of item.parameters ?? []) { - if (REASONING_PARAM.test(param.id)) continue; - if (isBooleanParam(paramValues(param))) out[param.id] = "false"; +export function buildModelVariants(item: ModelListItem): Record { + const axes = buildModelAxes(item); + const out: Record = {}; + const base: Record = {}; + for (const a of axes) base[a.id] = a.default; + + for (const a of axes) { + for (const value of a.values) { + if (value === a.default) continue; + if (a.kind === "toggle" && value === "false") continue; // off = baseline + const label = variantLabel(a.id, value); + out[label] = { params: { ...base, [a.id]: value } }; + } } return out; } -/** - * Derive opencode model variants from a Cursor model's parameters so the - * variant picker can expose thinking/reasoning levels plus the `fast` toggle. - * Each variant's object is exactly what {@link resolveControls} consumes. Plan - * mode is NOT a variant: opencode's plan agent (Tab) is mapped to Cursor's plan - * mode by the plugin's `chat.params` hook. - * - * Every variant for a fast-capable model carries an explicit `fast` value - * (reasoning variants pin it OFF via {@link defaultModelParams}; the `fast` - * variant turns it ON) so a selection never depends on Cursor's server-side - * default for an omitted param. - */ -export function buildModelVariants(item: ModelListItem): Record { - const out: Record = {}; - // Non-reasoning boolean defaults (e.g. { fast: "false" }), pinned into every - // reasoning variant so picking a reasoning level never re-enables fast. - const defaults = defaultModelParams(item); - - // Pre-pass: does any reasoning param expose a non-boolean effort enum (e.g. - // ["low","medium","high","xhigh","max"])? When it does, a coexisting boolean - // reasoning toggle (Cursor's `thinking=["false","true"]` on claude-* models) - // is redundant — selecting any effort level already enables reasoning — and - // surfacing it would add a stray `thinking` variant the standard opencode - // providers don't show. Suppress the boolean variant for parity. Order- - // independent: the enum may be declared before or after the boolean. - const hasEffortEnum = (item.parameters ?? []).some( - (p) => REASONING_PARAM.test(p.id) && !isBooleanParam(paramValues(p)) && paramValues(p).length > 0, - ); - - for (const param of item.parameters ?? []) { - const values = paramValues(param); - if (values.length === 0) continue; - const boolean = isBooleanParam(values); - - if (REASONING_PARAM.test(param.id)) { - if (boolean) { - // Boolean toggle (e.g. thinking=["false","true"]). Literal true/false - // variant names are meaningless in the picker — surface a single - // variant named after the param that switches it on. "Off" is the - // model's default (no variant selected). Skipped entirely when an - // effort enum coexists (see hasEffortEnum above). - if (!hasEffortEnum && values.includes("true")) { - out[param.id.toLowerCase()] = { params: { ...defaults, [param.id]: "true" } }; - } - continue; - } - - for (const value of values) { - // `none` means reasoning OFF — the model's default when no variant is - // selected. Surfacing it as a selectable variant is meaningless (you - // get it by picking nothing), so skip it. Standard providers - // (models.dev) include `none` in their effort values, but the - // no-variant-selected state already represents it. - if (value === "none") continue; - // Cursor labels the top reasoning tier "extra-high"; the opencode - // standard (models.dev) calls it "xhigh". Use the standard label for - // the variant key so the cycler is consistent across providers, but - // keep the Cursor wire-format value ("extra-high") in the params sent - // to the API. - const displayKey = value === "extra-high" ? "xhigh" : value; - const key = out[displayKey] === undefined ? displayKey : `${param.id}-${displayKey}`; - out[key] = { params: { ...defaults, [param.id]: value } }; - } - continue; - } +function variantLabel(axisId: string, value: string): string { + if (axisId === "thinking" && value === "true") return "thinking"; + if (axisId === "fast" && value === "true") return "fast"; + if (value === "extra-high") return "xhigh"; + return value; +} - // Non-reasoning boolean toggle (e.g. Cursor's `fast`). Default is OFF (see - // defaultModelParams); expose a single opt-in variant that turns it ON. - if (boolean && values.includes("true")) { - out[param.id.toLowerCase()] = { params: { ...defaults, [param.id]: "true" } }; +/** Baseline params to seed a model's default request (axis defaults + pinned). */ +export function defaultModelParams(item: ModelListItem): Record { + const out: Record = {}; + for (const a of buildModelAxes(item)) out[a.id] = a.default; + // Pinned single-value params (never axes) still belong in the baseline. + const counts = new Map>(); + for (const v of item.variants ?? []) + for (const p of v.params ?? []) (counts.get(p.id) ?? counts.set(p.id, new Set()).get(p.id)!).add(p.value); + for (const [id, set] of counts) { + if (set.size === 1) { + const only = [...set][0]; + if (only !== undefined) out[id] = only; } - // Non-reasoning enum params (e.g. `context`) remain unsupported in the picker. } - return out; } diff --git a/src/plugin/index.ts b/src/plugin/index.ts index 8536d6a..042f045 100644 --- a/src/plugin/index.ts +++ b/src/plugin/index.ts @@ -12,6 +12,7 @@ import { import { buildCursorTools } from "./cursor-tools.js"; import { warnIfStale } from "../version-check.js"; import { removeSystemRule } from "../provider/system-rule.js"; +import { filterStringParams, readSelection } from "../tui/state-file.js"; function apiKeyFromAuth(auth: Auth | undefined): string | undefined { return auth?.type === "api" ? auth.key : undefined; @@ -165,6 +166,22 @@ export const CursorPlugin: Plugin = async (input) => { output.options["mode"] = "plan"; } + // Merge the TUI-composed per-axis model state (persisted to the shared + // cursor-states.json by the tui plugin) into the request params. The + // provider's controls.ts turns output.options.params into the Cursor + // {id,params:[{id,value}]} wire shape — unchanged. The state file is only + // shallowly validated on read, so filter to string values here: Cursor + // params must be strings and a malformed file must never inject a + // non-string into the request. + const persisted = readSelection(input.sessionID); + if (persisted) { + const safe = filterStringParams(persisted); + const existingParams = + (output.options["params"] as Record | undefined) ?? + {}; + output.options["params"] = { ...existingParams, ...safe }; + } + // Dynamically re-forward MCP servers from opencode's *live* state so // mid-session enable/disable reaches the Cursor agent (the config hook // only snapshots the set once, at startup). `client.mcp.status()` is the diff --git a/src/tui/axis-state.ts b/src/tui/axis-state.ts new file mode 100644 index 0000000..9c3c8fd --- /dev/null +++ b/src/tui/axis-state.ts @@ -0,0 +1,81 @@ +import type { ModelAxis } from "../model-axes.js"; + +export type AxisSelection = Record; + +/** Toggle axes whose "off" value should not appear in the compact label. */ +const TOGGLE_OFF = new Set(["false"]); + +export function seedSelection(axes: ModelAxis[]): AxisSelection { + const sel: AxisSelection = {}; + for (const a of axes) sel[a.id] = a.default; + return sel; +} + +/** + * Rebuild a selection for `axes` from a previously-persisted selection: carry + * over each axis value that is still valid for THIS model's axes, fall back to + * the axis default otherwise, and DROP any persisted keys that are not axes of + * this model (e.g. left over from a different model). Pure; never mutates input. + */ +export function reconcileSelection( + axes: ModelAxis[], + persisted: AxisSelection | undefined, +): AxisSelection { + const sel = seedSelection(axes); + if (!persisted) return sel; + for (const a of axes) { + const v = persisted[a.id]; + if (v !== undefined && a.values.includes(v)) sel[a.id] = v; + } + return sel; +} + +/** Map a raw Cursor value to its display token (wire `extra-high` shows as `xhigh`). */ +export function axisValueLabel(value: string): string { + return value === "extra-high" ? "xhigh" : value; +} + +/** + * Advance `axisId` by `dir` (+1 next, -1 prev) with wraparound. Returns a NEW + * selection object; never mutates the input. Unknown axis id -> input echoed. + */ +export function cycleAxis( + axes: ModelAxis[], + sel: AxisSelection, + axisId: string, + dir: 1 | -1, +): AxisSelection { + const axis = axes.find((a) => a.id === axisId); + if (!axis) return sel; + const cur = sel[axisId] ?? axis.default; + const i = axis.values.indexOf(cur); + const base = i < 0 ? 0 : i; + const n = axis.values.length; + const nextIdx = (base + dir + n) % n; + const nextVal = axis.values[nextIdx]; + if (nextVal === undefined) return sel; // noUncheckedIndexedAccess guard + return { ...sel, [axisId]: nextVal }; +} + +/** + * Compact one-line label, e.g. "high · think · fast · 1m". Toggle axes only + * appear when ON; their label word is the axis label lowercased (thinking -> + * "think", fast -> "fast"). Non-toggle axes always show their value. + */ +export function formatSelection(axes: ModelAxis[], sel: AxisSelection): string { + const parts: string[] = []; + for (const a of axes) { + const val = sel[a.id] ?? a.default; + if (a.kind === "toggle") { + if (!TOGGLE_OFF.has(val)) parts.push(toggleWord(a.id, a.label)); + } else { + parts.push(axisValueLabel(val)); + } + } + return parts.join(" · "); +} + +function toggleWord(id: string, label: string): string { + if (id === "thinking") return "think"; + return label.toLowerCase(); +} diff --git a/src/tui/index.tsx b/src/tui/index.tsx new file mode 100644 index 0000000..f9404d1 --- /dev/null +++ b/src/tui/index.tsx @@ -0,0 +1,204 @@ +/** @jsxImportSource @opentui/solid */ +// State bridge: this plugin persists the composed axis selection to a shared +// JSON file (src/tui/state-file.ts). The server half (src/plugin/index.ts +// chat.params) reads the SAME file and merges the params into the Cursor +// request. There is no opencode client API for per-session model options — +// the filesystem is the only cross-half channel (verified against the SDK). +import type { TuiPlugin } from "@opencode-ai/plugin/tui"; +import type { ModelListItem } from "@cursor/sdk"; +import { buildModelAxes, isValidCombo, snapCombo, type ModelAxis } from "../model-axes.js"; +import { cachedCatalog } from "../model-discovery.js"; +import { + axisValueLabel, + cycleAxis, + reconcileSelection, + seedSelection, + type AxisSelection, +} from "./axis-state.js"; +import { readSelection, writeSelection } from "./state-file.js"; +import { createSignal } from "solid-js"; +import { StatusWidget } from "./status-widget.js"; + +const PLUGIN_ID = "cursor.states"; + +// Default hotkeys per axis. shift+ = previous. All overridable in tui.json. +const AXIS_KEYS: Record = { + effort: "ctrl+e", + reasoning: "ctrl+e", // effort and reasoning are mutually exclusive per model + thinking: "ctrl+t", + fast: "ctrl+f", + context: "ctrl+k", +}; + +const tui: TuiPlugin = async (api) => { + // opencode's provider id for the Cursor backend (see src/provider/index.ts). + const PROVIDER_ID = "cursor"; + // Per-model working state, rebuilt whenever the active Cursor model changes. + let axes: ModelAxis[] = []; + let selection: AxisSelection = {}; + // Identity of the (session, model) pair the current axes/selection were built + // for. refreshForModel() no-ops until this key changes, so the many + // `message.updated` fires per turn don't reseed and desync the selection. + let lastKey: string | undefined; + // Disposes the currently-registered keymap layer. Keymap auto-cleanup only + // runs on plugin deactivation, not on our per-model re-registration, so we + // dispose the old layer ourselves before creating a new one. + let disposeLayer: (() => void) | undefined; + + // Solid signals mirroring `axes`/`selection` so the status widget re-renders + // reactively as the model changes or the user cycles an axis. The widget + // reads these getters; we push updates at the end of refreshForModel() and + // after each cycle. New objects on write so Solid sees a fresh reference. + const [axesSig, setAxesSig] = createSignal([]); + const [selSig, setSelSig] = createSignal({}); + + // The current session id, or undefined on the home route / non-session routes. + // `TuiRouteCurrent` also has a permissive `{ name: string; params?: ... }` arm + // that survives the `name === "session"` narrow, so `params`/`sessionID` are + // read defensively rather than assumed present. + function activeSessionID(): string | undefined { + const route = api.route.current; + if (route.name !== "session") return undefined; + const params: Record | undefined = route.params; + const sessionID = params?.["sessionID"]; + return typeof sessionID === "string" ? sessionID : undefined; + } + + // The active Cursor model id = the `modelID` of the most recent ASSISTANT + // message in the current session. opencode records provider/model per + // assistant message; there is no model field on `Session` and no + // model-selection event, so the transcript is the source of truth. `Message` + // is a union — only the `role === "assistant"` arm carries top-level + // `modelID`/`providerID` (a user message nests them under `model`), hence the + // narrow. The `m &&` guard satisfies `noUncheckedIndexedAccess`. + function activeCursorModelId(): string | undefined { + const sessionID = activeSessionID(); + if (!sessionID) return undefined; + const messages = api.state.session.messages(sessionID); + for (let i = messages.length - 1; i >= 0; i--) { + const m = messages[i]; + if (m && m.role === "assistant" && m.providerID === PROVIDER_ID) { + return m.modelID; + } + } + return undefined; + } + + // Resolve the active Cursor ModelListItem (with variants[]) from the cached + // catalog. Returns undefined until a model has been used in the session. + function currentCursorModel(): ModelListItem | undefined { + const id = activeCursorModelId(); + if (!id) return undefined; + return cachedCatalog().find((m) => m.id === id); + } + + // Rebuild axes + reload the selection ONLY when the active (session, model) + // pair changes. `message.updated` fires many times per streaming turn; the + // key gate makes those repeat fires no-ops so the persisted selection is not + // reseeded to defaults mid-turn. On a real change we LOAD the persisted + // selection for the session and reconcile it to the current model's axes + // (dropping a previous model's stale params), then persist so the file, the + // status widget, and the server-side wire all agree. + function refreshForModel() { + const sessionID = activeSessionID(); + const model = currentCursorModel(); + const modelId = model?.id; + const key = `${sessionID ?? ""}|${modelId ?? ""}`; + if (key === lastKey) return; + lastKey = key; + + axes = model ? buildModelAxes(model) : []; + const persisted = sessionID ? readSelection(sessionID) : undefined; + selection = reconcileSelection(axes, persisted); + // Asymmetric-combo safety: a reconciled combo that isn't offered by this + // model falls back to the always-valid per-axis defaults. + if (model && !isValidCombo(model, selection)) selection = seedSelection(axes); + // Re-register the hotkey layer for the (possibly new) axis set. + registerAxisLayer(); + // Push the fresh axis set + selection into the reactive signals so the + // status widget re-renders for the new model. + setAxesSig(axes); + setSelSig({ ...selection }); + // Persist the reconciled selection so file/widget/wire agree. No-op off a + // session route. + // Only persist once a model is actually known; writing an empty selection + // for an unresolved model would clobber the session's saved state on resume. + if (model) applySelection(); + } + + // Persist the composed selection for the active session. The server half + // (src/plugin/index.ts chat.params) reads this file and merges the params + // into the Cursor call. No-op off a session route (nothing to key on). + function applySelection() { + const sessionID = activeSessionID(); + if (!sessionID) return; + writeSelection(sessionID, selection); + } + + // Register a keymap layer with next/prev cycle commands for each axis of the + // current model, plus default bindings from AXIS_KEYS (shift+ = prev). + // Re-runs on every model change; disposes the prior layer first. + function registerAxisLayer() { + disposeLayer?.(); + disposeLayer = undefined; + if (axes.length === 0) return; + + const commands = []; + const bindings = []; + for (const axis of axes) { + const key = AXIS_KEYS[axis.id]; + for (const dir of [1, -1] as const) { + const suffix = dir === 1 ? "next" : "prev"; + const name = `cursor.axis.${axis.id}.${suffix}`; + commands.push({ + name, + title: `Cursor: ${axis.label} ${dir === 1 ? "next" : "previous"}`, + category: "Cursor", + namespace: "palette", + run() { + selection = cycleAxis(axes, selection, axis.id, dir); + const model = currentCursorModel(); + if (model) selection = snapCombo(model, selection, axis.id); + // Mirror the new selection into the signal so the widget updates. + setSelSig({ ...selection }); + applySelection(); + api.ui.toast({ + variant: "info", + message: `${axis.label}: ${axisValueLabel(selection[axis.id] ?? "")}`, + }); + }, + }); + if (key) { + const binding = dir === 1 ? key : `shift+${key}`; + bindings.push({ key: binding, cmd: name, desc: `Cursor ${axis.label} ${suffix}` }); + } + } + } + disposeLayer = api.keymap.registerLayer({ mode: "base", commands, bindings }); + } + + refreshForModel(); + + // Register the status widget into the host's right-of-prompt slot. Renderers + // nest under a required `slots` key, keyed by host slot name; each value is a + // (ctx, props) => JSX.Element renderer (params unused here). No `id` — the + // host omits it. The widget reads the signals, so it re-renders on cycle. + api.slots.register({ + slots: { + session_prompt_right: () => ( + + ), + }, + }); + + // Re-derive axes whenever a message updates: `message.updated` fires when an + // assistant message is added/updated, i.e. exactly when the model in use may + // have changed. It also fires repeatedly during streaming, but the + // (sessionID, modelId) gate in refreshForModel() makes those repeat fires + // no-ops until the model or session actually changes. Dispose on unload. + const off = api.event.on("message.updated", () => refreshForModel()); + api.lifecycle.onDispose(off); +}; + +const plugin = { id: PLUGIN_ID, tui }; +export default plugin; diff --git a/src/tui/state-file.ts b/src/tui/state-file.ts new file mode 100644 index 0000000..29b10d4 --- /dev/null +++ b/src/tui/state-file.ts @@ -0,0 +1,71 @@ +import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs"; +import { homedir, tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import type { AxisSelection } from "./axis-state.js"; + +/** Same base dir as model-cache.ts so both plugin halves agree. */ +function baseDir(): string { + const xdg = process.env.XDG_CACHE_HOME?.trim(); + const home = homedir(); + const root = xdg ? xdg : home ? join(home, ".cache") : tmpdir(); + return join(root, "opencode-cursor"); +} + +/** Shared bridge file path. Env override lets tests isolate it. */ +export function stateFilePath(): string { + const override = process.env.OPENCODE_CURSOR_STATE_FILE; + return override && override.length > 0 ? override : join(baseDir(), "cursor-states.json"); +} + +/** All persisted per-session selections. Corrupt/missing file => {}. */ +export function readStates(): Record { + try { + const raw = readFileSync(stateFilePath(), "utf8"); + const parsed = JSON.parse(raw) as unknown; + if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { + return parsed as Record; + } + return {}; + } catch { + return {}; + } +} + +export function readSelection(sessionID: string): AxisSelection | undefined { + const states = readStates(); + return states[sessionID]; +} + +/** + * Keep only string-valued entries. `readStates` shallowly validates the file + * (confirms it's an object) but does NOT check that each session's values are + * strings, so a hand-edited or corrupt file could carry non-string values. + * Cursor params MUST be strings, so the server half filters through this before + * merging the persisted selection into the request. + */ +export function filterStringParams( + input: Record, +): Record { + const safe: Record = {}; + for (const [k, v] of Object.entries(input)) { + if (typeof v === "string") safe[k] = v; + } + return safe; +} + +/** Persist one session's selection, preserving the others. Best-effort. */ +export function writeSelection(sessionID: string, sel: AxisSelection): void { + const states = readStates(); + states[sessionID] = { ...sel }; + const file = stateFilePath(); + try { + mkdirSync(dirname(file), { recursive: true }); + // Atomic write: a concurrent server-side read must never observe a torn + // file, so write to a temp path and rename (atomic on the same fs). + const tmp = `${file}.${process.pid}.tmp`; + writeFileSync(tmp, JSON.stringify(states), "utf8"); + renameSync(tmp, file); + } catch { + // Non-fatal: the widget still reflects the selection in-memory. + } +} diff --git a/src/tui/status-widget.tsx b/src/tui/status-widget.tsx new file mode 100644 index 0000000..7b0206c --- /dev/null +++ b/src/tui/status-widget.tsx @@ -0,0 +1,13 @@ +/** @jsxImportSource @opentui/solid */ +import type { ModelAxis } from "../model-axes.js"; +import type { AxisSelection } from "./axis-state.js"; +import { formatSelection } from "./axis-state.js"; + +export function StatusWidget(props: { axes: () => ModelAxis[]; selection: () => AxisSelection }) { + const label = () => { + const axes = props.axes(); + if (axes.length === 0) return ""; + return formatSelection(axes, props.selection()); + }; + return {label()}; +} diff --git a/test/axis-state.test.ts b/test/axis-state.test.ts new file mode 100644 index 0000000..3002bef --- /dev/null +++ b/test/axis-state.test.ts @@ -0,0 +1,94 @@ +import { describe, expect, it } from "vitest"; +import type { ModelAxis } from "../src/model-axes.js"; +import type { AxisSelection } from "../src/tui/axis-state.js"; +import { + seedSelection, + cycleAxis, + formatSelection, + reconcileSelection, +} from "../src/tui/axis-state.js"; + +const AXES: ModelAxis[] = [ + { id: "effort", label: "Effort", values: ["low", "medium", "high", "xhigh", "max"], default: "high", kind: "cycle" }, + { id: "fast", label: "Fast", values: ["false", "true"], default: "false", kind: "toggle" }, +]; + +const REASONING_AXES: ModelAxis[] = [ + { id: "reasoning", label: "Reasoning", values: ["none", "low", "medium", "high", "extra-high"], default: "medium", kind: "cycle" }, +]; + +describe("axis-state", () => { + it("seeds from each axis default", () => { + expect(seedSelection(AXES)).toEqual({ effort: "high", fast: "false" }); + }); + + it("cycles forward and wraps", () => { + let sel: AxisSelection = { effort: "xhigh", fast: "false" }; + sel = cycleAxis(AXES, sel, "effort", 1); + expect(sel.effort).toBe("max"); + sel = cycleAxis(AXES, sel, "effort", 1); + expect(sel.effort).toBe("low"); // wrapped + }); + + it("cycles backward and wraps", () => { + const sel = cycleAxis(AXES, { effort: "low", fast: "false" }, "effort", -1); + expect(sel.effort).toBe("max"); + }); + + it("does not mutate the input selection", () => { + const sel = { effort: "high", fast: "false" }; + cycleAxis(AXES, sel, "effort", 1); + expect(sel.effort).toBe("high"); + }); + + it("ignores an unknown axis id", () => { + const sel = { effort: "high", fast: "false" }; + expect(cycleAxis(AXES, sel, "nope", 1)).toEqual(sel); + }); + + it("formats a compact label line", () => { + expect(formatSelection(AXES, { effort: "high", fast: "true" })).toBe("high · fast"); + }); + + it("omits the OFF value of toggles from the label", () => { + // fast=false is the off state -> not shown; effort always shown. + expect(formatSelection(AXES, { effort: "low", fast: "false" })).toBe("low"); + }); + + it("renders the wire value extra-high as its display token xhigh", () => { + expect(formatSelection(REASONING_AXES, { reasoning: "extra-high" })).toBe("xhigh"); + }); +}); + +describe("reconcileSelection", () => { + it("falls back to axis defaults when nothing is persisted", () => { + expect(reconcileSelection(AXES, undefined)).toEqual({ effort: "high", fast: "false" }); + }); + + it("carries over valid persisted values", () => { + expect(reconcileSelection(AXES, { effort: "low", fast: "true" })).toEqual({ + effort: "low", + fast: "true", + }); + }); + + it("replaces a persisted value not offered by the axis with the axis default", () => { + expect(reconcileSelection(AXES, { effort: "bogus", fast: "true" })).toEqual({ + effort: "high", + fast: "true", + }); + }); + + it("drops persisted keys that are not axes of this model", () => { + expect(reconcileSelection(AXES, { effort: "low", reasoning: "high" })).toEqual({ + effort: "low", + fast: "false", + }); + }); + + it("does not mutate the persisted input", () => { + const persisted: AxisSelection = { effort: "low", fast: "true" }; + reconcileSelection(AXES, persisted); + expect(persisted).toEqual({ effort: "low", fast: "true" }); + }); +}); diff --git a/test/fixtures/cursor-catalog.json b/test/fixtures/cursor-catalog.json new file mode 100644 index 0000000..bfe78b4 --- /dev/null +++ b/test/fixtures/cursor-catalog.json @@ -0,0 +1,5712 @@ +[ + { + "id": "default", + "displayName": "Auto", + "aliases": [ + "auto" + ], + "variants": [ + { + "params": [], + "displayName": "Auto", + "isDefault": true + } + ] + }, + { + "id": "grok-4.5", + "displayName": "Cursor Grok 4.5", + "parameters": [ + { + "id": "effort", + "displayName": "Effort", + "values": [ + { + "value": "low", + "displayName": "Low" + }, + { + "value": "medium", + "displayName": "Medium" + }, + { + "value": "high", + "displayName": "High" + } + ] + }, + { + "id": "fast", + "displayName": "Fast", + "values": [ + { + "value": "false" + }, + { + "value": "true", + "displayName": "Fast​" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Cursor Grok 4.5" + }, + { + "params": [ + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Cursor Grok 4.5" + }, + { + "params": [ + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Cursor Grok 4.5" + }, + { + "params": [ + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Cursor Grok 4.5" + }, + { + "params": [ + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Cursor Grok 4.5" + }, + { + "params": [ + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Cursor Grok 4.5", + "isDefault": true + } + ] + }, + { + "id": "composer-2.5", + "displayName": "Composer 2.5", + "aliases": [ + "composer-latest", + "composer", + "composer-2-5" + ], + "parameters": [ + { + "id": "fast", + "displayName": "Fast", + "values": [ + { + "value": "false" + }, + { + "value": "true", + "displayName": "Fast" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Composer 2.5", + "isDefault": true + }, + { + "params": [ + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Composer 2.5" + } + ] + }, + { + "id": "claude-opus-4-8", + "displayName": "Opus 4.8", + "aliases": [ + "opus-latest", + "opus", + "opus-4.8", + "opus-4-8" + ], + "parameters": [ + { + "id": "thinking", + "displayName": "Thinking", + "values": [ + { + "value": "false" + }, + { + "value": "true" + } + ] + }, + { + "id": "context", + "displayName": "Context", + "values": [ + { + "value": "300k", + "displayName": "300K" + }, + { + "value": "1m", + "displayName": "1M" + } + ] + }, + { + "id": "effort", + "displayName": "Effort", + "values": [ + { + "value": "low", + "displayName": "Low" + }, + { + "value": "medium", + "displayName": "Medium" + }, + { + "value": "high", + "displayName": "High" + }, + { + "value": "xhigh", + "displayName": "Extra High" + }, + { + "value": "max", + "displayName": "Max" + } + ] + }, + { + "id": "fast", + "displayName": "Fast", + "values": [ + { + "value": "false" + }, + { + "value": "true", + "displayName": "Fast" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "xhigh" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "xhigh" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "max" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "max" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "xhigh" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "xhigh" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "max" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "max" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "xhigh" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "xhigh" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "max" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "max" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8", + "isDefault": true + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "xhigh" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "xhigh" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "max" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.8" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "max" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.8" + } + ] + }, + { + "id": "gpt-5.5", + "displayName": "GPT-5.5", + "aliases": [ + "gpt-5-5" + ], + "parameters": [ + { + "id": "context", + "displayName": "Context", + "values": [ + { + "value": "272k", + "displayName": "272K" + }, + { + "value": "1m", + "displayName": "1M" + } + ] + }, + { + "id": "reasoning", + "displayName": "Reasoning", + "values": [ + { + "value": "none", + "displayName": "None" + }, + { + "value": "low", + "displayName": "Low" + }, + { + "value": "medium", + "displayName": "Medium" + }, + { + "value": "high", + "displayName": "High" + }, + { + "value": "extra-high", + "displayName": "Extra High" + } + ] + }, + { + "id": "fast", + "displayName": "Fast", + "values": [ + { + "value": "false" + }, + { + "value": "true", + "displayName": "Fast" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "none" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.5" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "none" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "GPT-5.5" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "low" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.5" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "low" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "GPT-5.5" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "medium" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.5" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "medium" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "GPT-5.5" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.5" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "GPT-5.5" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "extra-high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.5" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "extra-high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "GPT-5.5" + }, + { + "params": [ + { + "id": "context", + "value": "1m" + }, + { + "id": "reasoning", + "value": "none" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.5" + }, + { + "params": [ + { + "id": "context", + "value": "1m" + }, + { + "id": "reasoning", + "value": "low" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.5" + }, + { + "params": [ + { + "id": "context", + "value": "1m" + }, + { + "id": "reasoning", + "value": "medium" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.5", + "isDefault": true + }, + { + "params": [ + { + "id": "context", + "value": "1m" + }, + { + "id": "reasoning", + "value": "high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.5" + }, + { + "params": [ + { + "id": "context", + "value": "1m" + }, + { + "id": "reasoning", + "value": "extra-high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.5" + } + ] + }, + { + "id": "claude-fable-5", + "displayName": "Fable 5", + "aliases": [ + "fable", + "fable-5", + "fable-5" + ], + "parameters": [ + { + "id": "thinking", + "displayName": "Thinking", + "values": [ + { + "value": "false" + }, + { + "value": "true" + } + ] + }, + { + "id": "context", + "displayName": "Context", + "values": [ + { + "value": "300k", + "displayName": "300K" + }, + { + "value": "1m", + "displayName": "1M" + } + ] + }, + { + "id": "effort", + "displayName": "Effort", + "values": [ + { + "value": "low", + "displayName": "Low" + }, + { + "value": "medium", + "displayName": "Medium" + }, + { + "value": "high", + "displayName": "High" + }, + { + "value": "xhigh", + "displayName": "Extra High" + }, + { + "value": "max", + "displayName": "Max" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "low" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "medium" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "high" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "xhigh" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "max" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "low" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "medium" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "high" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "xhigh" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "max" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "low" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "medium" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "high" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "xhigh" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "max" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "low" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "medium" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "high" + } + ], + "displayName": "Fable 5", + "isDefault": true + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "xhigh" + } + ], + "displayName": "Fable 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "max" + } + ], + "displayName": "Fable 5" + } + ] + }, + { + "id": "claude-sonnet-5", + "displayName": "Sonnet 5", + "aliases": [ + "sonnet-latest", + "sonnet-5" + ], + "parameters": [ + { + "id": "thinking", + "displayName": "Thinking", + "values": [ + { + "value": "false" + }, + { + "value": "true" + } + ] + }, + { + "id": "context", + "displayName": "Context", + "values": [ + { + "value": "300k", + "displayName": "300K" + }, + { + "value": "1m", + "displayName": "1M" + } + ] + }, + { + "id": "effort", + "displayName": "Effort", + "values": [ + { + "value": "low", + "displayName": "Low" + }, + { + "value": "medium", + "displayName": "Medium" + }, + { + "value": "high", + "displayName": "High" + }, + { + "value": "xhigh", + "displayName": "Extra High" + }, + { + "value": "max", + "displayName": "Max" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "low" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "medium" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "high" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "xhigh" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "max" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "low" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "medium" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "high" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "xhigh" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "max" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "low" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "medium" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "high" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "xhigh" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "max" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "low" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "medium" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "high" + } + ], + "displayName": "Sonnet 5", + "isDefault": true + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "xhigh" + } + ], + "displayName": "Sonnet 5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "max" + } + ], + "displayName": "Sonnet 5" + } + ] + }, + { + "id": "claude-sonnet-4-6", + "displayName": "Sonnet 4.6", + "aliases": [ + "sonnet-latest", + "sonnet", + "sonnet-4.6", + "sonnet-4-6" + ], + "parameters": [ + { + "id": "thinking", + "displayName": "Thinking", + "values": [ + { + "value": "false" + }, + { + "value": "true" + } + ] + }, + { + "id": "context", + "displayName": "Context", + "values": [ + { + "value": "200k", + "displayName": "200K" + }, + { + "value": "1m", + "displayName": "1M" + } + ] + }, + { + "id": "effort", + "displayName": "Effort", + "values": [ + { + "value": "low", + "displayName": "Low" + }, + { + "value": "medium", + "displayName": "Medium" + }, + { + "value": "high", + "displayName": "High" + }, + { + "value": "max", + "displayName": "Max" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "200k" + }, + { + "id": "effort", + "value": "low" + } + ], + "displayName": "Sonnet 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "200k" + }, + { + "id": "effort", + "value": "medium" + } + ], + "displayName": "Sonnet 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "200k" + }, + { + "id": "effort", + "value": "high" + } + ], + "displayName": "Sonnet 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "200k" + }, + { + "id": "effort", + "value": "max" + } + ], + "displayName": "Sonnet 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "low" + } + ], + "displayName": "Sonnet 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "medium" + } + ], + "displayName": "Sonnet 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "high" + } + ], + "displayName": "Sonnet 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "max" + } + ], + "displayName": "Sonnet 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "200k" + }, + { + "id": "effort", + "value": "low" + } + ], + "displayName": "Sonnet 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "200k" + }, + { + "id": "effort", + "value": "medium" + } + ], + "displayName": "Sonnet 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "200k" + }, + { + "id": "effort", + "value": "high" + } + ], + "displayName": "Sonnet 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "200k" + }, + { + "id": "effort", + "value": "max" + } + ], + "displayName": "Sonnet 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "low" + } + ], + "displayName": "Sonnet 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "medium" + } + ], + "displayName": "Sonnet 4.6", + "isDefault": true + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "high" + } + ], + "displayName": "Sonnet 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "max" + } + ], + "displayName": "Sonnet 4.6" + } + ] + }, + { + "id": "composer-2", + "displayName": "Composer 2", + "parameters": [ + { + "id": "fast", + "displayName": "Fast", + "values": [ + { + "value": "false" + }, + { + "value": "true", + "displayName": "Fast" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Composer 2", + "isDefault": true + }, + { + "params": [ + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Composer 2" + } + ] + }, + { + "id": "gpt-5.3-codex", + "displayName": "Codex 5.3", + "aliases": [ + "codex-latest", + "codex", + "codex-5.3" + ], + "parameters": [ + { + "id": "reasoning", + "displayName": "Reasoning", + "values": [ + { + "value": "low", + "displayName": "Low" + }, + { + "value": "medium", + "displayName": "Medium" + }, + { + "value": "high", + "displayName": "High" + }, + { + "value": "extra-high", + "displayName": "Extra High" + } + ] + }, + { + "id": "fast", + "displayName": "Fast", + "values": [ + { + "value": "false" + }, + { + "value": "true", + "displayName": "Fast" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "reasoning", + "value": "low" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Codex 5.3" + }, + { + "params": [ + { + "id": "reasoning", + "value": "low" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Codex 5.3" + }, + { + "params": [ + { + "id": "reasoning", + "value": "medium" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Codex 5.3" + }, + { + "params": [ + { + "id": "reasoning", + "value": "medium" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Codex 5.3" + }, + { + "params": [ + { + "id": "reasoning", + "value": "high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Codex 5.3" + }, + { + "params": [ + { + "id": "reasoning", + "value": "high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Codex 5.3", + "isDefault": true + }, + { + "params": [ + { + "id": "reasoning", + "value": "extra-high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Codex 5.3" + }, + { + "params": [ + { + "id": "reasoning", + "value": "extra-high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Codex 5.3" + } + ] + }, + { + "id": "claude-opus-4-7", + "displayName": "Opus 4.7", + "aliases": [ + "opus-4.7", + "opus-4-7" + ], + "parameters": [ + { + "id": "thinking", + "displayName": "Thinking", + "values": [ + { + "value": "false" + }, + { + "value": "true" + } + ] + }, + { + "id": "context", + "displayName": "Context", + "values": [ + { + "value": "300k", + "displayName": "300K" + }, + { + "value": "1m", + "displayName": "1M" + } + ] + }, + { + "id": "effort", + "displayName": "Effort", + "values": [ + { + "value": "low", + "displayName": "Low" + }, + { + "value": "medium", + "displayName": "Medium" + }, + { + "value": "high", + "displayName": "High" + }, + { + "value": "xhigh", + "displayName": "Extra High" + }, + { + "value": "max", + "displayName": "Max" + } + ] + }, + { + "id": "fast", + "displayName": "Fast", + "values": [ + { + "value": "false" + }, + { + "value": "true", + "displayName": "Fast" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "xhigh" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "xhigh" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "max" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "max" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "xhigh" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "xhigh" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "max" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "max" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "xhigh" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "xhigh" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "max" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "300k" + }, + { + "id": "effort", + "value": "max" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "low" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "medium" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "xhigh" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7", + "isDefault": true + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "xhigh" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "max" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Opus 4.7" + }, + { + "params": [ + { + "id": "cyber", + "value": "false" + }, + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "max" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Opus 4.7" + } + ] + }, + { + "id": "gpt-5.4", + "displayName": "GPT-5.4", + "aliases": [ + "gpt" + ], + "parameters": [ + { + "id": "context", + "displayName": "Context", + "values": [ + { + "value": "272k", + "displayName": "272K" + }, + { + "value": "1m", + "displayName": "1M" + } + ] + }, + { + "id": "reasoning", + "displayName": "Reasoning", + "values": [ + { + "value": "none", + "displayName": "None" + }, + { + "value": "low", + "displayName": "Low" + }, + { + "value": "medium", + "displayName": "Medium" + }, + { + "value": "high", + "displayName": "High" + }, + { + "value": "extra-high", + "displayName": "Extra High" + } + ] + }, + { + "id": "fast", + "displayName": "Fast", + "values": [ + { + "value": "false" + }, + { + "value": "true", + "displayName": "Fast" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "none" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.4" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "none" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "GPT-5.4" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "low" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.4" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "low" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "GPT-5.4" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "medium" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.4" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "medium" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "GPT-5.4" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.4" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "GPT-5.4" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "extra-high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.4" + }, + { + "params": [ + { + "id": "context", + "value": "272k" + }, + { + "id": "reasoning", + "value": "extra-high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "GPT-5.4" + }, + { + "params": [ + { + "id": "context", + "value": "1m" + }, + { + "id": "reasoning", + "value": "none" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.4" + }, + { + "params": [ + { + "id": "context", + "value": "1m" + }, + { + "id": "reasoning", + "value": "low" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.4" + }, + { + "params": [ + { + "id": "context", + "value": "1m" + }, + { + "id": "reasoning", + "value": "medium" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.4", + "isDefault": true + }, + { + "params": [ + { + "id": "context", + "value": "1m" + }, + { + "id": "reasoning", + "value": "high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.4" + }, + { + "params": [ + { + "id": "context", + "value": "1m" + }, + { + "id": "reasoning", + "value": "extra-high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.4" + } + ] + }, + { + "id": "claude-opus-4-6", + "displayName": "Opus 4.6", + "aliases": [ + "opus", + "opus-4.6", + "opus-4-6" + ], + "parameters": [ + { + "id": "thinking", + "displayName": "Thinking", + "values": [ + { + "value": "false" + }, + { + "value": "true" + } + ] + }, + { + "id": "context", + "displayName": "Context", + "values": [ + { + "value": "200k", + "displayName": "200K" + }, + { + "value": "1m", + "displayName": "1M" + } + ] + }, + { + "id": "effort", + "displayName": "Effort", + "values": [ + { + "value": "low", + "displayName": "Low" + }, + { + "value": "medium", + "displayName": "Medium" + }, + { + "value": "high", + "displayName": "High" + }, + { + "value": "max", + "displayName": "Max" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "200k" + }, + { + "id": "effort", + "value": "low" + } + ], + "displayName": "Opus 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "200k" + }, + { + "id": "effort", + "value": "medium" + } + ], + "displayName": "Opus 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "200k" + }, + { + "id": "effort", + "value": "high" + } + ], + "displayName": "Opus 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "200k" + }, + { + "id": "effort", + "value": "max" + } + ], + "displayName": "Opus 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "low" + } + ], + "displayName": "Opus 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "medium" + } + ], + "displayName": "Opus 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "high" + } + ], + "displayName": "Opus 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "max" + } + ], + "displayName": "Opus 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "200k" + }, + { + "id": "effort", + "value": "low" + } + ], + "displayName": "Opus 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "200k" + }, + { + "id": "effort", + "value": "medium" + } + ], + "displayName": "Opus 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "200k" + }, + { + "id": "effort", + "value": "high" + } + ], + "displayName": "Opus 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "200k" + }, + { + "id": "effort", + "value": "max" + } + ], + "displayName": "Opus 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "low" + } + ], + "displayName": "Opus 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "medium" + } + ], + "displayName": "Opus 4.6" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "high" + } + ], + "displayName": "Opus 4.6", + "isDefault": true + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "1m" + }, + { + "id": "effort", + "value": "max" + } + ], + "displayName": "Opus 4.6" + } + ] + }, + { + "id": "claude-opus-4-5", + "displayName": "Opus 4.5", + "aliases": [ + "opus", + "opus-4.5", + "opus-4-5" + ], + "parameters": [ + { + "id": "thinking", + "displayName": "Thinking", + "values": [ + { + "value": "false" + }, + { + "value": "true" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "thinking", + "value": "false" + } + ], + "displayName": "Opus 4.5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + } + ], + "displayName": "Opus 4.5", + "isDefault": true + } + ] + }, + { + "id": "gpt-5.2", + "displayName": "GPT-5.2", + "aliases": [ + "gpt" + ], + "parameters": [ + { + "id": "reasoning", + "displayName": "Reasoning", + "values": [ + { + "value": "low", + "displayName": "Low" + }, + { + "value": "medium", + "displayName": "Medium" + }, + { + "value": "high", + "displayName": "High" + }, + { + "value": "extra-high", + "displayName": "Extra High" + } + ] + }, + { + "id": "fast", + "displayName": "Fast", + "values": [ + { + "value": "false" + }, + { + "value": "true", + "displayName": "Fast" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "reasoning", + "value": "low" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.2" + }, + { + "params": [ + { + "id": "reasoning", + "value": "low" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "GPT-5.2" + }, + { + "params": [ + { + "id": "reasoning", + "value": "medium" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.2" + }, + { + "params": [ + { + "id": "reasoning", + "value": "medium" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "GPT-5.2" + }, + { + "params": [ + { + "id": "reasoning", + "value": "high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.2" + }, + { + "params": [ + { + "id": "reasoning", + "value": "high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "GPT-5.2", + "isDefault": true + }, + { + "params": [ + { + "id": "reasoning", + "value": "extra-high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "GPT-5.2" + }, + { + "params": [ + { + "id": "reasoning", + "value": "extra-high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "GPT-5.2" + } + ] + }, + { + "id": "gemini-3.1-pro", + "displayName": "Gemini 3.1 Pro", + "aliases": [ + "gemini-latest", + "gemini-pro-latest", + "gemini", + "gemini-pro" + ], + "variants": [ + { + "params": [], + "displayName": "Gemini 3.1 Pro", + "isDefault": true + } + ] + }, + { + "id": "gpt-5.4-mini", + "displayName": "GPT-5.4 Mini", + "aliases": [ + "gpt-mini-latest", + "gpt-mini" + ], + "parameters": [ + { + "id": "reasoning", + "displayName": "Reasoning", + "values": [ + { + "value": "none", + "displayName": "None" + }, + { + "value": "low", + "displayName": "Low" + }, + { + "value": "medium", + "displayName": "Medium" + }, + { + "value": "high", + "displayName": "High" + }, + { + "value": "xhigh", + "displayName": "Extra High" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "reasoning", + "value": "none" + } + ], + "displayName": "GPT-5.4 Mini" + }, + { + "params": [ + { + "id": "reasoning", + "value": "low" + } + ], + "displayName": "GPT-5.4 Mini" + }, + { + "params": [ + { + "id": "reasoning", + "value": "medium" + } + ], + "displayName": "GPT-5.4 Mini", + "isDefault": true + }, + { + "params": [ + { + "id": "reasoning", + "value": "high" + } + ], + "displayName": "GPT-5.4 Mini" + }, + { + "params": [ + { + "id": "reasoning", + "value": "xhigh" + } + ], + "displayName": "GPT-5.4 Mini" + } + ] + }, + { + "id": "gpt-5.4-nano", + "displayName": "GPT-5.4 Nano", + "aliases": [ + "gpt-nano-latest", + "gpt-nano" + ], + "parameters": [ + { + "id": "reasoning", + "displayName": "Reasoning", + "values": [ + { + "value": "none", + "displayName": "None" + }, + { + "value": "low", + "displayName": "Low" + }, + { + "value": "medium", + "displayName": "Medium" + }, + { + "value": "high", + "displayName": "High" + }, + { + "value": "xhigh", + "displayName": "Extra High" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "reasoning", + "value": "none" + } + ], + "displayName": "GPT-5.4 Nano" + }, + { + "params": [ + { + "id": "reasoning", + "value": "low" + } + ], + "displayName": "GPT-5.4 Nano" + }, + { + "params": [ + { + "id": "reasoning", + "value": "medium" + } + ], + "displayName": "GPT-5.4 Nano", + "isDefault": true + }, + { + "params": [ + { + "id": "reasoning", + "value": "high" + } + ], + "displayName": "GPT-5.4 Nano" + }, + { + "params": [ + { + "id": "reasoning", + "value": "xhigh" + } + ], + "displayName": "GPT-5.4 Nano" + } + ] + }, + { + "id": "claude-haiku-4-5", + "displayName": "Haiku 4.5", + "aliases": [ + "haiku-latest", + "haiku", + "haiku-4.5", + "haiku-4-5" + ], + "parameters": [ + { + "id": "thinking", + "displayName": "Thinking", + "values": [ + { + "value": "false" + }, + { + "value": "true" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "thinking", + "value": "false" + } + ], + "displayName": "Haiku 4.5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + } + ], + "displayName": "Haiku 4.5", + "isDefault": true + } + ] + }, + { + "id": "claude-sonnet-4-5", + "displayName": "Sonnet 4.5", + "aliases": [ + "sonnet", + "sonnet-4.5", + "sonnet-4-5" + ], + "parameters": [ + { + "id": "thinking", + "displayName": "Thinking", + "values": [ + { + "value": "false" + }, + { + "value": "true" + } + ] + }, + { + "id": "context", + "displayName": "Context", + "values": [ + { + "value": "200k", + "displayName": "200K" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "200k" + } + ], + "displayName": "Sonnet 4.5" + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "200k" + } + ], + "displayName": "Sonnet 4.5", + "isDefault": true + } + ] + }, + { + "id": "gpt-5.2-codex", + "displayName": "Codex 5.2", + "aliases": [ + "codex", + "codex-5.2" + ], + "parameters": [ + { + "id": "reasoning", + "displayName": "Reasoning", + "values": [ + { + "value": "low", + "displayName": "Low" + }, + { + "value": "medium", + "displayName": "Medium" + }, + { + "value": "high", + "displayName": "High" + }, + { + "value": "extra-high", + "displayName": "Extra High" + } + ] + }, + { + "id": "fast", + "displayName": "Fast", + "values": [ + { + "value": "false" + }, + { + "value": "true", + "displayName": "Fast" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "reasoning", + "value": "low" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Codex 5.2" + }, + { + "params": [ + { + "id": "reasoning", + "value": "low" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Codex 5.2" + }, + { + "params": [ + { + "id": "reasoning", + "value": "medium" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Codex 5.2" + }, + { + "params": [ + { + "id": "reasoning", + "value": "medium" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Codex 5.2" + }, + { + "params": [ + { + "id": "reasoning", + "value": "high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Codex 5.2" + }, + { + "params": [ + { + "id": "reasoning", + "value": "high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Codex 5.2", + "isDefault": true + }, + { + "params": [ + { + "id": "reasoning", + "value": "extra-high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Codex 5.2" + }, + { + "params": [ + { + "id": "reasoning", + "value": "extra-high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Codex 5.2" + } + ] + }, + { + "id": "gpt-5.1-codex-max", + "displayName": "Codex 5.1 Max", + "aliases": [ + "codex", + "codex-5.1-max" + ], + "parameters": [ + { + "id": "reasoning", + "displayName": "Reasoning", + "values": [ + { + "value": "low", + "displayName": "Low" + }, + { + "value": "medium", + "displayName": "Medium" + }, + { + "value": "high", + "displayName": "High" + }, + { + "value": "extra-high", + "displayName": "Extra High" + } + ] + }, + { + "id": "fast", + "displayName": "Fast", + "values": [ + { + "value": "false" + }, + { + "value": "true", + "displayName": "Fast" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "reasoning", + "value": "low" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Codex 5.1 Max" + }, + { + "params": [ + { + "id": "reasoning", + "value": "low" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Codex 5.1 Max" + }, + { + "params": [ + { + "id": "reasoning", + "value": "medium" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Codex 5.1 Max" + }, + { + "params": [ + { + "id": "reasoning", + "value": "medium" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Codex 5.1 Max" + }, + { + "params": [ + { + "id": "reasoning", + "value": "high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Codex 5.1 Max" + }, + { + "params": [ + { + "id": "reasoning", + "value": "high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Codex 5.1 Max", + "isDefault": true + }, + { + "params": [ + { + "id": "reasoning", + "value": "extra-high" + }, + { + "id": "fast", + "value": "false" + } + ], + "displayName": "Codex 5.1 Max" + }, + { + "params": [ + { + "id": "reasoning", + "value": "extra-high" + }, + { + "id": "fast", + "value": "true" + } + ], + "displayName": "Codex 5.1 Max" + } + ] + }, + { + "id": "gpt-5.1", + "displayName": "GPT-5.1", + "aliases": [ + "gpt" + ], + "parameters": [ + { + "id": "reasoning", + "displayName": "Reasoning", + "values": [ + { + "value": "low", + "displayName": "Low" + }, + { + "value": "medium", + "displayName": "Medium" + }, + { + "value": "high", + "displayName": "High" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "reasoning", + "value": "low" + } + ], + "displayName": "GPT-5.1" + }, + { + "params": [ + { + "id": "reasoning", + "value": "medium" + } + ], + "displayName": "GPT-5.1", + "isDefault": true + }, + { + "params": [ + { + "id": "reasoning", + "value": "high" + } + ], + "displayName": "GPT-5.1" + } + ] + }, + { + "id": "gemini-3-flash", + "displayName": "Gemini 3 Flash", + "variants": [ + { + "params": [], + "displayName": "Gemini 3 Flash", + "isDefault": true + } + ] + }, + { + "id": "gemini-3.5-flash", + "displayName": "Gemini 3.5 Flash", + "aliases": [ + "gemini-flash-latest", + "gemini-flash" + ], + "variants": [ + { + "params": [], + "displayName": "Gemini 3.5 Flash", + "isDefault": true + } + ] + }, + { + "id": "gpt-5.1-codex-mini", + "displayName": "Codex 5.1 Mini", + "aliases": [ + "codex-mini-latest", + "codex-mini" + ], + "parameters": [ + { + "id": "reasoning", + "displayName": "Reasoning", + "values": [ + { + "value": "low", + "displayName": "Low" + }, + { + "value": "medium", + "displayName": "Medium" + }, + { + "value": "high", + "displayName": "High" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "reasoning", + "value": "low" + } + ], + "displayName": "Codex 5.1 Mini" + }, + { + "params": [ + { + "id": "reasoning", + "value": "medium" + } + ], + "displayName": "Codex 5.1 Mini", + "isDefault": true + }, + { + "params": [ + { + "id": "reasoning", + "value": "high" + } + ], + "displayName": "Codex 5.1 Mini" + } + ] + }, + { + "id": "claude-sonnet-4", + "displayName": "Sonnet 4", + "aliases": [ + "sonnet", + "sonnet-4" + ], + "parameters": [ + { + "id": "thinking", + "displayName": "Thinking", + "values": [ + { + "value": "false" + }, + { + "value": "true" + } + ] + }, + { + "id": "context", + "displayName": "Context", + "values": [ + { + "value": "200k", + "displayName": "200K" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "thinking", + "value": "false" + }, + { + "id": "context", + "value": "200k" + } + ], + "displayName": "Sonnet 4", + "isDefault": true + }, + { + "params": [ + { + "id": "thinking", + "value": "true" + }, + { + "id": "context", + "value": "200k" + } + ], + "displayName": "Sonnet 4" + } + ] + }, + { + "id": "gpt-5-mini", + "displayName": "GPT-5 Mini", + "aliases": [ + "gpt-mini" + ], + "variants": [ + { + "params": [], + "displayName": "GPT-5 Mini", + "isDefault": true + } + ] + }, + { + "id": "gemini-2.5-flash", + "displayName": "Gemini 2.5 Flash", + "aliases": [ + "gemini-flash" + ], + "variants": [ + { + "params": [], + "displayName": "Gemini 2.5 Flash", + "isDefault": true + } + ] + }, + { + "id": "glm-5.2", + "displayName": "GLM 5.2", + "parameters": [ + { + "id": "reasoning", + "displayName": "Reasoning", + "values": [ + { + "value": "high", + "displayName": "High" + }, + { + "value": "max", + "displayName": "Max" + } + ] + } + ], + "variants": [ + { + "params": [ + { + "id": "reasoning", + "value": "high" + } + ], + "displayName": "GLM 5.2", + "isDefault": true + }, + { + "params": [ + { + "id": "reasoning", + "value": "max" + } + ], + "displayName": "GLM 5.2" + } + ] + } +] \ No newline at end of file diff --git a/test/model-axes.test.ts b/test/model-axes.test.ts new file mode 100644 index 0000000..d00336c --- /dev/null +++ b/test/model-axes.test.ts @@ -0,0 +1,83 @@ +import { describe, expect, it } from "vitest"; +import type { ModelListItem } from "@cursor/sdk"; +import catalog from "./fixtures/cursor-catalog.json" with { type: "json" }; +import { buildModelAxes, isValidCombo, snapCombo } from "../src/model-axes.js"; + +const byId = (id: string): ModelListItem => + (catalog as ModelListItem[]).find((m) => m.id === id)!; + +describe("buildModelAxes", () => { + it("derives four axes for a full-matrix model (claude-opus-4-8)", () => { + const axes = buildModelAxes(byId("claude-opus-4-8")); + const ids = axes.map((a) => a.id).sort(); + expect(ids).toEqual(["context", "effort", "fast", "thinking"]); + }); + + it("orders effort values low..max and marks it a cycle", () => { + const effort = buildModelAxes(byId("claude-opus-4-8")).find((a) => a.id === "effort")!; + expect(effort.values).toEqual(["low", "medium", "high", "xhigh", "max"]); + expect(effort.kind).toBe("cycle"); + }); + + it("marks a two-value param as a toggle (fast)", () => { + const fast = buildModelAxes(byId("claude-opus-4-8")).find((a) => a.id === "fast")!; + expect(fast.values).toEqual(["false", "true"]); + expect(fast.kind).toBe("toggle"); + }); + + it("seeds default from the isDefault variant (opus effort=high)", () => { + const effort = buildModelAxes(byId("claude-opus-4-8")).find((a) => a.id === "effort")!; + expect(effort.default).toBe("high"); + }); + + it("returns a single thinking toggle for sonnet-4-5", () => { + const axes = buildModelAxes(byId("claude-sonnet-4-5")); + expect(axes.map((a) => a.id)).toEqual(["thinking"]); + expect(axes[0]!.default).toBe("true"); + }); + + it("returns no axes for a fixed model (default)", () => { + expect(buildModelAxes(byId("default"))).toEqual([]); + }); + + it("drops pinned single-value params (cyber never becomes an axis)", () => { + const axes = buildModelAxes(byId("claude-opus-4-8")); + expect(axes.find((a) => a.id === "cyber")).toBeUndefined(); + }); + + it("orders a reasoning axis and includes catalog-only values", () => { + const axes = buildModelAxes(byId("gpt-5.4-mini")); + const reasoning = axes.find((a) => a.id === "reasoning"); + expect(reasoning).toBeDefined(); + // known-order prefix must lead; any catalog value present must appear + const idx = (v: string) => reasoning!.values.indexOf(v); + if (idx("low") >= 0 && idx("high") >= 0) expect(idx("low")).toBeLessThan(idx("high")); + }); +}); + +describe("combo validation (asymmetric models)", () => { + // gpt-5.5: fast=true only exists at context=272k, never at context=1m. + it("accepts a real combo", () => { + const item = byId("gpt-5.5"); + expect(isValidCombo(item, { context: "272k", reasoning: "high", fast: "true" })).toBe(true); + }); + + it("rejects an impossible combo (fast at 1m context)", () => { + const item = byId("gpt-5.5"); + expect(isValidCombo(item, { context: "1m", reasoning: "high", fast: "true" })).toBe(false); + }); + + it("snapCombo fixes a conflicting non-changed axis, keeping the just-changed axis", () => { + const item = byId("gpt-5.5"); + // User just switched context to 1m; fast=true is now invalid -> snap fast. + const snapped = snapCombo(item, { context: "1m", reasoning: "high", fast: "true" }, "context"); + expect(snapped.context).toBe("1m"); // changed axis preserved + expect(isValidCombo(item, snapped)).toBe(true); // result is valid + }); + + it("snapCombo returns the input unchanged when already valid", () => { + const item = byId("gpt-5.5"); + const input = { context: "272k", reasoning: "high", fast: "true" }; + expect(snapCombo(item, input, "fast")).toEqual(input); + }); +}); diff --git a/test/model-discovery.test.ts b/test/model-discovery.test.ts index 12b0081..631aa60 100644 --- a/test/model-discovery.test.ts +++ b/test/model-discovery.test.ts @@ -18,7 +18,11 @@ const items: ModelListItem[] = [ { id: "composer-2.5", displayName: "Composer 2.5", - parameters: [{ id: "thinking", values: [{ value: "off" }, { value: "on" }] }], + parameters: [{ id: "thinking", values: [{ value: "false" }, { value: "true" }] }], + variants: [ + { params: [{ id: "thinking", value: "false" }], displayName: "d", isDefault: true }, + { params: [{ id: "thinking", value: "true" }], displayName: "d" }, + ], }, { id: "plain", displayName: "Plain Model" }, ]; @@ -56,10 +60,8 @@ describe("toOpencodeModels", () => { // variants can come from — they must be present here. const map = toOpencodeModels(items); expect(map["composer-2.5"]!.variants).toEqual({ - off: { params: { thinking: "off" } }, - on: { params: { thinking: "on" } }, + thinking: { params: { thinking: "true" } }, }); - // No reasoning params → no variants (plan is an opencode agent, not a variant). expect(map["plain"]!.variants).toEqual({}); }); @@ -72,6 +74,10 @@ describe("toOpencodeModels", () => { id: "composer-2.5", displayName: "Composer 2.5", parameters: [{ id: "fast", values: [{ value: "false" }, { value: "true" }] }], + variants: [ + { params: [{ id: "fast", value: "false" }], displayName: "d", isDefault: true }, + { params: [{ id: "fast", value: "true" }], displayName: "d" }, + ], }, { id: "plain", displayName: "Plain Model" }, ]); diff --git a/test/model-v2.test.ts b/test/model-v2.test.ts index 16f01ad..c12d111 100644 --- a/test/model-v2.test.ts +++ b/test/model-v2.test.ts @@ -8,7 +8,10 @@ describe("buildModelV2Map", () => { { id: "composer-2.5", displayName: "Composer 2.5", - parameters: [{ id: "fast", values: [{ value: "false" }, { value: "true" }] }], + variants: [ + { params: [{ id: "fast", value: "false" }], displayName: "d", isDefault: true }, + { params: [{ id: "fast", value: "true" }], displayName: "d" }, + ], } satisfies ModelListItem, ]); expect(map["composer-2.5"]!.options).toEqual({ params: { fast: "false" } }); diff --git a/test/model-variants.test.ts b/test/model-variants.test.ts index 35deede..ef2a33f 100644 --- a/test/model-variants.test.ts +++ b/test/model-variants.test.ts @@ -1,260 +1,85 @@ import { describe, expect, it } from "vitest"; import type { ModelListItem } from "@cursor/sdk"; import { buildModelVariants, defaultModelParams } from "../src/model-variants.js"; +import catalog from "./fixtures/cursor-catalog.json" with { type: "json" }; -function model(parameters: ModelListItem["parameters"]): ModelListItem { - return { id: "m", displayName: "M", parameters }; +const cat = catalog as ModelListItem[]; +function byId(id: string): ModelListItem { + const m = cat.find((x) => x.id === id); + if (!m) throw new Error(`fixture missing model ${id}`); + return m; } -describe("buildModelVariants", () => { - it("collapses a boolean thinking param into a single param-named variant", () => { - // Real catalog shape: thinking=["false","true"] (claude-* models). A - // literal "true"/"false" variant pair is meaningless in the picker. - const variants = buildModelVariants( - model([{ id: "thinking", values: [{ value: "false" }, { value: "true" }] }]), +describe("buildModelVariants (curated, non-cartesian)", () => { + it("curates a 40-variant model into a short single-axis list", () => { + const variants = buildModelVariants(byId("claude-opus-4-8")); + expect(Object.keys(variants).sort()).toEqual( + ["300k", "fast", "low", "max", "medium", "xhigh"].sort(), ); - expect(variants).toEqual({ thinking: { params: { thinking: "true" } } }); - }); - - it("keys enum reasoning params by their values", () => { - // Real catalog shape: reasoning=["none","low","medium","high","extra-high"]. - const variants = buildModelVariants( - model([{ id: "reasoning", values: [{ value: "low" }, { value: "high" }] }]), - ); - expect(variants).toEqual({ - low: { params: { reasoning: "low" } }, - high: { params: { reasoning: "high" } }, + // effort values vary effort only; other axes stay at model defaults. + expect(variants["low"]).toEqual({ + params: { thinking: "true", context: "1m", effort: "low", fast: "false" }, }); - }); - - it("drops the boolean thinking variant when an effort enum is present (claude catalog shape)", () => { - // Cursor's claude-* catalog exposes BOTH a boolean `thinking` toggle and an - // effort enum. Selecting any effort level already enables reasoning, so the - // standalone `thinking` variant is redundant — and surfacing it would add a - // stray entry the standard opencode providers (effort-only) never show. - const variants = buildModelVariants( - model([ - { id: "thinking", values: [{ value: "false" }, { value: "true" }] }, - { id: "effort", values: [{ value: "low" }, { value: "max" }] }, - ]), - ); - expect(variants).toEqual({ - low: { params: { effort: "low" } }, - max: { params: { effort: "max" } }, + // the fast opt-in bakes the effort/context/thinking defaults. + expect(variants["fast"]).toEqual({ + params: { thinking: "true", context: "1m", effort: "high", fast: "true" }, }); - }); - - it("suppresses the boolean thinking variant regardless of param order", () => { - // Order-independence guard for the hasEffortEnum pre-pass: the effort enum - // declared AFTER the boolean must still suppress it, and vice versa. - const enumFirst = buildModelVariants( - model([ - { id: "effort", values: [{ value: "low" }, { value: "max" }] }, - { id: "thinking", values: [{ value: "false" }, { value: "true" }] }, - ]), - ); - expect(enumFirst).toEqual({ - low: { params: { effort: "low" } }, - max: { params: { effort: "max" } }, + // the non-default context value is offered too. + expect(variants["300k"]).toEqual({ + params: { thinking: "true", context: "300k", effort: "high", fast: "false" }, }); }); - it("composes suppression with fast defaults (production claude-via-Cursor shape)", () => { - // The real catalog model: boolean `thinking` + effort enum + `fast`. The - // `thinking` variant is suppressed, each effort variant bakes `fast` OFF - // (defaultModelParams), and a standalone `fast` opt-in still surfaces. - const variants = buildModelVariants( - model([ - { id: "thinking", values: [{ value: "false" }, { value: "true" }] }, - { id: "effort", values: [{ value: "low" }, { value: "high" }] }, - { id: "fast", values: [{ value: "false" }, { value: "true" }] }, - ]), - ); - expect(variants).toEqual({ - low: { params: { effort: "low", fast: "false" } }, - high: { params: { effort: "high", fast: "false" } }, - fast: { params: { fast: "true" } }, - }); - }); - - it("does not suppress the boolean thinking variant for a zero-value effort enum", () => { - // hasEffortEnum requires a non-empty enum; an effort param with no values - // must not count as an enum, so the boolean `thinking` variant survives. - const variants = buildModelVariants( - model([ - { id: "thinking", values: [{ value: "false" }, { value: "true" }] }, - { id: "effort", values: [] }, - ]), - ); - expect(variants).toEqual({ thinking: { params: { thinking: "true" } } }); - }); - - it("does not emit a thinking variant when the boolean lacks a 'true' value", () => { - // Boolean `thinking=["false"]` has nothing to opt INTO; combined with an - // effort enum the result is purely the effort variants. - const variants = buildModelVariants( - model([ - { id: "thinking", values: [{ value: "false" }] }, - { id: "effort", values: [{ value: "low" }] }, - ]), - ); - expect(variants).toEqual({ low: { params: { effort: "low" } } }); - }); - - it("pins current behavior for a mixed boolean+enum reasoning param", () => { - // A single reasoning param mixing boolean sentinels with effort values is - // classified non-boolean (isBooleanParam requires EVERY value be a sentinel), - // so it flows through the enum branch and emits literal false/true variants. - // Not a real catalog shape today; pinned so a future change is caught. - const variants = buildModelVariants( - model([ - { id: "reasoning", values: [{ value: "false" }, { value: "true" }, { value: "high" }] }, - ]), - ); - expect(variants).toEqual({ - false: { params: { reasoning: "false" } }, - true: { params: { reasoning: "true" } }, - high: { params: { reasoning: "high" } }, + it("varies effort and bakes the model's default fast (grok fast defaults ON)", () => { + expect(buildModelVariants(byId("grok-4.5"))).toEqual({ + low: { params: { effort: "low", fast: "true" } }, + medium: { params: { effort: "medium", fast: "true" } }, }); }); - it("prefixes a value key on collision between two enum params", () => { - const variants = buildModelVariants( - model([ - { id: "reasoning", values: [{ value: "low" }] }, - { id: "effort", values: [{ value: "low" }] }, - ]), - ); - expect(variants).toEqual({ - low: { params: { reasoning: "low" } }, - "effort-low": { params: { effort: "low" } }, - }); - }); - - it("renames extra-high to xhigh in the variant key but keeps the wire value", () => { - // Cursor labels the top reasoning tier "extra-high"; the opencode standard - // (models.dev) calls it "xhigh". The variant KEY normalizes to xhigh so - // the cycler is consistent across providers; the param VALUE sent to - // Cursor's API stays "extra-high". - const variants = buildModelVariants( - model([{ id: "reasoning", values: [{ value: "low" }, { value: "extra-high" }] }]), - ); - expect(variants).toEqual({ - low: { params: { reasoning: "low" } }, - xhigh: { params: { reasoning: "extra-high" } }, - }); - }); - - it("drops the 'none' reasoning value (it is the model default)", () => { - // `none` = reasoning OFF, which is what you get by selecting no variant. - // Surfacing it as a selectable entry is meaningless, so it is skipped. - const variants = buildModelVariants( - model([ - { id: "reasoning", values: [{ value: "none" }, { value: "low" }, { value: "high" }] }, - ]), - ); - expect(variants).toEqual({ + it("keys a reasoning axis by value, dropping the default (medium)", () => { + expect(buildModelVariants(byId("gpt-5.4-mini"))).toEqual({ + none: { params: { reasoning: "none" } }, low: { params: { reasoning: "low" } }, high: { params: { reasoning: "high" } }, + xhigh: { params: { reasoning: "xhigh" } }, }); }); - it("composes none-drop and extra-high rename for the real GPT shape", () => { - // gpt-5.5 / gpt-5.4 catalog: reasoning=[none,low,medium,high,extra-high] - // + fast. Expect: low, medium, high, xhigh (no none, extra-high→xhigh), - // each effort variant bakes fast OFF, plus a standalone fast opt-in. - const variants = buildModelVariants( - model([ - { - id: "reasoning", - values: [ - { value: "none" }, - { value: "low" }, - { value: "medium" }, - { value: "high" }, - { value: "extra-high" }, - ], - }, - { id: "fast", values: [{ value: "false" }, { value: "true" }] }, - ]), - ); - expect(variants).toEqual({ - low: { params: { reasoning: "low", fast: "false" } }, - medium: { params: { reasoning: "medium", fast: "false" } }, - high: { params: { reasoning: "high", fast: "false" } }, - xhigh: { params: { reasoning: "extra-high", fast: "false" } }, - fast: { params: { fast: "true" } }, - }); + it("returns no variants for a model with no axes", () => { + expect(buildModelVariants(byId("gemini-3.1-pro"))).toEqual({}); }); - it("prefixes the display key on a collision involving extra-high", () => { - // Defensive: if two reasoning params both resolve to the xhigh display key - // (one via the real "xhigh" value, one via "extra-high"→xhigh), the second - // is prefixed with its param id. No current catalog model hits this, but - // the guard must hold. - const variants = buildModelVariants( - model([ - { id: "effort", values: [{ value: "xhigh" }] }, - { id: "reasoning", values: [{ value: "extra-high" }] }, - ]), - ); - expect(variants).toEqual({ - xhigh: { params: { effort: "xhigh" } }, - "reasoning-xhigh": { params: { reasoning: "extra-high" } }, - }); + it("returns no variants for a single default-ON toggle (composer fast defaults true)", () => { + // Known --pure fallback limitation: nothing non-default/non-off to add. + expect(buildModelVariants(byId("composer-2.5"))).toEqual({}); }); - it("surfaces a non-reasoning boolean (fast) as an opt-in toggle; ignores enum context", () => { - // `fast` is Cursor's fast-tier toggle. It is not a reasoning level, but the - // user must be able to opt INTO it from the picker (default is off, sent - // explicitly via defaultModelParams). `context` is an enum, still unsupported. - // plan is opencode's plan AGENT (Tab), mapped via the chat.params hook. - const variants = buildModelVariants( - model([ - { id: "fast", values: [{ value: "false" }, { value: "true" }] }, - { id: "context", values: [{ value: "300k" }, { value: "1m" }] }, - ]), - ); - expect(variants).toEqual({ fast: { params: { fast: "true" } } }); - }); - - it("bakes the fast-off default into reasoning variants for fast-capable models", () => { - // Cursor defaults `fast` to true for several models (composer/codex). When - // the user picks a reasoning level we must pin fast OFF explicitly, so a - // reasoning selection never silently falls back to Cursor's fast default. - const variants = buildModelVariants( - model([ - { id: "effort", values: [{ value: "low" }, { value: "high" }] }, - { id: "fast", values: [{ value: "false" }, { value: "true" }] }, - ]), - ); - expect(variants).toEqual({ - low: { params: { effort: "low", fast: "false" } }, - high: { params: { effort: "high", fast: "false" } }, - fast: { params: { fast: "true" } }, + it("renames the extra-high reasoning wire value to the xhigh key", () => { + const variants = buildModelVariants(byId("gpt-5.5")); + // key is normalized to xhigh; the Cursor wire value stays extra-high. + expect(variants["xhigh"]).toEqual({ + params: { context: "1m", reasoning: "extra-high", fast: "false" }, }); }); +}); - it("returns no variants for a model without parameters", () => { - expect(buildModelVariants(model(undefined))).toEqual({}); +describe("defaultModelParams (axis defaults + pinned)", () => { + it("seeds every axis default plus pinned single-value params (opus pins cyber=false)", () => { + expect(defaultModelParams(byId("claude-opus-4-8"))).toEqual({ + thinking: "true", + context: "1m", + effort: "high", + fast: "false", + cyber: "false", + }); }); -}); -describe("defaultModelParams", () => { - it("defaults non-reasoning boolean params (fast) OFF", () => { - expect( - defaultModelParams( - model([{ id: "fast", values: [{ value: "false" }, { value: "true" }] }]), - ), - ).toEqual({ fast: "false" }); + it("uses the model's own default toggle direction (grok fast defaults ON)", () => { + expect(defaultModelParams(byId("grok-4.5"))).toEqual({ effort: "high", fast: "true" }); }); - it("returns no defaults for models without non-reasoning booleans", () => { - expect( - defaultModelParams( - model([{ id: "effort", values: [{ value: "low" }, { value: "high" }] }]), - ), - ).toEqual({}); - expect(defaultModelParams(model(undefined))).toEqual({}); + it("returns empty for a model with no axes or pinned params", () => { + expect(defaultModelParams(byId("gemini-3.1-pro"))).toEqual({}); }); }); diff --git a/test/state-file.test.ts b/test/state-file.test.ts new file mode 100644 index 0000000..acd0a2d --- /dev/null +++ b/test/state-file.test.ts @@ -0,0 +1,72 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { + filterStringParams, + readSelection, + readStates, + writeSelection, +} from "../src/tui/state-file.js"; + +describe("cursor-states file bridge", () => { + let dir: string; + beforeEach(() => { + dir = mkdtempSync(join(tmpdir(), "cursor-states-")); + process.env.OPENCODE_CURSOR_STATE_FILE = join(dir, "cursor-states.json"); + }); + afterEach(() => { + delete process.env.OPENCODE_CURSOR_STATE_FILE; + rmSync(dir, { recursive: true, force: true }); + }); + + it("returns empty map when no file exists", () => { + expect(readStates()).toEqual({}); + expect(readSelection("s1")).toBeUndefined(); + }); + + it("round-trips a selection for a session", () => { + writeSelection("s1", { effort: "high", fast: "false" }); + expect(readSelection("s1")).toEqual({ effort: "high", fast: "false" }); + }); + + it("keeps sessions independent and overwrites in place", () => { + writeSelection("s1", { effort: "low" }); + writeSelection("s2", { effort: "max" }); + writeSelection("s1", { effort: "high" }); + expect(readSelection("s1")).toEqual({ effort: "high" }); + expect(readSelection("s2")).toEqual({ effort: "max" }); + }); + + it("tolerates a corrupt file by treating it as empty", () => { + writeSelection("s1", { effort: "high" }); + // corrupt it + require("node:fs").writeFileSync(process.env.OPENCODE_CURSOR_STATE_FILE!, "{not json"); + expect(readStates()).toEqual({}); + }); +}); + +describe("filterStringParams", () => { + it("keeps string values and drops non-string values", () => { + const input = { + effort: "high", + fast: "false", + // Non-string values a corrupt/hand-edited file could carry: + thinking: true as unknown as string, + context: 42 as unknown as string, + reasoning: null as unknown as string, + }; + expect(filterStringParams(input)).toEqual({ + effort: "high", + fast: "false", + }); + }); + + it("returns an empty object when no values are strings", () => { + const input = { a: 1, b: false, c: null } as unknown as Record< + string, + unknown + >; + expect(filterStringParams(input)).toEqual({}); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 1e77169..a7381b8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,8 @@ "lib": ["ES2023"], "strict": true, "noUncheckedIndexedAccess": true, + "jsx": "react-jsx", + "jsxImportSource": "@opentui/solid", "esModuleInterop": true, "skipLibCheck": true, "noEmit": true, diff --git a/tsup.config.ts b/tsup.config.ts index 0256a9e..b006cf2 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -12,6 +12,7 @@ export default defineConfig({ entry: { "provider/index": "src/provider/index.ts", "plugin/index": "src/plugin/index.ts", + "tui/index": "src/tui/index.tsx", // Node sidecar hosting @cursor/sdk traffic when the plugin runs under Bun // (Bun's node:http2 breaks Cursor's streaming RPC). Spawned, not imported. "sidecar/agent-host": "src/sidecar/agent-host.mjs", @@ -26,5 +27,12 @@ export default defineConfig({ // inlined at build time instead. define: { __PKG_VERSION__: JSON.stringify(pkg.version) }, // @cursor/sdk is heavy and resolved at runtime; keep these external. - external: ["@cursor/sdk", "@ai-sdk/provider", "@opencode-ai/plugin"], + external: [ + "@cursor/sdk", + "@ai-sdk/provider", + "@opencode-ai/plugin", + "@opentui/core", + "@opentui/solid", + "solid-js", + ], });