Compare commits

..

2 Commits

7 changed files with 140 additions and 141 deletions

View File

@@ -1 +0,0 @@
- fix(providers): filter Perplexity model import to the Sonar family so Agent-API catalog ids stop surfacing as routable chat models (#11060)

View File

@@ -0,0 +1 @@
- fix(install): make the ONNX dependency chain optional so Termux/Android installs succeed again (#11095)

72
package-lock.json generated
View File

@@ -18,7 +18,6 @@
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@huggingface/transformers": "^4.2.0",
"@lobehub/icons": "^5.16.0",
"@modelcontextprotocol/sdk": "^1.29.0",
"@monaco-editor/react": "^4.7.0",
@@ -61,7 +60,6 @@
"next-themes": "^0.4.6",
"node-machine-id": "^1.1.12",
"omniglyph": "^1.4.0",
"onnxruntime-node": "1.24.3",
"open": "^11.0.1",
"ora": "^9.4.1",
"parse5": "^8.0.1",
@@ -156,9 +154,11 @@
},
"optionalDependencies": {
"@atjsh/llmlingua-2": "3.0.0",
"@huggingface/transformers": "^4.2.0",
"better-sqlite3": "^13.0.2",
"js-tiktoken": "^1.0.20",
"keytar": "^7.9.0",
"onnxruntime-node": "1.24.3",
"sqlite-vec": "^0.1.9",
"tls-client-node": "^0.2.0",
"wreq-js": "^3.0.0"
@@ -4510,6 +4510,7 @@
"resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.5.9.tgz",
"integrity": "sha512-uWTG+l3VJRsl7EXxYizuL3P+cCPoc3cRqbWWRcQN0FhejRfbdq0RNhCmbY/YDtnTcz9icdLYuLDjsnz4d8JMuw==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=18"
}
@@ -4518,13 +4519,15 @@
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@huggingface/tokenizers/-/tokenizers-0.1.3.tgz",
"integrity": "sha512-8rF/RRT10u+kn7YuUbUg0OF30K8rjTc78aHpxT+qJ1uWSqxT1MHi8+9ltwYfkFYJzT/oS+qw3JVfHtNMGAdqyA==",
"license": "Apache-2.0"
"license": "Apache-2.0",
"optional": true
},
"node_modules/@huggingface/transformers": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-4.2.0.tgz",
"integrity": "sha512-8BRCoBMH0XsWaEIamuR0LrJGAfftgHAfb2Vrffy0VKlSAE/MnUJ5/h/zTfEP3fDIft+nk7TqB8xXEyABGitBjQ==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"@huggingface/jinja": "^0.5.6",
"@huggingface/tokenizers": "^0.1.3",
@@ -9483,30 +9486,35 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/codegen": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
"integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
"integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/fetch": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
"integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
"devOptional": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.1"
@@ -9516,24 +9524,28 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/utf8": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz",
"integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@radix-ui/number": {
@@ -12737,6 +12749,7 @@
"version": "26.2.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz",
"integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"undici-types": "~8.3.0"
@@ -13998,6 +14011,7 @@
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.6.0.tgz",
"integrity": "sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14.0"
}
@@ -14971,7 +14985,8 @@
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
"integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/bottleneck": {
"version": "2.19.5",
@@ -17935,6 +17950,7 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0",
@@ -17964,6 +17980,7 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"define-data-property": "^1.0.1",
@@ -18064,7 +18081,8 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/detect-node-es": {
"version": "1.1.0",
@@ -18908,7 +18926,8 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/es6-promisify": {
"version": "7.0.0",
@@ -20400,7 +20419,8 @@
"version": "25.9.23",
"resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.9.23.tgz",
"integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==",
"license": "Apache-2.0"
"license": "Apache-2.0",
"optional": true
},
"node_modules/flatted": {
"version": "3.4.2",
@@ -21226,6 +21246,7 @@
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
"integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
"license": "BSD-3-Clause",
"optional": true,
"dependencies": {
"boolean": "^3.0.1",
"es6-error": "^4.1.1",
@@ -21243,6 +21264,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"license": "ISC",
"optional": true,
"bin": {
"semver": "bin/semver.js"
},
@@ -21291,6 +21313,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
"integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"define-properties": "^1.2.1",
@@ -21645,7 +21668,8 @@
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz",
"integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==",
"license": "ISC"
"license": "ISC",
"optional": true
},
"node_modules/hachure-fill": {
"version": "0.5.2",
@@ -21679,6 +21703,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0"
@@ -24790,7 +24815,8 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
"license": "ISC"
"license": "ISC",
"optional": true
},
"node_modules/json5": {
"version": "2.2.3",
@@ -26654,6 +26680,7 @@
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
"license": "MIT",
"optional": true,
"dependencies": {
"escape-string-regexp": "^4.0.0"
},
@@ -29425,6 +29452,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -29632,7 +29660,8 @@
"version": "1.24.3",
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.3.tgz",
"integrity": "sha512-GeuPZO6U/LBJXvwdaqHbuUmoXiEdeCjWi/EG7Y1HNnDwJYuk6WUbNXpF6luSUY8yASul3cmUlLGrCCL1ZgVXqA==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/onnxruntime-node": {
"version": "1.24.3",
@@ -29640,6 +29669,7 @@
"integrity": "sha512-JH7+czbc8ALA819vlTgcV+Q214/+VjGeBHDjX81+ZCD0PCVCIFGFNtT0V4sXG/1JXypKPgScQcB3ij/hk3YnTg==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"win32",
"darwin",
@@ -29656,6 +29686,7 @@
"resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.26.0-dev.20260416-b7804b056c.tgz",
"integrity": "sha512-MD6Ss4GSpQBo6zqoJzyT9LRbKYs7x/JVN23FT24EcEvlqF4VuzPOeH6X38orZPKHQDbprn7K+SBpu0/mj2CQiw==",
"license": "MIT",
"optional": true,
"dependencies": {
"flatbuffers": "^25.1.24",
"guid-typescript": "^1.0.9",
@@ -29669,13 +29700,15 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
"license": "Apache-2.0",
"optional": true
},
"node_modules/onnxruntime-web/node_modules/onnxruntime-common": {
"version": "1.24.0-dev.20251116-b39e144322",
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.0-dev.20251116-b39e144322.tgz",
"integrity": "sha512-BOoomdHYmNRL5r4iQ4bMvsl2t0/hzVQ3OM3PHD0gxeXu1PmggqBv3puZicEUVOA3AtHHYmqZtjMj9FOfGrATTw==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/open": {
"version": "11.0.1",
@@ -30906,7 +30939,8 @@
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/playwright": {
"version": "1.62.1",
@@ -31861,6 +31895,7 @@
"version": "7.6.5",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz",
"integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==",
"devOptional": true,
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -31884,6 +31919,7 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/proxy-addr": {
@@ -33280,6 +33316,7 @@
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
"integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
"license": "BSD-3-Clause",
"optional": true,
"dependencies": {
"boolean": "^3.0.1",
"detect-node": "^2.0.4",
@@ -33655,7 +33692,8 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/send": {
"version": "1.2.1",
@@ -33688,6 +33726,7 @@
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
"integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
"license": "MIT",
"optional": true,
"dependencies": {
"type-fest": "^0.13.1"
},
@@ -33703,6 +33742,7 @@
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
"license": "(MIT OR CC0-1.0)",
"optional": true,
"engines": {
"node": ">=10"
},
@@ -34474,7 +34514,8 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
"license": "BSD-3-Clause"
"license": "BSD-3-Clause",
"optional": true
},
"node_modules/sql.js": {
"version": "1.14.2",
@@ -36187,6 +36228,7 @@
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
"devOptional": true,
"license": "MIT"
},
"node_modules/unicode-emoji-modifier-base": {

View File

@@ -265,7 +265,6 @@
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@huggingface/transformers": "^4.2.0",
"@lobehub/icons": "^5.16.0",
"@modelcontextprotocol/sdk": "^1.29.0",
"@monaco-editor/react": "^4.7.0",
@@ -308,7 +307,6 @@
"next-themes": "^0.4.6",
"node-machine-id": "^1.1.12",
"omniglyph": "^1.4.0",
"onnxruntime-node": "1.24.3",
"open": "^11.0.1",
"ora": "^9.4.1",
"parse5": "^8.0.1",
@@ -343,9 +341,11 @@
},
"optionalDependencies": {
"@atjsh/llmlingua-2": "3.0.0",
"@huggingface/transformers": "^4.2.0",
"better-sqlite3": "^13.0.2",
"js-tiktoken": "^1.0.20",
"keytar": "^7.9.0",
"onnxruntime-node": "1.24.3",
"sqlite-vec": "^0.1.9",
"tls-client-node": "^0.2.0",
"wreq-js": "^3.0.0"

View File

@@ -87,22 +87,6 @@ export function parseAlibabaModelStudioModelsForConnection(
export function parseQwenCloudTextModels(data: any): any[] {
return parseCuratedDashscopeModels(data, QWEN_CLOUD_TEXT_MODELS, QWEN_CLOUD_TEXT_MODEL_IDS);
}
// Perplexity's /v1/models lists the Agent API catalog (vendor-prefixed ids like
// "anthropic/claude-fable-5"), but chat requests always go to the classic
// /chat/completions endpoint, which only accepts the Sonar family. Filter
// discovery to Sonar-family ids so agent-style ids never surface as routable
// chat models (#11060). Bounded pattern — no ReDoS-prone quantifiers.
export function parsePerplexitySonarModels(data: any): any[] {
const models = Array.isArray(data?.data)
? data.data
: Array.isArray(data?.models)
? data.models
: [];
return models.filter(
(model: any) => typeof model?.id === "string" && /^sonar(-|$)/.test(model.id)
);
}
type ProviderModelsHeaderContext = {
authType?: string;
providerSpecificData?: unknown;
@@ -675,17 +659,6 @@ export const PROVIDER_MODELS_CONFIG: Record<string, ProviderModelsConfigEntry> =
headers: { Accept: "application/json" },
parseResponse: parseClinepassRecommendedModels,
},
// Perplexity's /v1/models lists the Agent API catalog (vendor-prefixed agent
// ids), but chat only accepts the Sonar family on /chat/completions. Import
// must keep Sonar-family ids only (#11060).
perplexity: {
url: "https://api.perplexity.ai/v1/models",
method: "GET",
headers: { "Content-Type": "application/json" },
authHeader: "Authorization",
authPrefix: "Bearer ",
parseResponse: parsePerplexitySonarModels,
},
cohere: {
url: "https://api.cohere.com/v2/models",
method: "GET",

View File

@@ -9,40 +9,52 @@ function readJson<T = Record<string, unknown>>(relPath: string): T {
return JSON.parse(readFileSync(join(repoRoot, relPath), "utf8")) as T;
}
test("@huggingface/transformers is a regular dependency so npm ci never skips it", () => {
// #9962 deliberately moved @huggingface/transformers out of optionalDependencies:
// as an optional dep, npm silently skipped the whole subtree on Node 24/26 (old
// pin dragged onnxruntime-node@1.21.0 whose NAN build no longer compiles), which
// broke `npm ci`/`next build` with "Can't resolve @huggingface/transformers"
// (lazy import in src/lib/memory/embedding/transformersLocal.ts). As a regular
// dep with onnxruntime-node@~1.24.3 (napi prebuilds, no node-gyp) it stays
// installable and the memory embedding path requires() cleanly.
test("ONNX chain (@huggingface/transformers + onnxruntime-node) stays optional so Termux/Android installs succeed", () => {
// #11095: onnxruntime-node declares os ["win32","darwin","linux"], so while
// these lived in `dependencies` every npm install on Android/Termux aborted
// with a fatal EBADPLATFORM. As optionalDependencies npm skips only the
// unsupported-platform subtree (with a warning) and installs normally
// everywhere else. This deliberately reverses the MECHANISM of #9962 while
// keeping its goal: #9962's skip happened because the old onnxruntime-node@
// 1.21.0 pin built from source (NAN) and failed to compile on Node 24/26;
// the current 1.24.3 pin ships napi prebuilds, so on supported platforms the
// chain always installs and `npm ci`/`next build` keep resolving it. On
// platforms where it IS skipped, both consumers degrade gracefully via lazy/
// dynamic imports (asserted below).
const pkg = readJson<{
dependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
overrides?: Record<string, string>;
}>("package.json");
assert.equal(
pkg.dependencies?.["@huggingface/transformers"],
undefined,
"transformers must NOT be a hard dependency (fatal EBADPLATFORM on Android)"
);
assert.equal(
pkg.optionalDependencies?.["@huggingface/transformers"],
"^4.2.0",
"transformers must be a regular dependency (never optional) so npm ci cannot skip it"
"transformers must be an optionalDependency"
);
assert.equal(pkg.optionalDependencies?.["@huggingface/transformers"], undefined);
});
test("transformers + onnxruntime-node are regular dependencies (not optional)", () => {
const pkg = readJson<{
dependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
}>("package.json");
assert.equal(
pkg.dependencies?.["onnxruntime-node"],
"1.24.3",
"onnxruntime-node is a regular dep (napi prebuilds, installable on Node 24/26)"
undefined,
"onnxruntime-node must NOT be a hard dependency (fatal EBADPLATFORM on Android)"
);
assert.equal(pkg.optionalDependencies?.["onnxruntime-node"], undefined);
assert.equal(
pkg.optionalDependencies?.["onnxruntime-node"],
"1.24.3",
"onnxruntime-node must be an optionalDependency pinned in lockstep with the overrides pin"
);
assert.equal(
pkg.overrides?.["onnxruntime-node"],
"1.24.3",
"the overrides pin must stay aligned with @huggingface/transformers' own pin (single-copy invariant)"
);
});
test("lockfile marks the whole ONNX chain optional", () => {
const lock = readJson<{
packages: Record<
string,
@@ -51,26 +63,61 @@ test("transformers + onnxruntime-node are regular dependencies (not optional)",
dependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
}
>;
>;
}>("package-lock.json");
assert.equal(
lock.packages[""]?.dependencies?.["@huggingface/transformers"],
lock.packages[""]?.optionalDependencies?.["@huggingface/transformers"],
"^4.2.0",
"root lock dependencies must hold transformers as a regular (non-optional) dep"
"root lock optionalDependencies must hold transformers"
);
// Optional flag is only written `true` for genuinely optional packages;
// regular deps leave it absent/null. Assert each is NOT optional.
assert.ok(
!lock.packages["node_modules/@huggingface/transformers"]?.optional,
"transformers must not be marked optional in the lockfile"
assert.equal(
lock.packages[""]?.optionalDependencies?.["onnxruntime-node"],
"1.24.3",
"root lock optionalDependencies must hold onnxruntime-node"
);
assert.ok(
!lock.packages["node_modules/onnxruntime-node"]?.optional,
"onnxruntime-node must not be marked optional in the lockfile"
lock.packages["node_modules/@huggingface/transformers"]?.optional,
"transformers must be marked optional in the lockfile"
);
assert.ok(
!lock.packages["node_modules/onnxruntime-common"]?.optional,
"onnxruntime-common must not be marked optional in the lockfile"
lock.packages["node_modules/onnxruntime-node"]?.optional,
"onnxruntime-node must be marked optional in the lockfile"
);
assert.ok(
lock.packages["node_modules/onnxruntime-common"]?.optional,
"onnxruntime-common must be marked optional in the lockfile"
);
});
test("every @huggingface/transformers consumer loads it lazily so absent installs degrade gracefully", () => {
// If any module ever switches to a STATIC import of the optional chain,
// startup crashes on platforms where npm skipped it (Android/Termux).
// transformersLocal.ts must keep its lazy await import() (D8/D25);
// onnxWorker.ts must keep its runtime-variable dynamicImport indirection.
const embeddingSrc = readFileSync(
join(repoRoot, "src/lib/memory/embedding/transformersLocal.ts"),
"utf8"
);
assert.doesNotMatch(
embeddingSrc,
/^\s*import\s+(?:[^'"]*?\s+from\s+)?["']@huggingface\/transformers["']/m,
"transformersLocal.ts must not statically import @huggingface/transformers"
);
assert.match(
embeddingSrc,
/await import\(["']@huggingface\/transformers["']\)/,
"transformersLocal.ts must load @huggingface/transformers via await import()"
);
const workerSrc = readFileSync(
join(repoRoot, "open-sse/services/compression/engines/llmlingua/onnxWorker.ts"),
"utf8"
);
assert.doesNotMatch(
workerSrc,
/^\s*import\s+(?:[^'"]*?\s+from\s+)?["']@huggingface\/transformers["']/m,
"onnxWorker.ts must not statically import @huggingface/transformers"
);
});

View File

@@ -1,63 +0,0 @@
import test from "node:test";
import assert from "node:assert/strict";
import { PROVIDER_MODELS_CONFIG } from "../../src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts";
// Regression guard for #11060 — Perplexity's /v1/models endpoint lists the
// Agent API catalog (vendor-prefixed ids like "anthropic/claude-fable-5"), but
// chat requests always go to the classic /chat/completions endpoint, which only
// accepts the Sonar family. Without a PROVIDER_MODELS_CONFIG entry, generic
// model import pulled those agent-style ids into the connection's chat model
// list and every routed request failed with 400 "Invalid model". The discovery
// entry must exist and its parseResponse must keep only Sonar-family ids.
test("perplexity has a discovery entry in PROVIDER_MODELS_CONFIG", () => {
const cfg = PROVIDER_MODELS_CONFIG.perplexity;
assert.ok(cfg, "expected a perplexity entry in PROVIDER_MODELS_CONFIG");
assert.equal(cfg.method, "GET");
assert.equal(cfg.url, "https://api.perplexity.ai/v1/models");
assert.equal(typeof cfg.parseResponse, "function");
});
test("perplexity parseResponse keeps only the Sonar family (#11060)", () => {
const cfg = PROVIDER_MODELS_CONFIG.perplexity;
const models = cfg.parseResponse({
object: "list",
data: [
{ id: "anthropic/claude-fable-5", object: "model", owned_by: "anthropic" },
{ id: "sonar-pro", object: "model", owned_by: "perplexity" },
{ id: "sonar", object: "model", owned_by: "perplexity" },
],
}) as Array<{ id: string }>;
assert.deepEqual(
models.map((model) => model.id),
["sonar-pro", "sonar"]
);
});
test("perplexity parseResponse keeps every Sonar variant and drops non-Sonar ids", () => {
const cfg = PROVIDER_MODELS_CONFIG.perplexity;
const models = cfg.parseResponse({
data: [
{ id: "sonar-deep-research" },
{ id: "sonar-reasoning-pro" },
{ id: "sonar-pro" },
{ id: "sonar" },
{ id: "openai/gpt-5" },
{ id: "sonarish" },
],
}) as Array<{ id: string }>;
assert.deepEqual(
models.map((model) => model.id),
["sonar-deep-research", "sonar-reasoning-pro", "sonar-pro", "sonar"]
);
});
test("perplexity parseResponse tolerates empty and malformed payloads", () => {
const cfg = PROVIDER_MODELS_CONFIG.perplexity;
assert.deepEqual(cfg.parseResponse({ data: [] }), []);
assert.deepEqual(cfg.parseResponse(undefined), []);
assert.deepEqual(cfg.parseResponse({}), []);
});