Compare commits
4 Commits
78932e82af
...
c1a913d5e4
| Author | SHA1 | Date | |
|---|---|---|---|
| c1a913d5e4 | |||
| 363f0cd607 | |||
| 858e3d7990 | |||
| 067bbd0708 |
@@ -28,8 +28,8 @@
|
|||||||
"@types/fluent-ffmpeg": "^2.1.28",
|
"@types/fluent-ffmpeg": "^2.1.28",
|
||||||
"@types/node": "^25.6.1",
|
"@types/node": "^25.6.1",
|
||||||
"@types/qrcode": "^1.5.6",
|
"@types/qrcode": "^1.5.6",
|
||||||
"@typescript/native-preview": "^7.0.0-beta",
|
|
||||||
"drizzle-kit": "^0.31.10",
|
"drizzle-kit": "^0.31.10",
|
||||||
|
"typescript": "^6.0.3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -236,22 +236,6 @@
|
|||||||
|
|
||||||
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
||||||
|
|
||||||
"@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260421.2", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260421.2", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260421.2", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260421.2", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260421.2", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260421.2", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260421.2", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260421.2" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-CmajHI25HpVWE9R1XFoxr+cphJPxoYD3eFioQtAvXYkMFKnLdICMS9pXre9Pybizb75ejRxjKD5/CVG055rEIg=="],
|
|
||||||
|
|
||||||
"@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260421.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fHv1r3ZmVo6zxuAIFmuX3w9QxbcauoG0SsWhmDwm6VmRubLlOJIcmTtlmV3JAb9oOnq8LuzZljzT7Q39fSMQDw=="],
|
|
||||||
|
|
||||||
"@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260421.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-KWTR6xbW9t+JS7D5DQIzo75pqVXVWUxF9PMv/+S6xsnOjCVd6g0ixHcFpFMJMKSUQpGPr8Z5f7b8ks6LHW01jg=="],
|
|
||||||
|
|
||||||
"@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260421.2", "", { "os": "linux", "cpu": "arm" }, "sha512-BWLQO3nemLDSV5PoE5GPHe1dU9Dth77Kv8/cle9Ujcp4LhPo0KincdPqFH/qKeU/xvW25mgFueflZ1nc4rKuww=="],
|
|
||||||
|
|
||||||
"@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260421.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-VLMEuml3BhUb+jaL0TXQ4xvVODxJF+RhkI+tBWvlynsJI4khTXEiwWh+wPOJrsfBRYFRMXEu28Odl/HXkYze8w=="],
|
|
||||||
|
|
||||||
"@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260421.2", "", { "os": "linux", "cpu": "x64" }, "sha512-qUrJWTB5/wv4wnRG0TRXElAxc2kykNiRNyEIEqBbLmzDlrcvAW7RRy8MXoY1ZyTiKGMu14itZ3x9oW6+blFpRw=="],
|
|
||||||
|
|
||||||
"@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260421.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Rc6NsWlZmCs5YUKVzKgwoBOoRUGsPzct4BDMRX0csD1devLBBc4AbUXWKsJRbpwIAnqMO1ld4sNHEb+wXgfNHQ=="],
|
|
||||||
|
|
||||||
"@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260421.2", "", { "os": "win32", "cpu": "x64" }, "sha512-GQv1+dya1t6EqF2Cpsb+xoozovdX10JUSf6Kl/8xNkTapzmlHd+uMr+8ku3jIASTxoRGn0Mklgjj3MDKrOTuLg=="],
|
|
||||||
|
|
||||||
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
||||||
|
|
||||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
@@ -498,6 +482,8 @@
|
|||||||
|
|
||||||
"twemoji-parser": ["twemoji-parser@14.0.0", "", {}, "sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA=="],
|
"twemoji-parser": ["twemoji-parser@14.0.0", "", {}, "sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA=="],
|
||||||
|
|
||||||
|
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
|
||||||
|
|
||||||
"typescript-telegram-bot-api": ["typescript-telegram-bot-api@0.16.0", "", { "dependencies": { "axios": "^1.7.7", "form-data": "^4.0.1" } }, "sha512-NaAjXucQiZ87U8La/IMaWDOghbMlJzfMbU4rG8ppFpgPOvEwat/zfN5BM+J2QDvKVGN87qJ+1nELnkm3ctSLnQ=="],
|
"typescript-telegram-bot-api": ["typescript-telegram-bot-api@0.16.0", "", { "dependencies": { "axios": "^1.7.7", "form-data": "^4.0.1" } }, "sha512-NaAjXucQiZ87U8La/IMaWDOghbMlJzfMbU4rG8ppFpgPOvEwat/zfN5BM+J2QDvKVGN87qJ+1nELnkm3ctSLnQ=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
|
"undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
|
||||||
|
|||||||
@@ -128,6 +128,7 @@
|
|||||||
"getImageGenDoneText.default": "👨🎨 Image generated.",
|
"getImageGenDoneText.default": "👨🎨 Image generated.",
|
||||||
"getErrorText.withReason": "{errorText} Reason:\n{reason}",
|
"getErrorText.withReason": "{errorText} Reason:\n{reason}",
|
||||||
"getUseToolText.python": "👨💻 Running `Python`",
|
"getUseToolText.python": "👨💻 Running `Python`",
|
||||||
|
"getUseToolText.codeInterpreter": "👨💻 Running `Code Interpreter`",
|
||||||
"getUseToolText.default": "🔧 Using tool `{name}`",
|
"getUseToolText.default": "🔧 Using tool `{name}`",
|
||||||
"getAnalyzingDocumentText.default": "🔍 Analyzing the document...",
|
"getAnalyzingDocumentText.default": "🔍 Analyzing the document...",
|
||||||
"getAnalyzingDocumentText.single": "🔍 Analyzing document: `{name}`",
|
"getAnalyzingDocumentText.single": "🔍 Analyzing document: `{name}`",
|
||||||
|
|||||||
+2
-1
@@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"providerChoice.default": "По умолчанию",
|
"providerChoice.default": "По умолчанию",
|
||||||
"errorText": "⚠️ Произошла ошибка.",
|
"errorText": "⚠️ Произошла ошибка.",
|
||||||
"waitThinkText": "⏳ Думаю...",
|
"waitThinkText": "⏳ Дайте-ка подумать...",
|
||||||
"analyzingPictureText": "🔍 Анализирую изображение...",
|
"analyzingPictureText": "🔍 Анализирую изображение...",
|
||||||
"analyzingPicturesText": "🔍 Анализирую изображения...",
|
"analyzingPicturesText": "🔍 Анализирую изображения...",
|
||||||
"reasoningText": "🤔 Рассуждаю...",
|
"reasoningText": "🤔 Рассуждаю...",
|
||||||
@@ -154,6 +154,7 @@
|
|||||||
"getImageGenDoneText.default": "👨🎨 Изображение сгенерировано.",
|
"getImageGenDoneText.default": "👨🎨 Изображение сгенерировано.",
|
||||||
"getErrorText.withReason": "{errorText} Причина:\n{reason}",
|
"getErrorText.withReason": "{errorText} Причина:\n{reason}",
|
||||||
"getUseToolText.python": "👨💻 Запускаю `Python`",
|
"getUseToolText.python": "👨💻 Запускаю `Python`",
|
||||||
|
"getUseToolText.codeInterpreter": "👨💻 Запускаю `Code Interpreter`",
|
||||||
"getUseToolText.default": "🔧 Использую инструмент `{name}`",
|
"getUseToolText.default": "🔧 Использую инструмент `{name}`",
|
||||||
"getAnalyzingDocumentText.default": "🔍 Анализирую документ...",
|
"getAnalyzingDocumentText.default": "🔍 Анализирую документ...",
|
||||||
"getAnalyzingDocumentText.single": "🔍 Анализирую документ: `{name}`",
|
"getAnalyzingDocumentText.single": "🔍 Анализирую документ: `{name}`",
|
||||||
|
|||||||
+2
-1
@@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"providerChoice.default": "За замовчуванням",
|
"providerChoice.default": "За замовчуванням",
|
||||||
"errorText": "⚠️ Сталася помилка.",
|
"errorText": "⚠️ Сталася помилка.",
|
||||||
"waitThinkText": "⏳ Думаю...",
|
"waitThinkText": "⏳ Дайте-но подумати...",
|
||||||
"analyzingPictureText": "🔍 Аналізую зображення...",
|
"analyzingPictureText": "🔍 Аналізую зображення...",
|
||||||
"analyzingPicturesText": "🔍 Аналізую зображення...",
|
"analyzingPicturesText": "🔍 Аналізую зображення...",
|
||||||
"reasoningText": "🤔 Міркую...",
|
"reasoningText": "🤔 Міркую...",
|
||||||
@@ -153,6 +153,7 @@
|
|||||||
"getImageGenDoneText.default": "👨🎨 Зображення згенеровано.",
|
"getImageGenDoneText.default": "👨🎨 Зображення згенеровано.",
|
||||||
"getErrorText.withReason": "{errorText} Причина:\n{reason}",
|
"getErrorText.withReason": "{errorText} Причина:\n{reason}",
|
||||||
"getUseToolText.python": "👨💻 Запускаю `Python`",
|
"getUseToolText.python": "👨💻 Запускаю `Python`",
|
||||||
|
"getUseToolText.codeInterpreter": "👨💻 Запускаю `Code Interpreter`",
|
||||||
"getUseToolText.default": "🔧 Використовую інструмент `{name}`",
|
"getUseToolText.default": "🔧 Використовую інструмент `{name}`",
|
||||||
"getAnalyzingDocumentText.default": "🔍 Аналізую документ...",
|
"getAnalyzingDocumentText.default": "🔍 Аналізую документ...",
|
||||||
"getAnalyzingDocumentText.single": "🔍 Аналізую документ: `{name}`",
|
"getAnalyzingDocumentText.single": "🔍 Аналізую документ: `{name}`",
|
||||||
|
|||||||
Generated
+16
-119
@@ -31,8 +31,8 @@
|
|||||||
"@types/fluent-ffmpeg": "^2.1.28",
|
"@types/fluent-ffmpeg": "^2.1.28",
|
||||||
"@types/node": "^25.6.1",
|
"@types/node": "^25.6.1",
|
||||||
"@types/qrcode": "^1.5.6",
|
"@types/qrcode": "^1.5.6",
|
||||||
"@typescript/native-preview": "^7.0.0-beta",
|
"drizzle-kit": "^0.31.10",
|
||||||
"drizzle-kit": "^0.31.10"
|
"typescript": "^6.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@drizzle-team/brocli": {
|
"node_modules/@drizzle-team/brocli": {
|
||||||
@@ -2025,123 +2025,6 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript/native-preview": {
|
|
||||||
"version": "7.0.0-dev.20260421.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20260421.2.tgz",
|
|
||||||
"integrity": "sha512-CmajHI25HpVWE9R1XFoxr+cphJPxoYD3eFioQtAvXYkMFKnLdICMS9pXre9Pybizb75ejRxjKD5/CVG055rEIg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"bin": {
|
|
||||||
"tsgo": "bin/tsgo.js"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260421.2",
|
|
||||||
"@typescript/native-preview-darwin-x64": "7.0.0-dev.20260421.2",
|
|
||||||
"@typescript/native-preview-linux-arm": "7.0.0-dev.20260421.2",
|
|
||||||
"@typescript/native-preview-linux-arm64": "7.0.0-dev.20260421.2",
|
|
||||||
"@typescript/native-preview-linux-x64": "7.0.0-dev.20260421.2",
|
|
||||||
"@typescript/native-preview-win32-arm64": "7.0.0-dev.20260421.2",
|
|
||||||
"@typescript/native-preview-win32-x64": "7.0.0-dev.20260421.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@typescript/native-preview-darwin-arm64": {
|
|
||||||
"version": "7.0.0-dev.20260421.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20260421.2.tgz",
|
|
||||||
"integrity": "sha512-fHv1r3ZmVo6zxuAIFmuX3w9QxbcauoG0SsWhmDwm6VmRubLlOJIcmTtlmV3JAb9oOnq8LuzZljzT7Q39fSMQDw==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@typescript/native-preview-darwin-x64": {
|
|
||||||
"version": "7.0.0-dev.20260421.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20260421.2.tgz",
|
|
||||||
"integrity": "sha512-KWTR6xbW9t+JS7D5DQIzo75pqVXVWUxF9PMv/+S6xsnOjCVd6g0ixHcFpFMJMKSUQpGPr8Z5f7b8ks6LHW01jg==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@typescript/native-preview-linux-arm": {
|
|
||||||
"version": "7.0.0-dev.20260421.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20260421.2.tgz",
|
|
||||||
"integrity": "sha512-BWLQO3nemLDSV5PoE5GPHe1dU9Dth77Kv8/cle9Ujcp4LhPo0KincdPqFH/qKeU/xvW25mgFueflZ1nc4rKuww==",
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@typescript/native-preview-linux-arm64": {
|
|
||||||
"version": "7.0.0-dev.20260421.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20260421.2.tgz",
|
|
||||||
"integrity": "sha512-VLMEuml3BhUb+jaL0TXQ4xvVODxJF+RhkI+tBWvlynsJI4khTXEiwWh+wPOJrsfBRYFRMXEu28Odl/HXkYze8w==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@typescript/native-preview-linux-x64": {
|
|
||||||
"version": "7.0.0-dev.20260421.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20260421.2.tgz",
|
|
||||||
"integrity": "sha512-qUrJWTB5/wv4wnRG0TRXElAxc2kykNiRNyEIEqBbLmzDlrcvAW7RRy8MXoY1ZyTiKGMu14itZ3x9oW6+blFpRw==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@typescript/native-preview-win32-arm64": {
|
|
||||||
"version": "7.0.0-dev.20260421.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20260421.2.tgz",
|
|
||||||
"integrity": "sha512-Rc6NsWlZmCs5YUKVzKgwoBOoRUGsPzct4BDMRX0csD1devLBBc4AbUXWKsJRbpwIAnqMO1ld4sNHEb+wXgfNHQ==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@typescript/native-preview-win32-x64": {
|
|
||||||
"version": "7.0.0-dev.20260421.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20260421.2.tgz",
|
|
||||||
"integrity": "sha512-GQv1+dya1t6EqF2Cpsb+xoozovdX10JUSf6Kl/8xNkTapzmlHd+uMr+8ku3jIASTxoRGn0Mklgjj3MDKrOTuLg==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/agent-base": {
|
"node_modules/agent-base": {
|
||||||
"version": "7.1.4",
|
"version": "7.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||||
@@ -4007,6 +3890,20 @@
|
|||||||
"integrity": "sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA==",
|
"integrity": "sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/typescript-telegram-bot-api": {
|
"node_modules/typescript-telegram-bot-api": {
|
||||||
"version": "0.16.0",
|
"version": "0.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/typescript-telegram-bot-api/-/typescript-telegram-bot-api-0.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/typescript-telegram-bot-api/-/typescript-telegram-bot-api-0.16.0.tgz",
|
||||||
|
|||||||
+1
-1
@@ -33,6 +33,6 @@
|
|||||||
"@types/node": "^25.6.1",
|
"@types/node": "^25.6.1",
|
||||||
"@types/qrcode": "^1.5.6",
|
"@types/qrcode": "^1.5.6",
|
||||||
"drizzle-kit": "^0.31.10",
|
"drizzle-kit": "^0.31.10",
|
||||||
"@typescript/native-preview": "^7.0.0-beta"
|
"typescript": "^6.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const PURPOSE_SUFFIXES: Record<AiRuntimePurpose, string[]> = {
|
|||||||
thinking: ["THINKING", "THINK"],
|
thinking: ["THINKING", "THINK"],
|
||||||
extendedThinking: ["EXTENDED_THINKING", "THINKING", "THINK"],
|
extendedThinking: ["EXTENDED_THINKING", "THINKING", "THINK"],
|
||||||
tools: ["TOOLS", "CHAT"],
|
tools: ["TOOLS", "CHAT"],
|
||||||
|
toolRank: ["TOOL_RANK", "TOOL_RANKER"],
|
||||||
audio: ["AUDIO"],
|
audio: ["AUDIO"],
|
||||||
documents: ["DOCUMENTS", "RAG", "EMBEDDING"],
|
documents: ["DOCUMENTS", "RAG", "EMBEDDING"],
|
||||||
outputImages: ["OUTPUT_IMAGES", "IMAGE"],
|
outputImages: ["OUTPUT_IMAGES", "IMAGE"],
|
||||||
|
|||||||
@@ -111,6 +111,12 @@ function isOpenAiReasoningModel(model: string): boolean {
|
|||||||
return /^o\d/.test(name) || name.startsWith("gpt-5");
|
return /^o\d/.test(name) || name.startsWith("gpt-5");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: OpenAI image input rollout
|
||||||
|
// 1. Keep OpenAI chat payload building `input_image` parts from `MessagePart.imageParts`.
|
||||||
|
// 2. Replace name-only vision detection with a dedicated OpenAI vision-capability helper.
|
||||||
|
// 3. Use allowlist/denylist heuristics for `vision` and `ocr`, with a probe/cache fallback later if needed.
|
||||||
|
// 4. Gate `rejectUnsupportedAttachments()` on the resolved vision capability, not on `OPENAI_IMAGE_MODEL`.
|
||||||
|
// 5. Add tests for supported/unsupported model names and the resulting `input_image` payload shape.
|
||||||
function isOpenAiVisionModel(model: string): boolean {
|
function isOpenAiVisionModel(model: string): boolean {
|
||||||
const name = lowerModelName(model);
|
const name = lowerModelName(model);
|
||||||
if (!isOpenAiTextModel(model)) return false;
|
if (!isOpenAiTextModel(model)) return false;
|
||||||
|
|||||||
+31
-2
@@ -1,6 +1,8 @@
|
|||||||
import {AiTool} from "./tool-types";
|
import {AiTool} from "./tool-types";
|
||||||
import {AiProvider} from "../model/ai-provider";
|
import {AiProvider} from "../model/ai-provider";
|
||||||
import {getTools} from "./tools/registry";
|
import {getTools} from "./tools/registry";
|
||||||
|
import {WEB_SEARCH_TOOL_NAME} from "./tools/brave-search";
|
||||||
|
import {PYTHON_INTERPRETER_TOOL_NAME} from "./tools/python-interpretator";
|
||||||
|
|
||||||
export type AiProviderName = "ollama" | "openai" | "gemini" | "mistral";
|
export type AiProviderName = "ollama" | "openai" | "gemini" | "mistral";
|
||||||
|
|
||||||
@@ -8,8 +10,17 @@ export function getOllamaTools(): AiTool[] {
|
|||||||
return getTools();
|
return getTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openAiForbiddenTools = [
|
||||||
|
WEB_SEARCH_TOOL_NAME,
|
||||||
|
PYTHON_INTERPRETER_TOOL_NAME
|
||||||
|
]
|
||||||
|
|
||||||
|
function allowedOpenAiTool(tool: AiTool): boolean {
|
||||||
|
return !openAiForbiddenTools.includes(tool.function.name)
|
||||||
|
}
|
||||||
|
|
||||||
export function getOpenAITools(): AiTool[] {
|
export function getOpenAITools(): AiTool[] {
|
||||||
return getTools().map(tool => ({
|
return getTools().filter(allowedOpenAiTool).map(tool => ({
|
||||||
type: "function",
|
type: "function",
|
||||||
function: tool.function,
|
function: tool.function,
|
||||||
}));
|
}));
|
||||||
@@ -23,8 +34,17 @@ export type OpenAiResponseTool = {
|
|||||||
strict: false;
|
strict: false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type OpenAiCodeInterpreterTool = {
|
||||||
|
type: "code_interpreter";
|
||||||
|
container: {
|
||||||
|
type: "auto";
|
||||||
|
file_ids?: string[];
|
||||||
|
memory_limit?: "1g" | "4g" | "16g" | "64g" | null;
|
||||||
|
} | string;
|
||||||
|
};
|
||||||
|
|
||||||
export function getOpenAIResponsesTools(): OpenAiResponseTool[] {
|
export function getOpenAIResponsesTools(): OpenAiResponseTool[] {
|
||||||
return getTools().map(tool => ({
|
return getTools().filter(allowedOpenAiTool).map(tool => ({
|
||||||
type: "function",
|
type: "function",
|
||||||
name: tool.function.name,
|
name: tool.function.name,
|
||||||
description: tool.function.description,
|
description: tool.function.description,
|
||||||
@@ -33,6 +53,15 @@ export function getOpenAIResponsesTools(): OpenAiResponseTool[] {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getOpenAICodeInterpreterTool(): OpenAiCodeInterpreterTool {
|
||||||
|
return {
|
||||||
|
type: "code_interpreter",
|
||||||
|
container: {
|
||||||
|
type: "auto",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function getMistralTools(): AiTool[] {
|
export function getMistralTools(): AiTool[] {
|
||||||
return getTools().map(tool => ({
|
return getTools().map(tool => ({
|
||||||
type: "function",
|
type: "function",
|
||||||
|
|||||||
@@ -90,10 +90,12 @@ type BraveSearchApiResponse = {
|
|||||||
summarizer?: unknown;
|
summarizer?: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const WEB_SEARCH_TOOL_NAME = "web_search";
|
||||||
|
|
||||||
export const braveSearchTool = {
|
export const braveSearchTool = {
|
||||||
type: "function",
|
type: "function",
|
||||||
function: {
|
function: {
|
||||||
name: "web_search",
|
name: WEB_SEARCH_TOOL_NAME,
|
||||||
description:
|
description:
|
||||||
"Search the web using Brave Search API. Use this for current information, facts, documentation, news, products, recent events, source lookup, and general web search. Returns ranked web/news/video results with titles, URLs and snippets.",
|
"Search the web using Brave Search API. Use this for current information, facts, documentation, news, products, recent events, source lookup, and general web search. Returns ranked web/news/video results with titles, URLs and snippets.",
|
||||||
parameters: {
|
parameters: {
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export async function listNotes(): Promise<ListNotesResult> {
|
|||||||
const markdownFiles = entries
|
const markdownFiles = entries
|
||||||
.filter((entry) => entry.isFile())
|
.filter((entry) => entry.isFile())
|
||||||
.map((entry) => entry.name)
|
.map((entry) => entry.name)
|
||||||
.filter((fileName) => fileName.endsWith(".md"));
|
.filter((fileName) => fileName.endsWith(".md") && !fileName.startsWith("index"));
|
||||||
|
|
||||||
const notes: NoteListItem[] = await Promise.all(
|
const notes: NoteListItem[] = await Promise.all(
|
||||||
markdownFiles.map(async (fileName) => {
|
markdownFiles.map(async (fileName) => {
|
||||||
@@ -115,6 +115,10 @@ export async function getNoteContent(
|
|||||||
return {success: false, error: "No file name provided"};
|
return {success: false, error: "No file name provided"};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fileName.trim().includes("index")) {
|
||||||
|
return {success: false, error: "It is forbidden to access `index.md`"};
|
||||||
|
}
|
||||||
|
|
||||||
const noteFilePath = buildSafeNoteFilePath(fileName);
|
const noteFilePath = buildSafeNoteFilePath(fileName);
|
||||||
if (!noteFilePath) {
|
if (!noteFilePath) {
|
||||||
return {success: false, error: "Invalid or unsafe file name provided"};
|
return {success: false, error: "Invalid or unsafe file name provided"};
|
||||||
@@ -125,7 +129,12 @@ export async function getNoteContent(
|
|||||||
const normalizedFileName = path.basename(noteFilePath);
|
const normalizedFileName = path.basename(noteFilePath);
|
||||||
const relativePath = path.relative(path.dirname(notesRootFile), noteFilePath);
|
const relativePath = path.relative(path.dirname(notesRootFile), noteFilePath);
|
||||||
|
|
||||||
logger.debug("get_content.done", {fileName: normalizedFileName, relativePath, chars: content.length, duration: logger.duration(startedAt)});
|
logger.debug("get_content.done", {
|
||||||
|
fileName: normalizedFileName,
|
||||||
|
relativePath,
|
||||||
|
chars: content.length,
|
||||||
|
duration: logger.duration(startedAt)
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
fileName: normalizedFileName,
|
fileName: normalizedFileName,
|
||||||
@@ -210,14 +219,14 @@ export const deleteNoteTool = {
|
|||||||
type: "function",
|
type: "function",
|
||||||
function: {
|
function: {
|
||||||
name: "delete_note",
|
name: "delete_note",
|
||||||
description: "Delete an existing Markdown note by its file name and remove its link from the notes root file if present.",
|
description: "Delete an existing Markdown note by its file name and remove its link from the notes root file if present. It is forbidden to delete/edit/rename `index.md` note.",
|
||||||
parameters: {
|
parameters: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
fileName: {
|
fileName: {
|
||||||
type: "string",
|
type: "string",
|
||||||
description:
|
description:
|
||||||
"The file name of the note to delete. It may be provided with or without the .md extension. Must not contain forbidden or unsafe characters such as /, \\, :, *, ?, \", <, >, |, or control characters.",
|
"The file name of the note to delete. It may be provided with or without the .md extension. Must not contain forbidden or unsafe characters such as /, \\, :, *, ?, \", <, >, |, or control characters. It is forbidden to delete/edit/rename `index.md` note.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
required: ["fileName"],
|
required: ["fileName"],
|
||||||
@@ -236,6 +245,10 @@ export async function updateNoteContent(
|
|||||||
return {success: false, error: "No file name provided"};
|
return {success: false, error: "No file name provided"};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fileName.trim().includes("index")) {
|
||||||
|
return {success: false, error: "It is forbidden to edit `index.md`"};
|
||||||
|
}
|
||||||
|
|
||||||
const content = asNonEmptyString(args?.content) ?? "";
|
const content = asNonEmptyString(args?.content) ?? "";
|
||||||
if (!content.trim().length) {
|
if (!content.trim().length) {
|
||||||
return {success: false, error: "No content provided"};
|
return {success: false, error: "No content provided"};
|
||||||
@@ -249,7 +262,12 @@ export async function updateNoteContent(
|
|||||||
try {
|
try {
|
||||||
await readFile(noteFilePath, "utf-8");
|
await readFile(noteFilePath, "utf-8");
|
||||||
await writeFile(noteFilePath, content, "utf-8");
|
await writeFile(noteFilePath, content, "utf-8");
|
||||||
logger.debug("update_content.done", {fileName, filePath: noteFilePath, chars: content.length, duration: logger.duration(startedAt)});
|
logger.debug("update_content.done", {
|
||||||
|
fileName,
|
||||||
|
filePath: noteFilePath,
|
||||||
|
chars: content.length,
|
||||||
|
duration: logger.duration(startedAt)
|
||||||
|
});
|
||||||
|
|
||||||
return {success: true, filePath: noteFilePath};
|
return {success: true, filePath: noteFilePath};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -270,6 +288,10 @@ export async function deleteNote(
|
|||||||
return {success: false, error: "No file name provided"};
|
return {success: false, error: "No file name provided"};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fileName.trim().includes("index")) {
|
||||||
|
return {success: false, error: "It is forbidden to delete `index.md`"};
|
||||||
|
}
|
||||||
|
|
||||||
const noteFilePath = buildSafeNoteFilePath(fileName);
|
const noteFilePath = buildSafeNoteFilePath(fileName);
|
||||||
if (!noteFilePath) {
|
if (!noteFilePath) {
|
||||||
return {success: false, error: "Invalid or unsafe file name provided"};
|
return {success: false, error: "Invalid or unsafe file name provided"};
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import {toolsLogger} from "./tool-logger";
|
|||||||
|
|
||||||
const logger = toolsLogger.child("market-rates");
|
const logger = toolsLogger.child("market-rates");
|
||||||
|
|
||||||
export const getMarketRatesTool = {
|
export const GET_FINANCIAL_MARKET_DATA = "get_financial_market_data";
|
||||||
|
|
||||||
|
export const getFinancialMarketData = {
|
||||||
type: "function",
|
type: "function",
|
||||||
function: {
|
function: {
|
||||||
name: "get_market_rates",
|
name: GET_FINANCIAL_MARKET_DATA,
|
||||||
description:
|
description:
|
||||||
"Retrieve the latest exchange rates for supported currency, crypto, and precious metal pairs, including 24-hour change data when available. Supported pairs: USD/RUB, USD/EUR, USD/KZT, USD/UAH, USD/BYN, USD/GBP, USD/CNY, TON/USD, BTC/USD, ETH/USD, SOL/USD, and XAU/USD. Use this tool when the user asks for current rates, currency conversion, crypto prices, gold price, or recent 24-hour movement. This tool takes no parameters.",
|
"Retrieve the latest exchange rates for supported currency, crypto, and precious metal pairs, including 24-hour change data when available. Supported pairs: USD/RUB, USD/EUR, USD/KZT, USD/UAH, USD/BYN, USD/GBP, USD/CNY, TON/USD, BTC/USD, ETH/USD, SOL/USD, and XAU/USD. Use this tool when the user asks for current rates, currency conversion, crypto prices, gold price, or recent 24-hour movement. This tool takes no parameters.",
|
||||||
parameters: {
|
parameters: {
|
||||||
@@ -18,11 +20,11 @@ export const getMarketRatesTool = {
|
|||||||
},
|
},
|
||||||
} satisfies AiTool;
|
} satisfies AiTool;
|
||||||
|
|
||||||
export const marketRatesToolPrompt = [
|
export const financialMarketDataToolPrompt = [
|
||||||
"Currency rates tool rules:",
|
"Currency rates tool rules:",
|
||||||
"- Use `get_market_rates` whenever the answer depends on current exchange rates, crypto prices, or gold price.",
|
`- Use \`${GET_FINANCIAL_MARKET_DATA}\` whenever the answer depends on current exchange rates, crypto prices, or gold price.`,
|
||||||
"- Use `get_market_rates` when the user asks whether a supported asset went up or down recently.",
|
`- Use \`${GET_FINANCIAL_MARKET_DATA}\` when the user asks whether a supported asset went up or down recently.`,
|
||||||
"- Use `get_market_rates` when the user asks for the 24-hour change, percentage change, or movement direction for a supported pair.",
|
`- Use \`${GET_FINANCIAL_MARKET_DATA}\` when the user asks for the 24-hour change, percentage change, or movement direction for a supported pair.`,
|
||||||
"- Never guess current rates, prices, or 24-hour changes. Call the tool first.",
|
"- Never guess current rates, prices, or 24-hour changes. Call the tool first.",
|
||||||
"- Do not use this tool for unsupported pairs unless the user asks about one of the supported pairs listed below.",
|
"- Do not use this tool for unsupported pairs unless the user asks about one of the supported pairs listed below.",
|
||||||
"- Do not use this tool for historical rates beyond the provided 24-hour comparison.",
|
"- Do not use this tool for historical rates beyond the provided 24-hour comparison.",
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import {spawn} from "node:child_process";
|
import {spawn} from "node:child_process";
|
||||||
import {copyFile, lstat, mkdir, readdir, writeFile} from "node:fs/promises";
|
import {copyFile, lstat, mkdir, readdir, rm, writeFile} from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import {AiTool} from "../tool-types";
|
import {AiTool} from "../tool-types";
|
||||||
import {Environment} from "../../common/environment";
|
import {Environment} from "../../common/environment";
|
||||||
import {randomUUID} from "node:crypto";
|
|
||||||
import {toolsLogger} from "./tool-logger";
|
import {toolsLogger} from "./tool-logger";
|
||||||
|
import {randomUUID} from "node:crypto";
|
||||||
|
|
||||||
const logger = toolsLogger.child("python-interpreter");
|
const logger = toolsLogger.child("python-interpreter");
|
||||||
|
|
||||||
@@ -300,13 +300,12 @@ async function executePythonCode(
|
|||||||
logger.info("execute.start", {args, options});
|
logger.info("execute.start", {args, options});
|
||||||
|
|
||||||
const pythonBinary =
|
const pythonBinary =
|
||||||
options.pythonBinary ?? process.env.PYTHON_INTERPRETER_BINARY ?? "C:\\Users\\meloda\\Desktop\\AI_BOT\\.venv\\Scripts\\python.exe";
|
options.pythonBinary ?? process.env.PYTHON_INTERPRETER_BINARY ?? "python";
|
||||||
|
|
||||||
const timeoutMs = args.timeoutMs ?? options.executionTimeoutMs ?? DEFAULT_EXECUTION_TIMEOUT_MS;
|
const timeoutMs = args.timeoutMs ?? options.executionTimeoutMs ?? DEFAULT_EXECUTION_TIMEOUT_MS;
|
||||||
const maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
const maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
||||||
|
|
||||||
// const tempDir = path.join(Environment.DATA_PATH, "cache", "python", "python-temp-" + randomUUID());
|
const tempDir = path.join(Environment.DATA_PATH, "cache", "python", "python-temp-" + randomUUID());
|
||||||
const tempDir = path.join(Environment.FILE_TOOLS_ROOT_DIR ?? ".", "ollama-python-temp-" + randomUUID());
|
|
||||||
const inputDir = path.join(tempDir, PYTHON_INPUTS_DIR_NAME);
|
const inputDir = path.join(tempDir, PYTHON_INPUTS_DIR_NAME);
|
||||||
const outputDir = path.join(tempDir, PYTHON_OUTPUTS_DIR_NAME);
|
const outputDir = path.join(tempDir, PYTHON_OUTPUTS_DIR_NAME);
|
||||||
const attachmentsPath = path.join(tempDir, PYTHON_ATTACHMENTS_FILE_NAME);
|
const attachmentsPath = path.join(tempDir, PYTHON_ATTACHMENTS_FILE_NAME);
|
||||||
@@ -350,7 +349,12 @@ async function executePythonCode(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.debug("process.done", {duration: logger.duration(startedAt), exitCode: result.exitCode, timedOut: result.timedOut, outputTruncated: result.outputTruncated});
|
logger.debug("process.done", {
|
||||||
|
duration: logger.duration(startedAt),
|
||||||
|
exitCode: result.exitCode,
|
||||||
|
timedOut: result.timedOut,
|
||||||
|
outputTruncated: result.outputTruncated
|
||||||
|
});
|
||||||
|
|
||||||
if (result.timedOut) {
|
if (result.timedOut) {
|
||||||
logger.warn("process.timeout", {duration: logger.duration(startedAt)});
|
logger.warn("process.timeout", {duration: logger.duration(startedAt)});
|
||||||
@@ -369,7 +373,11 @@ async function executePythonCode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result.outputTruncated) {
|
if (result.outputTruncated) {
|
||||||
logger.warn("process.output_truncated", {duration: logger.duration(startedAt), stdoutChars: result.stdout.length, stderrChars: result.stderr.length});
|
logger.warn("process.output_truncated", {
|
||||||
|
duration: logger.duration(startedAt),
|
||||||
|
stdoutChars: result.stdout.length,
|
||||||
|
stderrChars: result.stderr.length
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
@@ -431,10 +439,10 @@ async function executePythonCode(
|
|||||||
error: errorToString(error),
|
error: errorToString(error),
|
||||||
};
|
};
|
||||||
} finally {
|
} finally {
|
||||||
// await rm(tempDir, {
|
await rm(tempDir, {
|
||||||
// recursive: true,
|
recursive: true,
|
||||||
// force: true,
|
force: true,
|
||||||
// });
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ import {getCurrentDateTime, getCurrentDateTimeTool} from "./datetime";
|
|||||||
import {shellExecute, shellExecuteTool} from "./shell";
|
import {shellExecute, shellExecuteTool} from "./shell";
|
||||||
import {ToolHandler} from "./types";
|
import {ToolHandler} from "./types";
|
||||||
import {getWeather, getWeatherTool} from "./weather";
|
import {getWeather, getWeatherTool} from "./weather";
|
||||||
import {getMarketRates, getMarketRatesTool} from "./market-rates";
|
import {
|
||||||
|
financialMarketDataToolPrompt,
|
||||||
|
GET_FINANCIAL_MARKET_DATA,
|
||||||
|
getFinancialMarketData,
|
||||||
|
getMarketRates
|
||||||
|
} from "./market-rates";
|
||||||
import {pythonInterpreterTool, runPythonInterpreter} from "./python-interpretator";
|
import {pythonInterpreterTool, runPythonInterpreter} from "./python-interpretator";
|
||||||
import {
|
import {
|
||||||
copyPath,
|
copyPath,
|
||||||
@@ -36,19 +41,19 @@ import {
|
|||||||
updateNoteContent,
|
updateNoteContent,
|
||||||
updateNoteContentTool
|
updateNoteContentTool
|
||||||
} from "./list-notes";
|
} from "./list-notes";
|
||||||
import {getNoteFile, getNoteFileTool} from "./send-note-file";
|
import {sendNoteAsFileTool, sendNoteAsFile} from "./send-note-as-file";
|
||||||
import {searchNotes, searchNotesTool} from "./search-notes";
|
import {searchNotes, searchNotesTool} from "./search-notes";
|
||||||
|
|
||||||
export const getTools = () => {
|
export const getTools = () => {
|
||||||
const tools: AiTool[] = [
|
const tools: AiTool[] = [
|
||||||
getCurrentDateTimeTool,
|
getCurrentDateTimeTool,
|
||||||
getMarketRatesTool,
|
getFinancialMarketData,
|
||||||
createNoteTool,
|
createNoteTool,
|
||||||
listNotesTool,
|
listNotesTool,
|
||||||
getNoteContentTool,
|
getNoteContentTool,
|
||||||
updateNoteContentTool,
|
updateNoteContentTool,
|
||||||
deleteNoteTool,
|
deleteNoteTool,
|
||||||
getNoteFileTool,
|
sendNoteAsFileTool,
|
||||||
searchNotesTool
|
searchNotesTool
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -97,13 +102,13 @@ export const getTools = () => {
|
|||||||
export const getToolHandlers = () => {
|
export const getToolHandlers = () => {
|
||||||
let handlers: Record<string, ToolHandler> = {
|
let handlers: Record<string, ToolHandler> = {
|
||||||
get_datetime: getCurrentDateTime,
|
get_datetime: getCurrentDateTime,
|
||||||
get_market_rates: getMarketRates,
|
get_financial_market_data: getMarketRates,
|
||||||
create_note: createNote,
|
create_note: createNote,
|
||||||
list_notes: listNotes,
|
list_notes: listNotes,
|
||||||
get_note_content: getNoteContent,
|
get_note_content: getNoteContent,
|
||||||
update_note_content: updateNoteContent,
|
update_note_content: updateNoteContent,
|
||||||
delete_note: deleteNote,
|
delete_note: deleteNote,
|
||||||
get_note_file: getNoteFile,
|
send_note_as_file: sendNoteAsFile,
|
||||||
search_notes: searchNotes
|
search_notes: searchNotes
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -151,3 +156,19 @@ export const getToolHandlers = () => {
|
|||||||
|
|
||||||
return handlers;
|
return handlers;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getToolPrompts(toolNames: string[]): string[] {
|
||||||
|
const prompts: string[] = [];
|
||||||
|
|
||||||
|
for (const toolName of toolNames) {
|
||||||
|
switch (toolName) {
|
||||||
|
case GET_FINANCIAL_MARKET_DATA:
|
||||||
|
prompts.push(financialMarketDataToolPrompt);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prompts;
|
||||||
|
}
|
||||||
@@ -44,10 +44,10 @@ export const GetNoteFileResultSchema = z.discriminatedUnion("success", [
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const getNoteFileTool = {
|
export const sendNoteAsFileTool = {
|
||||||
type: "function",
|
type: "function",
|
||||||
function: {
|
function: {
|
||||||
name: "get_note_file",
|
name: "send_note_as_file",
|
||||||
description:
|
description:
|
||||||
"Prepare a Markdown note file to be sent to the user as a .md attachment. Returns a local file descriptor that the host application should use to upload or send the file.",
|
"Prepare a Markdown note file to be sent to the user as a .md attachment. Returns a local file descriptor that the host application should use to upload or send the file.",
|
||||||
parameters: {
|
parameters: {
|
||||||
@@ -64,7 +64,7 @@ export const getNoteFileTool = {
|
|||||||
},
|
},
|
||||||
} satisfies AiTool;
|
} satisfies AiTool;
|
||||||
|
|
||||||
export async function getNoteFile(
|
export async function sendNoteAsFile(
|
||||||
args?: Record<string, unknown>,
|
args?: Record<string, unknown>,
|
||||||
): Promise<GetNoteFileResult> {
|
): Promise<GetNoteFileResult> {
|
||||||
logger.debug("start", {args});
|
logger.debug("start", {args});
|
||||||
@@ -6,7 +6,7 @@ export const shellExecuteTool = {
|
|||||||
type: "function",
|
type: "function",
|
||||||
function: {
|
function: {
|
||||||
name: "shell_execute",
|
name: "shell_execute",
|
||||||
description: "Execute command in a shell",
|
description: "Execute NON-Python command in a shell. Do not use if you intend to execute some python.",
|
||||||
parameters: {
|
parameters: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {Ollama} from "ollama";
|
import {Ollama} from "ollama";
|
||||||
import {z} from "zod";
|
|
||||||
import {toolsLogger} from "./tool-logger";
|
import {toolsLogger} from "./tool-logger";
|
||||||
|
|
||||||
const logger = toolsLogger.child("utils");
|
const logger = toolsLogger.child("utils");
|
||||||
@@ -106,25 +105,3 @@ export async function loadOllamaModel(model: string, ollama: Ollama, contextLeng
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ToolPlanStep = {
|
|
||||||
t: string;
|
|
||||||
h: string;
|
|
||||||
from: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RouterPlan = {
|
|
||||||
s: ToolPlanStep[];
|
|
||||||
m: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ToolPlanStepSchema = z.object({
|
|
||||||
t: z.string(),
|
|
||||||
h: z.string(),
|
|
||||||
from: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const RouterPlanSchema = z.object({
|
|
||||||
s: z.array(ToolPlanStepSchema),
|
|
||||||
m: z.string()
|
|
||||||
});
|
|
||||||
@@ -2,27 +2,48 @@
|
|||||||
import {Message} from "typescript-telegram-bot-api";
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
import * as fs from "node:fs";
|
import * as fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import {AiProvider} from "../model/ai-provider";
|
|
||||||
import {Environment} from "../common/environment";
|
import {Environment} from "../common/environment";
|
||||||
import {bot, notesDir} from "../index";
|
import {bot, notesDir} from "../index";
|
||||||
import {clamp, logError} from "../util/utils";
|
import {clamp, logError} from "../util/utils";
|
||||||
import {getOllamaTools} from "./tool-mappers";
|
import {getOllamaTools} from "./tool-mappers";
|
||||||
import {TelegramStreamMessage} from "./telegram-stream-message";
|
import {TelegramStreamMessage} from "./telegram-stream-message";
|
||||||
import {getModelCapabilities} from "./provider-model-runtime";
|
|
||||||
import {ChatMessage} from "./chat-messages-types";
|
import {ChatMessage} from "./chat-messages-types";
|
||||||
import {ChatRequest, Tool} from "ollama";
|
import {ChatRequest, Tool} from "ollama";
|
||||||
import {ToolRuntimeContext} from "./tools/runtime";
|
import {ToolRuntimeContext} from "./tools/runtime";
|
||||||
import {enqueueTelegramApiCall} from "../util/telegram-api-queue";
|
import {enqueueTelegramApiCall} from "../util/telegram-api-queue";
|
||||||
import {getCurrentDateTimeTool} from "./tools/datetime";
|
import {getCurrentDateTimeTool} from "./tools/datetime";
|
||||||
import {getMarketRatesTool} from "./tools/market-rates";
|
import {getFinancialMarketData} from "./tools/market-rates";
|
||||||
import {getWeatherTool} from "./tools/weather";
|
import {getWeatherTool} from "./tools/weather";
|
||||||
import {loadOllamaModel, unloadAllOllamaModels} from "./tools/utils";
|
import {loadOllamaModel, unloadAllOllamaModels} from "./tools/utils";
|
||||||
import {createOllamaClient} from "./ai-runtime-target";
|
import {createOllamaClient} from "./ai-runtime-target";
|
||||||
import {GetNoteFileResult, GetNoteFileResultSchema} from "./tools/send-note-file";
|
import {GetNoteFileResult, GetNoteFileResultSchema, sendNoteAsFileTool} from "./tools/send-note-as-file";
|
||||||
import {aiLog, aiLogDuration, aiLogMessageIdentity, aiLogProviderTarget, aiLogToolCall} from "../logging/ai-logger";
|
import {aiLog, aiLogDuration, aiLogMessageIdentity, aiLogProviderTarget, aiLogToolCall} from "../logging/ai-logger";
|
||||||
|
|
||||||
import {DEFAULT_OLLAMA_CONTEXT_SIZE, MAX_OLLAMA_CONTEXT_SIZE, MAX_TOOL_ROUNDS, MIN_OLLAMA_CONTEXT_SIZE, RuntimeConfigSnapshot, Think, ToolCallData, ToolExecutionMemory, allToolSchemaNames, appendOllamaToolResults, dedupeToolCalls, executeToolBatch, normalizeOllamaToolCalls, roundStatus, safeJsonParseObject, isRecord, isOllamaModelActive, OllamaToolCallLike} from "./unified-ai-runner.shared";
|
import {
|
||||||
import {latestUserTextFromOllamaMessages, looksLikeToolRankerJson, OllamaToolRanker} from "./unified-ai-runner.tool-ranker";
|
allToolSchemaNames,
|
||||||
|
appendOllamaToolResults,
|
||||||
|
dedupeToolCalls,
|
||||||
|
DEFAULT_OLLAMA_CONTEXT_SIZE,
|
||||||
|
executeToolBatch,
|
||||||
|
isOllamaModelActive,
|
||||||
|
isRecord,
|
||||||
|
MAX_OLLAMA_CONTEXT_SIZE,
|
||||||
|
MAX_TOOL_ROUNDS,
|
||||||
|
MIN_OLLAMA_CONTEXT_SIZE,
|
||||||
|
normalizeOllamaToolCalls,
|
||||||
|
OllamaToolCallLike,
|
||||||
|
roundStatus,
|
||||||
|
RuntimeConfigSnapshot,
|
||||||
|
safeJsonParseObject,
|
||||||
|
Think,
|
||||||
|
ToolCallData,
|
||||||
|
ToolExecutionMemory
|
||||||
|
} from "./unified-ai-runner.shared";
|
||||||
|
import {latestUserTextFromOllamaMessages, OllamaToolRanker} from "./unified-ai-runner.tool-ranker";
|
||||||
|
import {getToolPrompts} from "./tools/registry";
|
||||||
|
import {createNoteTool} from "./tools/create-note";
|
||||||
|
import {deleteNoteTool, getNoteContentTool, listNotesTool, updateNoteContentTool} from "./tools/list-notes";
|
||||||
|
import {searchNotesTool} from "./tools/search-notes";
|
||||||
|
|
||||||
export async function runOllama(
|
export async function runOllama(
|
||||||
msg: Message,
|
msg: Message,
|
||||||
@@ -156,46 +177,76 @@ export async function runOllama(
|
|||||||
messages: messages,
|
messages: messages,
|
||||||
think: audioCount ? false : think,
|
think: audioCount ? false : think,
|
||||||
options: {
|
options: {
|
||||||
temperature: 0.6,
|
temperature: 0.7,
|
||||||
num_ctx: context,
|
top_p: 0.9,
|
||||||
|
top_k: 40,
|
||||||
|
num_ctx: 16384
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let activeToolNames: string[] = [];
|
let activeToolNames: string[] = [];
|
||||||
if ((await getModelCapabilities(AiProvider.OLLAMA, model, "tools"))?.tools?.supported) {
|
// if ((await getModelCapabilities(AiProvider.OLLAMA, model, "tools"))?.tools?.supported) {
|
||||||
const availableOllamaTools: Tool[] = fromId !== Environment.CREATOR_ID
|
const availableOllamaTools: Tool[] =
|
||||||
? [getCurrentDateTimeTool, getMarketRatesTool, getWeatherTool]
|
fromId !== Environment.CREATOR_ID ? [
|
||||||
: getOllamaTools() as Tool[];
|
getCurrentDateTimeTool,
|
||||||
|
getFinancialMarketData,
|
||||||
|
getWeatherTool,
|
||||||
|
createNoteTool,
|
||||||
|
listNotesTool,
|
||||||
|
getNoteContentTool,
|
||||||
|
updateNoteContentTool,
|
||||||
|
deleteNoteTool,
|
||||||
|
sendNoteAsFileTool,
|
||||||
|
searchNotesTool
|
||||||
|
] : getOllamaTools() as Tool[];
|
||||||
|
|
||||||
aiLog("debug", "ollama.tools.available", {
|
aiLog("debug", "ollama.tools.available", {
|
||||||
round,
|
round,
|
||||||
tools: allToolSchemaNames(availableOllamaTools),
|
tools: allToolSchemaNames(availableOllamaTools),
|
||||||
rankerEnabled: !!config.ollamaToolRankerTarget,
|
rankerEnabled: !!config.ollamaToolRankerTarget,
|
||||||
});
|
});
|
||||||
|
|
||||||
const rankerSelection = await new OllamaToolRanker(config).selectTools({
|
const rankerSelection = await new OllamaToolRanker(config).selectTools({
|
||||||
userQuery: latestUserTextFromOllamaMessages(messages),
|
userQuery: latestUserTextFromOllamaMessages(messages),
|
||||||
availableTools: availableOllamaTools,
|
availableTools: availableOllamaTools,
|
||||||
round,
|
round,
|
||||||
signal,
|
signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
activeToolNames = rankerSelection.selectedNames;
|
activeToolNames = rankerSelection.tools.map(t => t.function.name ?? "");
|
||||||
if (rankerSelection.tools.length > 0) {
|
if (rankerSelection.tools.length > 0) {
|
||||||
request.tools = rankerSelection.tools;
|
request.tools = [...rankerSelection.tools, ...rankerSelection.tools];
|
||||||
} else {
|
request.options = {
|
||||||
delete request.tools;
|
...request.options,
|
||||||
|
temperature: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
aiLog("debug", "ollama.tools.selected", {
|
const newMessage = messages[messages.length - 1];
|
||||||
round,
|
if (newMessage) {
|
||||||
tools: activeToolNames,
|
newMessage.content += "\n" + "Suggested tools to call: " + activeToolNames.join(", ");
|
||||||
count: activeToolNames.length,
|
}
|
||||||
usedRanker: rankerSelection.usedRanker,
|
|
||||||
missing: rankerSelection.missing,
|
const systemMessage = messages.find(m => m.role === "system");
|
||||||
});
|
if (systemMessage) {
|
||||||
|
systemMessage.content += "\n\n" + getToolPrompts(activeToolNames).join("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
request.model = config.ollamaToolTarget.model;
|
||||||
|
} else {
|
||||||
|
delete request.tools;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: 14.05.2026, Danil Nikolaev: check if model supports tools
|
||||||
|
|
||||||
|
|
||||||
|
aiLog("debug", "ollama.tools.selected", {
|
||||||
|
round,
|
||||||
|
tools: activeToolNames,
|
||||||
|
count: activeToolNames.length,
|
||||||
|
usedRanker: rankerSelection.usedRanker,
|
||||||
|
});
|
||||||
|
// }
|
||||||
|
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
const response = await ollama.chat({
|
const response = await ollama.chat({
|
||||||
...request,
|
...request,
|
||||||
@@ -214,14 +265,14 @@ export async function runOllama(
|
|||||||
|
|
||||||
const responseText = rawContent;
|
const responseText = rawContent;
|
||||||
|
|
||||||
if (looksLikeToolRankerJson(responseText)) {
|
// if (looksLikeToolRankerJson(responseText)) {
|
||||||
aiLog("error", "ollama.response.looks_like_tool_ranker_json", {
|
// aiLog("error", "ollama.response.looks_like_tool_ranker_json", {
|
||||||
round,
|
// round,
|
||||||
preview: responseText.slice(0, 800),
|
// preview: responseText.slice(0, 800),
|
||||||
target: aiLogProviderTarget(target),
|
// target: aiLogProviderTarget(target),
|
||||||
});
|
// });
|
||||||
throw new Error("Ollama chat model returned tool-ranker JSON. Check that OLLAMA chat target and OLLAMA tools/ranker target are not mixed up.");
|
// throw new Error("Ollama chat model returned tool-ranker JSON. Check that OLLAMA chat target and OLLAMA tools/ranker target are not mixed up.");
|
||||||
}
|
// }
|
||||||
|
|
||||||
streamMessage.append(responseText);
|
streamMessage.append(responseText);
|
||||||
|
|
||||||
@@ -264,6 +315,7 @@ export async function runOllama(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("MESSAGES", JSON.stringify(request.messages));
|
||||||
const response = await ollama.chat({
|
const response = await ollama.chat({
|
||||||
...request,
|
...request,
|
||||||
stream: true
|
stream: true
|
||||||
@@ -277,6 +329,8 @@ export async function runOllama(
|
|||||||
if (signal.aborted) abortOllamaResponse();
|
if (signal.aborted) abortOllamaResponse();
|
||||||
try {
|
try {
|
||||||
for await (const chunk of response) {
|
for await (const chunk of response) {
|
||||||
|
console.log("OLLAMA_CHUNK: ", chunk);
|
||||||
|
|
||||||
const localToolCalls: ToolCallData[] = [];
|
const localToolCalls: ToolCallData[] = [];
|
||||||
|
|
||||||
localToolCalls.push(...normalizeOllamaToolCalls(
|
localToolCalls.push(...normalizeOllamaToolCalls(
|
||||||
@@ -324,16 +378,16 @@ export async function runOllama(
|
|||||||
signal.removeEventListener("abort", abortOllamaResponse);
|
signal.removeEventListener("abort", abortOllamaResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
const streamedRoundText = streamMessage.getText().slice(roundTextStart);
|
// const streamedRoundText = streamMessage.getText().slice(roundTextStart);
|
||||||
if (!calls.length && looksLikeToolRankerJson(streamedRoundText)) {
|
// if (!calls.length && looksLikeToolRankerJson(streamedRoundText)) {
|
||||||
streamMessage.replaceText(streamMessage.getText().slice(0, roundTextStart));
|
// streamMessage.replaceText(streamMessage.getText().slice(0, roundTextStart));
|
||||||
aiLog("error", "ollama.response.looks_like_tool_ranker_json", {
|
// aiLog("error", "ollama.response.looks_like_tool_ranker_json", {
|
||||||
round,
|
// round,
|
||||||
preview: streamedRoundText.slice(0, 800),
|
// preview: streamedRoundText.slice(0, 800),
|
||||||
target: aiLogProviderTarget(target),
|
// target: aiLogProviderTarget(target),
|
||||||
});
|
// });
|
||||||
throw new Error("Ollama chat model returned tool-ranker JSON. Check that OLLAMA chat target and OLLAMA tools/ranker target are not mixed up.");
|
// throw new Error("Ollama chat model returned tool-ranker JSON. Check that OLLAMA chat target and OLLAMA tools/ranker target are not mixed up.");
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!calls.length) {
|
if (!calls.length) {
|
||||||
aiLog("success", "ollama.run.done", {
|
aiLog("success", "ollama.run.done", {
|
||||||
@@ -397,8 +451,3 @@ export async function runOllama(
|
|||||||
if (interval) clearInterval(interval);
|
if (interval) clearInterval(interval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class OllamaProviderRunner {
|
|
||||||
static run = runOllama;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {aiLog, aiLogDuration, aiLogMessageIdentity, aiLogProviderTarget, aiLogTo
|
|||||||
import {
|
import {
|
||||||
AsyncIterableStream,
|
AsyncIterableStream,
|
||||||
collectOpenAiResponseFunctionCalls,
|
collectOpenAiResponseFunctionCalls,
|
||||||
|
collectOpenAiResponseCodeInterpreterCalls,
|
||||||
collectOpenAiResponseImages,
|
collectOpenAiResponseImages,
|
||||||
collectOpenAiResponseText,
|
collectOpenAiResponseText,
|
||||||
executeToolBatch,
|
executeToolBatch,
|
||||||
@@ -44,7 +45,7 @@ import {
|
|||||||
ToolCallData,
|
ToolCallData,
|
||||||
ToolExecutionMemory
|
ToolExecutionMemory
|
||||||
} from "./unified-ai-runner.shared";
|
} from "./unified-ai-runner.shared";
|
||||||
import {GetNoteFileResult, GetNoteFileResultSchema} from "./tools/send-note-file";
|
import {GetNoteFileResult, GetNoteFileResultSchema} from "./tools/send-note-as-file";
|
||||||
import {bot, notesDir} from "../index";
|
import {bot, notesDir} from "../index";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
@@ -60,9 +61,11 @@ export async function runOpenAi(
|
|||||||
sourceMessage: Message,
|
sourceMessage: Message,
|
||||||
config: RuntimeConfigSnapshot,
|
config: RuntimeConfigSnapshot,
|
||||||
toolContext: ToolRuntimeContext,
|
toolContext: ToolRuntimeContext,
|
||||||
|
think?: boolean
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// TODO: 13.05.2026: remove
|
// TODO: 13.05.2026: remove
|
||||||
firstRoundStatus;
|
firstRoundStatus;
|
||||||
|
think;
|
||||||
const runnerStartedAt = Date.now();
|
const runnerStartedAt = Date.now();
|
||||||
let responseInput: unknown[] = [...messages];
|
let responseInput: unknown[] = [...messages];
|
||||||
const openAi = createOpenAiClient(config.openAiChatTarget);
|
const openAi = createOpenAiClient(config.openAiChatTarget);
|
||||||
@@ -111,6 +114,21 @@ export async function runOpenAi(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const codeInterpreterCalls = collectOpenAiResponseCodeInterpreterCalls(response);
|
||||||
|
if (codeInterpreterCalls.length) {
|
||||||
|
aiLog("info", "openai.code_interpreter_calls", {
|
||||||
|
round,
|
||||||
|
duration: aiLogDuration(roundStartedAt),
|
||||||
|
calls: codeInterpreterCalls.map(call => ({
|
||||||
|
id: call.id,
|
||||||
|
status: call.status,
|
||||||
|
containerId: call.containerId,
|
||||||
|
codeChars: call.code?.length ?? 0,
|
||||||
|
outputItems: call.outputs.length,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const calls = collectOpenAiResponseFunctionCalls(response);
|
const calls = collectOpenAiResponseFunctionCalls(response);
|
||||||
aiLog(calls.length ? "info" : "success", calls.length ? "openai.tool_calls" : "openai.run.done", {
|
aiLog(calls.length ? "info" : "success", calls.length ? "openai.tool_calls" : "openai.run.done", {
|
||||||
round,
|
round,
|
||||||
@@ -170,6 +188,7 @@ export async function runOpenAi(
|
|||||||
input: responseInput as ResponseInputItem[],
|
input: responseInput as ResponseInputItem[],
|
||||||
stream: true,
|
stream: true,
|
||||||
tools: getOpenAIResponsesToolsWithImage(config) as ResponseCreateParamsStreaming["tools"],
|
tools: getOpenAIResponsesToolsWithImage(config) as ResponseCreateParamsStreaming["tools"],
|
||||||
|
parallel_tool_calls: true
|
||||||
};
|
};
|
||||||
const response = await openAi.responses.create(request, {signal}) as unknown as AsyncIterableStream<ResponseStreamEvent>;
|
const response = await openAi.responses.create(request, {signal}) as unknown as AsyncIterableStream<ResponseStreamEvent>;
|
||||||
|
|
||||||
@@ -207,6 +226,18 @@ export async function runOpenAi(
|
|||||||
streamMessage.setStatus(Environment.finalizingImageGenText);
|
streamMessage.setStatus(Environment.finalizingImageGenText);
|
||||||
await streamMessage.flush();
|
await streamMessage.flush();
|
||||||
break;
|
break;
|
||||||
|
case "response.code_interpreter_call.in_progress":
|
||||||
|
case "response.code_interpreter_call.interpreting":
|
||||||
|
streamMessage.setStatus(Environment.getUseToolText(["code_interpreter"]));
|
||||||
|
await streamMessage.flush();
|
||||||
|
break;
|
||||||
|
case "response.code_interpreter_call.completed":
|
||||||
|
streamMessage.clearStatus();
|
||||||
|
await streamMessage.flush();
|
||||||
|
break;
|
||||||
|
case "response.code_interpreter_call_code.delta":
|
||||||
|
case "response.code_interpreter_call_code.done":
|
||||||
|
break;
|
||||||
case "response.output_item.added":
|
case "response.output_item.added":
|
||||||
if (event.item.type === "function_call" && event.item.name) {
|
if (event.item.type === "function_call" && event.item.name) {
|
||||||
const item = event.item as OpenAiResponseOutputItem & { id?: string };
|
const item = event.item as OpenAiResponseOutputItem & { id?: string };
|
||||||
@@ -275,6 +306,21 @@ export async function runOpenAi(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const codeInterpreterCalls = collectOpenAiResponseCodeInterpreterCalls(completedResponse);
|
||||||
|
if (codeInterpreterCalls.length) {
|
||||||
|
aiLog("info", "openai.code_interpreter_calls", {
|
||||||
|
round,
|
||||||
|
duration: aiLogDuration(roundStartedAt),
|
||||||
|
calls: codeInterpreterCalls.map(call => ({
|
||||||
|
id: call.id,
|
||||||
|
status: call.status,
|
||||||
|
containerId: call.containerId,
|
||||||
|
codeChars: call.code?.length ?? 0,
|
||||||
|
outputItems: call.outputs.length,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const calls = collectOpenAiResponseFunctionCalls(completedResponse);
|
const calls = collectOpenAiResponseFunctionCalls(completedResponse);
|
||||||
aiLog(calls.length ? "info" : "success", calls.length ? "openai.tool_calls" : "openai.run.done", {
|
aiLog(calls.length ? "info" : "success", calls.length ? "openai.tool_calls" : "openai.run.done", {
|
||||||
round,
|
round,
|
||||||
@@ -422,7 +468,7 @@ export async function runOpenAiCompatibleChat(
|
|||||||
model: config.geminiChatTarget.model,
|
model: config.geminiChatTarget.model,
|
||||||
messages: chatMessages,
|
messages: chatMessages,
|
||||||
tools: getOpenAITools(),
|
tools: getOpenAITools(),
|
||||||
temperature: 0.6,
|
// temperature: 0.6,
|
||||||
};
|
};
|
||||||
const response = await geminiOpenAi.chat.completions.create(request, {signal}) as unknown as OpenAiChatCompletionResponseLike;
|
const response = await geminiOpenAi.chat.completions.create(request, {signal}) as unknown as OpenAiChatCompletionResponseLike;
|
||||||
const message = response.choices?.[0]?.message;
|
const message = response.choices?.[0]?.message;
|
||||||
@@ -484,8 +530,9 @@ export async function runOpenAiCompatibleChat(
|
|||||||
model: config.geminiChatTarget.model,
|
model: config.geminiChatTarget.model,
|
||||||
messages: chatMessages,
|
messages: chatMessages,
|
||||||
tools: getOpenAITools(),
|
tools: getOpenAITools(),
|
||||||
temperature: 0.6,
|
// temperature: 0.6,
|
||||||
stream: true,
|
stream: true,
|
||||||
|
parallel_tool_calls: true
|
||||||
};
|
};
|
||||||
const response = await geminiOpenAi.chat.completions.create(request, {signal}) as unknown as AsyncIterableStream<OpenAiChatCompletionStreamChunkLike>;
|
const response = await geminiOpenAi.chat.completions.create(request, {signal}) as unknown as AsyncIterableStream<OpenAiChatCompletionStreamChunkLike>;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {photoGenDir} from "../index";
|
|||||||
import {collectReplyChainText, delay, logError, replyToMessage} from "../util/utils";
|
import {collectReplyChainText, delay, logError, replyToMessage} from "../util/utils";
|
||||||
import {MessageStore} from "../common/message-store";
|
import {MessageStore} from "../common/message-store";
|
||||||
import type {OpenAiResponseTool} from "./tool-mappers";
|
import type {OpenAiResponseTool} from "./tool-mappers";
|
||||||
import {AiProviderName, getOpenAIResponsesTools} from "./tool-mappers";
|
import {AiProviderName, getOpenAICodeInterpreterTool, getOpenAIResponsesTools} from "./tool-mappers";
|
||||||
import {TelegramArtifactFile, TelegramStreamMessage} from "./telegram-stream-message";
|
import {TelegramArtifactFile, TelegramStreamMessage} from "./telegram-stream-message";
|
||||||
import {AiDownloadedFile} from "./telegram-attachments";
|
import {AiDownloadedFile} from "./telegram-attachments";
|
||||||
import {getRuntimeCapabilities} from "./provider-model-runtime";
|
import {getRuntimeCapabilities} from "./provider-model-runtime";
|
||||||
@@ -66,7 +66,7 @@ export type {
|
|||||||
export type {GenerateContentParameters} from "@google/genai";
|
export type {GenerateContentParameters} from "@google/genai";
|
||||||
|
|
||||||
export const TELEGRAM_LIMIT = 4096;
|
export const TELEGRAM_LIMIT = 4096;
|
||||||
export const MAX_TOOL_ROUNDS = 12;
|
export const MAX_TOOL_ROUNDS = 40;
|
||||||
export const MAX_IDENTICAL_TOOL_CALLS = 1;
|
export const MAX_IDENTICAL_TOOL_CALLS = 1;
|
||||||
export const OPENAI_IMAGE_PARTIALS = 3;
|
export const OPENAI_IMAGE_PARTIALS = 3;
|
||||||
export const AI_REQUEST_TIMEOUT_MS = 10 * 60 * 1000;
|
export const AI_REQUEST_TIMEOUT_MS = 10 * 60 * 1000;
|
||||||
@@ -163,11 +163,16 @@ export type OpenAiChatToolCallLike = {
|
|||||||
|
|
||||||
export type OpenAiResponseOutputItem = {
|
export type OpenAiResponseOutputItem = {
|
||||||
type?: string;
|
type?: string;
|
||||||
|
id?: string;
|
||||||
call_id?: string;
|
call_id?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
arguments?: string;
|
arguments?: string;
|
||||||
result?: string;
|
result?: string;
|
||||||
content?: Array<{ text?: string; refusal?: string }>;
|
content?: Array<{ text?: string; refusal?: string }>;
|
||||||
|
code?: string | null;
|
||||||
|
container_id?: string;
|
||||||
|
outputs?: Array<{ type?: "logs" | "image"; logs?: string; url?: string }> | null;
|
||||||
|
status?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OpenAiResponseLike = {
|
export type OpenAiResponseLike = {
|
||||||
@@ -265,6 +270,7 @@ export type RuntimeConfigSnapshot = {
|
|||||||
|
|
||||||
ollamaChatTarget: AiRuntimeTarget;
|
ollamaChatTarget: AiRuntimeTarget;
|
||||||
ollamaToolRankerTarget?: AiRuntimeTarget;
|
ollamaToolRankerTarget?: AiRuntimeTarget;
|
||||||
|
ollamaToolTarget: AiRuntimeTarget;
|
||||||
ollamaVisionTarget: AiRuntimeTarget;
|
ollamaVisionTarget: AiRuntimeTarget;
|
||||||
ollamaThinkingTarget: AiRuntimeTarget;
|
ollamaThinkingTarget: AiRuntimeTarget;
|
||||||
ollamaAudioTarget: AiRuntimeTarget;
|
ollamaAudioTarget: AiRuntimeTarget;
|
||||||
@@ -296,7 +302,8 @@ export function snapshotRuntimeConfig(): RuntimeConfigSnapshot {
|
|||||||
rankerToolPrompt: Environment.RANKER_TOOL_PROMPT,
|
rankerToolPrompt: Environment.RANKER_TOOL_PROMPT,
|
||||||
|
|
||||||
ollamaChatTarget: resolveAiRuntimeTarget(AiProvider.OLLAMA, "chat"),
|
ollamaChatTarget: resolveAiRuntimeTarget(AiProvider.OLLAMA, "chat"),
|
||||||
ollamaToolRankerTarget: resolveAiRuntimeTarget(AiProvider.OLLAMA, "tools"),
|
ollamaToolRankerTarget: resolveAiRuntimeTarget(AiProvider.OLLAMA, "toolRank"),
|
||||||
|
ollamaToolTarget: resolveAiRuntimeTarget(AiProvider.OLLAMA, "tools"),
|
||||||
ollamaVisionTarget: resolveAiRuntimeTarget(AiProvider.OLLAMA, "vision"),
|
ollamaVisionTarget: resolveAiRuntimeTarget(AiProvider.OLLAMA, "vision"),
|
||||||
ollamaThinkingTarget: resolveAiRuntimeTarget(AiProvider.OLLAMA, "thinking"),
|
ollamaThinkingTarget: resolveAiRuntimeTarget(AiProvider.OLLAMA, "thinking"),
|
||||||
ollamaAudioTarget: resolveAiRuntimeTarget(AiProvider.OLLAMA, "audio"),
|
ollamaAudioTarget: resolveAiRuntimeTarget(AiProvider.OLLAMA, "audio"),
|
||||||
@@ -369,6 +376,7 @@ export function providerTargets(provider: AiProvider, config: RuntimeConfigSnaps
|
|||||||
return [
|
return [
|
||||||
config.ollamaChatTarget,
|
config.ollamaChatTarget,
|
||||||
config.ollamaToolRankerTarget,
|
config.ollamaToolRankerTarget,
|
||||||
|
config.ollamaToolTarget,
|
||||||
config.ollamaVisionTarget,
|
config.ollamaVisionTarget,
|
||||||
config.ollamaThinkingTarget,
|
config.ollamaThinkingTarget,
|
||||||
config.ollamaAudioTarget,
|
config.ollamaAudioTarget,
|
||||||
@@ -402,7 +410,7 @@ export function buildSystemInstruction(
|
|||||||
includePythonToolPrompt: boolean,
|
includePythonToolPrompt: boolean,
|
||||||
): string {
|
): string {
|
||||||
return [
|
return [
|
||||||
getResponseLanguageInstruction(responseLanguage),
|
config.useSystemPrompt ? getResponseLanguageInstruction(responseLanguage) : null,
|
||||||
config.systemPrompt && config.useSystemPrompt ? config.systemPrompt : null,
|
config.systemPrompt && config.useSystemPrompt ? config.systemPrompt : null,
|
||||||
includePythonToolPrompt ? pythonInterpreterToolPrompt : null,
|
includePythonToolPrompt ? pythonInterpreterToolPrompt : null,
|
||||||
].filter(Boolean).join("\n\n");
|
].filter(Boolean).join("\n\n");
|
||||||
@@ -448,13 +456,10 @@ export function roundStatus(round: number, firstRoundStatus: string, content?: s
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const status = toolCalls?.length ? Environment.getUseToolText(toolCalls)
|
return toolCalls?.length ? Environment.getUseToolText(toolCalls)
|
||||||
: thinking ? Environment.reasoningText
|
: thinking ? Environment.reasoningText
|
||||||
: round === 0 ? firstRoundStatus
|
: round === 0 ? firstRoundStatus
|
||||||
: Environment.waitThinkText;
|
: Environment.waitThinkText;
|
||||||
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isPlainTextDocument(doc: AiDownloadedFile): boolean {
|
export function isPlainTextDocument(doc: AiDownloadedFile): boolean {
|
||||||
@@ -1278,7 +1283,7 @@ export async function executeTool(
|
|||||||
await message.flush();
|
await message.flush();
|
||||||
|
|
||||||
const parsedArgs = parseToolArgumentsObject(toolCall.argumentsText);
|
const parsedArgs = parseToolArgumentsObject(toolCall.argumentsText);
|
||||||
if (parsedArgs.ok === false) {
|
if (!parsedArgs.ok) {
|
||||||
const result = toolFailureResult("invalid_arguments", parsedArgs.message, {
|
const result = toolFailureResult("invalid_arguments", parsedArgs.message, {
|
||||||
raw: parsedArgs.raw.slice(0, 4000),
|
raw: parsedArgs.raw.slice(0, 4000),
|
||||||
});
|
});
|
||||||
@@ -1624,6 +1629,7 @@ export function allToolSchemaNames(tools: readonly unknown[]): string[] {
|
|||||||
export function getOpenAIResponsesToolsWithImage(config: RuntimeConfigSnapshot): Array<OpenAiResponseTool | LooseRecord> {
|
export function getOpenAIResponsesToolsWithImage(config: RuntimeConfigSnapshot): Array<OpenAiResponseTool | LooseRecord> {
|
||||||
return [
|
return [
|
||||||
...getOpenAIResponsesTools(),
|
...getOpenAIResponsesTools(),
|
||||||
|
getOpenAICodeInterpreterTool(),
|
||||||
{
|
{
|
||||||
type: "image_generation",
|
type: "image_generation",
|
||||||
model: config.openAiImageTarget.model,
|
model: config.openAiImageTarget.model,
|
||||||
@@ -1632,6 +1638,7 @@ export function getOpenAIResponsesToolsWithImage(config: RuntimeConfigSnapshot):
|
|||||||
output_format: "png",
|
output_format: "png",
|
||||||
partial_images: OPENAI_IMAGE_PARTIALS,
|
partial_images: OPENAI_IMAGE_PARTIALS,
|
||||||
},
|
},
|
||||||
|
{type: "web_search"}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1655,6 +1662,26 @@ export function collectOpenAiResponseFunctionCalls(response: OpenAiResponseLike)
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type OpenAiCodeInterpreterCall = {
|
||||||
|
id: string;
|
||||||
|
code: string | null;
|
||||||
|
containerId: string;
|
||||||
|
status: string;
|
||||||
|
outputs: Array<{type?: "logs" | "image"; logs?: string; url?: string}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function collectOpenAiResponseCodeInterpreterCalls(response: OpenAiResponseLike): OpenAiCodeInterpreterCall[] {
|
||||||
|
return (response.output ?? [])
|
||||||
|
.filter(item => item.type === "code_interpreter_call" && item.id && item.container_id)
|
||||||
|
.map(item => ({
|
||||||
|
id: item.id!,
|
||||||
|
code: item.code ?? null,
|
||||||
|
containerId: item.container_id!,
|
||||||
|
status: item.status ?? "unknown",
|
||||||
|
outputs: Array.isArray(item.outputs) ? item.outputs : [],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
export function collectOpenAiResponseImages(response: OpenAiResponseLike): string[] {
|
export function collectOpenAiResponseImages(response: OpenAiResponseLike): string[] {
|
||||||
return (response.output ?? [])
|
return (response.output ?? [])
|
||||||
.filter(item => item.type === "image_generation_call" && typeof item.result === "string")
|
.filter(item => item.type === "image_generation_call" && typeof item.result === "string")
|
||||||
|
|||||||
@@ -1,29 +1,23 @@
|
|||||||
import {Tool} from "ollama";
|
import {Tool} from "ollama";
|
||||||
import {AiRuntimeTarget, createOllamaClient} from "./ai-runtime-target";
|
import {createOllamaClient} from "./ai-runtime-target";
|
||||||
import {aiLog, aiLogDuration, aiLogProviderTarget} from "../logging/ai-logger";
|
import {aiLog, aiLogDuration, aiLogProviderTarget} from "../logging/ai-logger";
|
||||||
import {allToolSchemaNames, isRecord, JsonObject, RuntimeConfigSnapshot, safeJsonParseObject, toolSchemaNames} from "./unified-ai-runner.shared";
|
import {
|
||||||
|
allToolSchemaNames,
|
||||||
type RankedToolStep = {
|
isRecord,
|
||||||
t: string | string[];
|
RuntimeConfigSnapshot,
|
||||||
h?: string;
|
toolSchemaNames
|
||||||
from?: string;
|
} from "./unified-ai-runner.shared";
|
||||||
};
|
import {z} from "zod";
|
||||||
|
import {PYTHON_INTERPRETER_TOOL_NAME} from "./tools/python-interpretator";
|
||||||
type RankedToolPlan = {
|
|
||||||
s?: RankedToolStep[];
|
|
||||||
m?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ToolRankerSelection = {
|
export type ToolRankerSelection = {
|
||||||
tools: Tool[];
|
tools: Tool[];
|
||||||
selectedNames: string[];
|
|
||||||
missing: string;
|
|
||||||
raw: string;
|
|
||||||
usedRanker: boolean;
|
usedRanker: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class OllamaToolRanker {
|
export class OllamaToolRanker {
|
||||||
constructor(private readonly config: RuntimeConfigSnapshot) {}
|
constructor(private readonly config: RuntimeConfigSnapshot) {
|
||||||
|
}
|
||||||
|
|
||||||
async selectTools(args: {
|
async selectTools(args: {
|
||||||
userQuery: string;
|
userQuery: string;
|
||||||
@@ -35,27 +29,71 @@ export class OllamaToolRanker {
|
|||||||
const target = this.config.ollamaToolRankerTarget;
|
const target = this.config.ollamaToolRankerTarget;
|
||||||
|
|
||||||
if (!availableTools.length) {
|
if (!availableTools.length) {
|
||||||
return {tools: [], selectedNames: [], missing: "", raw: "", usedRanker: false};
|
return {tools: [], usedRanker: false};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ranker disabled/unconfigured: keep old behavior and expose all allowed tools.
|
// Ranker disabled/unconfigured: keep old behavior and expose all allowed tools.
|
||||||
if (!target?.model) {
|
if (!target?.model) {
|
||||||
return {
|
return {
|
||||||
tools: availableTools,
|
tools: availableTools,
|
||||||
selectedNames: allToolSchemaNames(availableTools),
|
|
||||||
missing: "",
|
|
||||||
raw: "",
|
|
||||||
usedRanker: false,
|
usedRanker: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const startedAt = Date.now();
|
const startedAt = Date.now();
|
||||||
const availableNames = new Set(allToolSchemaNames(availableTools));
|
const availableNames = new Set(allToolSchemaNames(availableTools));
|
||||||
const prompt = this.config.rankerToolPrompt?.trim() || DEFAULT_TOOL_RANKER_PROMPT;
|
// const prompt = this.config.rankerToolPrompt?.trim() || DEFAULT_TOOL_RANKER_PROMPT;
|
||||||
const toolsForPrompt = availableTools.map(tool => ({
|
|
||||||
names: toolSchemaNames(tool),
|
const availableToolNames = availableTools.map(t => "- " + (t.function.name ?? ""));
|
||||||
schema: tool,
|
|
||||||
}));
|
const toolRouterPrompt = () => [
|
||||||
|
"You are a tool routing model.",
|
||||||
|
"Select the best tool.",
|
||||||
|
"Return ONLY valid JSON.",
|
||||||
|
"",
|
||||||
|
"Available tools:",
|
||||||
|
"- no_tool",
|
||||||
|
// "- ask_clarification",
|
||||||
|
// "- user_sad",
|
||||||
|
// "- user_angry",
|
||||||
|
availableToolNames.join("\n"),
|
||||||
|
"",
|
||||||
|
"Never explain reasoning.",
|
||||||
|
// "If user is sounds aggressive/angry, then pick `user_angry` tool.",
|
||||||
|
// "If the user's request is unclear, then pick `ask_clarification` tool.",
|
||||||
|
availableToolNames.find(t => t === "web_search") ?
|
||||||
|
"If you don't know the answer, then pick `search_web` tool." : null,
|
||||||
|
availableToolNames.find(t => t == PYTHON_INTERPRETER_TOOL_NAME) ?
|
||||||
|
"If user asks to write/execute Python code, then use `" + PYTHON_INTERPRETER_TOOL_NAME + "`" : null,
|
||||||
|
"",
|
||||||
|
|
||||||
|
"Return valid JSON ONLY in this format (NO ARGUMENTS): {\"toolNames\": [\"$toolName\", ... \"lastToolName\"]}",
|
||||||
|
].filter(Boolean).join("\n");
|
||||||
|
|
||||||
|
const routerSchema = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: "string",
|
||||||
|
enum: [
|
||||||
|
"no_tool",
|
||||||
|
"ask_clarification",
|
||||||
|
...availableToolNames
|
||||||
|
],
|
||||||
|
},
|
||||||
|
arguments: {
|
||||||
|
type: "object",
|
||||||
|
additionalProperties: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["name"],
|
||||||
|
additionalProperties: false,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// const toolsForPrompt = availableTools.map(tool => ({
|
||||||
|
// names: toolSchemaNames(tool),
|
||||||
|
// schema: tool,
|
||||||
|
// }));
|
||||||
|
|
||||||
aiLog("debug", "ollama.tool_ranker.start", {
|
aiLog("debug", "ollama.tool_ranker.start", {
|
||||||
round,
|
round,
|
||||||
@@ -65,46 +103,86 @@ export class OllamaToolRanker {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ollama = createOllamaClient(target as AiRuntimeTarget);
|
const ollama = createOllamaClient(target);
|
||||||
|
// const response = await ollama.chat({
|
||||||
|
// model: target.model,
|
||||||
|
// messages: [
|
||||||
|
// {role: "system", content: prompt},
|
||||||
|
// {
|
||||||
|
// role: "user",
|
||||||
|
// content: JSON.stringify({
|
||||||
|
// q: userQuery,
|
||||||
|
// tools: toolsForPrompt,
|
||||||
|
// }),
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// stream: false,
|
||||||
|
// options: {
|
||||||
|
// temperature: 0,
|
||||||
|
// num_ctx: 8192,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
const then = performance.now();
|
||||||
|
|
||||||
const response = await ollama.chat({
|
const response = await ollama.chat({
|
||||||
model: target.model,
|
model: target?.model ?? "",
|
||||||
messages: [
|
messages: [
|
||||||
{role: "system", content: prompt},
|
{
|
||||||
|
role: "system",
|
||||||
|
content: toolRouterPrompt()
|
||||||
|
},
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
content: JSON.stringify({
|
content: userQuery,
|
||||||
q: userQuery,
|
}
|
||||||
tools: toolsForPrompt,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
stream: false,
|
stream: false,
|
||||||
|
think: false,
|
||||||
|
format: routerSchema,
|
||||||
options: {
|
options: {
|
||||||
temperature: 0,
|
temperature: 0,
|
||||||
|
top_p: 0.8,
|
||||||
|
top_k: 20,
|
||||||
|
repeat_penalty: 1.05,
|
||||||
num_ctx: 8192,
|
num_ctx: 8192,
|
||||||
|
num_predict: 256
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const now = performance.now();
|
||||||
|
const diff = now - then;
|
||||||
|
console.log("TOOK " + diff + "ms");
|
||||||
|
|
||||||
|
console.log("OLLAMA_RESPONSE: ", JSON.stringify(response));
|
||||||
|
|
||||||
if (signal.aborted) throw new Error("Aborted");
|
if (signal.aborted) throw new Error("Aborted");
|
||||||
|
|
||||||
const raw = response.message?.content?.trim() ?? "";
|
const raw = response.message?.content?.trim() ?? "";
|
||||||
const plan = parseToolRankerPlan(raw);
|
const schema = z.object({
|
||||||
const selectedNames = normalizeToolRankerNames(plan, availableNames);
|
toolNames: z.array(z.string())
|
||||||
|
});
|
||||||
|
const res = schema.safeParse(JSON.parse(raw));
|
||||||
|
|
||||||
|
const selectedNames: string[] = [];
|
||||||
|
|
||||||
|
if (res.success) {
|
||||||
|
selectedNames.push(...res.data.toolNames);
|
||||||
|
}
|
||||||
|
|
||||||
const selectedNameSet = new Set(selectedNames);
|
const selectedNameSet = new Set(selectedNames);
|
||||||
const tools = availableTools.filter(tool => toolSchemaNames(tool).some(name => selectedNameSet.has(name)));
|
const tools = availableTools.filter(tool => toolSchemaNames(tool).some(name => selectedNameSet.has(name)));
|
||||||
const missing = typeof plan?.m === "string" ? plan.m.trim() : "";
|
|
||||||
|
|
||||||
aiLog("debug", "ollama.tool_ranker.done", {
|
aiLog("debug", "ollama.tool_ranker.done", {
|
||||||
round,
|
round,
|
||||||
duration: aiLogDuration(startedAt),
|
duration: aiLogDuration(startedAt),
|
||||||
selectedNames,
|
selectedNames,
|
||||||
selectedCount: tools.length,
|
selectedCount: tools.length,
|
||||||
missing,
|
|
||||||
rawPreview: raw.slice(0, 800),
|
rawPreview: raw.slice(0, 800),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Important: if the plan is valid and empty, use NO tools. Do not fall back to all tools.
|
// Important: if the plan is valid and empty, use NO tools. Do not fall back to all tools.
|
||||||
return {tools, selectedNames, missing, raw, usedRanker: true};
|
return {tools, usedRanker: true};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (String(error).includes("Aborted")) throw error;
|
if (String(error).includes("Aborted")) throw error;
|
||||||
|
|
||||||
@@ -119,9 +197,6 @@ export class OllamaToolRanker {
|
|||||||
// In that case, preserve availability rather than silently disabling tools.
|
// In that case, preserve availability rather than silently disabling tools.
|
||||||
return {
|
return {
|
||||||
tools: availableTools,
|
tools: availableTools,
|
||||||
selectedNames: allToolSchemaNames(availableTools),
|
|
||||||
missing: "",
|
|
||||||
raw: "",
|
|
||||||
usedRanker: false,
|
usedRanker: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -142,84 +217,3 @@ export function latestUserTextFromOllamaMessages(messages: readonly { role?: str
|
|||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function looksLikeToolRankerJson(text: string): boolean {
|
|
||||||
const parsed = safeJsonParseObject(extractJsonObjectText(text) ?? text);
|
|
||||||
return Array.isArray(parsed.s) && typeof parsed.m === "string";
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseToolRankerPlan(raw: string): RankedToolPlan | undefined {
|
|
||||||
const jsonText = extractJsonObjectText(raw);
|
|
||||||
if (!jsonText) return undefined;
|
|
||||||
|
|
||||||
const parsed = safeJsonParseObject(jsonText) as JsonObject;
|
|
||||||
if (!Array.isArray(parsed.s)) return undefined;
|
|
||||||
|
|
||||||
return parsed as RankedToolPlan;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeToolRankerNames(plan: RankedToolPlan | undefined, availableNames: Set<string>): string[] {
|
|
||||||
if (!plan?.s?.length) return [];
|
|
||||||
|
|
||||||
const result: string[] = [];
|
|
||||||
for (const step of plan.s) {
|
|
||||||
const rawNames = Array.isArray(step.t) ? step.t : [step.t];
|
|
||||||
for (const rawName of rawNames) {
|
|
||||||
if (typeof rawName !== "string") continue;
|
|
||||||
const name = rawName.trim();
|
|
||||||
if (availableNames.has(name) && !result.includes(name)) {
|
|
||||||
result.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractJsonObjectText(raw: string): string | undefined {
|
|
||||||
const text = raw.trim()
|
|
||||||
.replace(/^```(?:json)?\s*/i, "")
|
|
||||||
.replace(/\s*```$/i, "")
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
const start = text.indexOf("{");
|
|
||||||
if (start === -1) return undefined;
|
|
||||||
|
|
||||||
let depth = 0;
|
|
||||||
let inString = false;
|
|
||||||
let escaped = false;
|
|
||||||
|
|
||||||
for (let i = start; i < text.length; i++) {
|
|
||||||
const ch = text[i];
|
|
||||||
|
|
||||||
if (escaped) {
|
|
||||||
escaped = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ch === "\\") {
|
|
||||||
escaped = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ch === '"') {
|
|
||||||
inString = !inString;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inString) continue;
|
|
||||||
|
|
||||||
if (ch === "{") depth++;
|
|
||||||
if (ch === "}") depth--;
|
|
||||||
|
|
||||||
if (depth === 0) {
|
|
||||||
return text.slice(start, i + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT_TOOL_RANKER_PROMPT = `You are a tool router. Return strict compact JSON only.
|
|
||||||
Schema: {"s":[{"t":"tool_name","h":"short input hint","from":"previous_tool.output_or_empty"}],"m":""}
|
|
||||||
Use tools only when they are needed. If no tool is needed, return {"s":[],"m":""}.
|
|
||||||
Never answer the user. Never explain. Never use markdown.`;
|
|
||||||
|
|||||||
@@ -8,7 +8,13 @@ import {AiDownloadedFile, attachmentsToDownloadedFiles, cleanupDownloads} from "
|
|||||||
import {ChatMessage} from "./chat-messages-types";
|
import {ChatMessage} from "./chat-messages-types";
|
||||||
import {aiProviderRequestQueue} from "./provider-request-queue";
|
import {aiProviderRequestQueue} from "./provider-request-queue";
|
||||||
import {prepareOllamaDocumentRag} from "./ollama-rag";
|
import {prepareOllamaDocumentRag} from "./ollama-rag";
|
||||||
import {AI_VOICE_MODE_TRANSCRIPT, DEFAULT_AI_RESPONSE_LANGUAGE, resolveAiContextSizeForUser, resolveAiResponseLanguageForUser, resolveAiVoiceModeForUser} from "../common/user-ai-settings";
|
import {
|
||||||
|
AI_VOICE_MODE_TRANSCRIPT,
|
||||||
|
DEFAULT_AI_RESPONSE_LANGUAGE,
|
||||||
|
resolveAiContextSizeForUser,
|
||||||
|
resolveAiResponseLanguageForUser,
|
||||||
|
resolveAiVoiceModeForUser
|
||||||
|
} from "../common/user-ai-settings";
|
||||||
import {isTranscribableAudioDownload} from "./speech-to-text";
|
import {isTranscribableAudioDownload} from "./speech-to-text";
|
||||||
import {OpenAIChatMessage} from "./openai-chat-message";
|
import {OpenAIChatMessage} from "./openai-chat-message";
|
||||||
import {MistralChatMessage} from "./mistral-chat-message";
|
import {MistralChatMessage} from "./mistral-chat-message";
|
||||||
@@ -22,7 +28,29 @@ import {runOpenAi, runOpenAiCompatibleChat} from "./unified-ai-runner.openai";
|
|||||||
import {runOllama} from "./unified-ai-runner.ollama";
|
import {runOllama} from "./unified-ai-runner.ollama";
|
||||||
import {runMistral} from "./unified-ai-runner.mistral";
|
import {runMistral} from "./unified-ai-runner.mistral";
|
||||||
import {runGemini} from "./unified-ai-runner.gemini";
|
import {runGemini} from "./unified-ai-runner.gemini";
|
||||||
import {AI_REQUEST_TIMEOUT_MS, TELEGRAM_LIMIT, RuntimeConfigSnapshot, UnifiedRunOptions, appendTranscriptToChatMessages, collectCachedMessageAttachments, collectRequestedAttachmentKinds, collectTextMessages, deleteMistralLibrary, hasAudioAttachmentKind, initialStatus, isAbortError, prepareMistralDocuments, providerName, rejectUnsupportedAttachments, resolveAiRequestQueueTarget, snapshotModel, snapshotRuntimeConfig, stripAudioFromRunnerMessages, toolRuntimeContextFromDownloads, transcribeAudioIfNeeded} from "./unified-ai-runner.shared";
|
import {
|
||||||
|
AI_REQUEST_TIMEOUT_MS,
|
||||||
|
appendTranscriptToChatMessages,
|
||||||
|
collectCachedMessageAttachments,
|
||||||
|
collectRequestedAttachmentKinds,
|
||||||
|
collectTextMessages,
|
||||||
|
deleteMistralLibrary,
|
||||||
|
hasAudioAttachmentKind,
|
||||||
|
initialStatus,
|
||||||
|
isAbortError,
|
||||||
|
prepareMistralDocuments,
|
||||||
|
providerName,
|
||||||
|
rejectUnsupportedAttachments,
|
||||||
|
resolveAiRequestQueueTarget,
|
||||||
|
RuntimeConfigSnapshot,
|
||||||
|
snapshotModel,
|
||||||
|
snapshotRuntimeConfig,
|
||||||
|
stripAudioFromRunnerMessages,
|
||||||
|
TELEGRAM_LIMIT,
|
||||||
|
toolRuntimeContextFromDownloads,
|
||||||
|
transcribeAudioIfNeeded,
|
||||||
|
UnifiedRunOptions
|
||||||
|
} from "./unified-ai-runner.shared";
|
||||||
|
|
||||||
export type {ToolCallData} from "./unified-ai-runner.shared";
|
export type {ToolCallData} from "./unified-ai-runner.shared";
|
||||||
export {snapshotModel, providerTargets, ollamaModelNames} from "./unified-ai-runner.shared";
|
export {snapshotModel, providerTargets, ollamaModelNames} from "./unified-ai-runner.shared";
|
||||||
@@ -137,7 +165,8 @@ async function executeUnifiedAiRequest(
|
|||||||
|
|
||||||
switch (options.provider) {
|
switch (options.provider) {
|
||||||
case AiProvider.OPENAI:
|
case AiProvider.OPENAI:
|
||||||
await runOpenAi(options.msg, chatMessages as OpenAIChatMessage[], streamMessage, controller.signal, options.stream ?? true, firstRoundStatus, options.msg, config, toolContext);
|
await runOpenAi(options.msg, chatMessages as OpenAIChatMessage[], streamMessage, controller.signal, options.stream ?? true, firstRoundStatus, options.msg, config, toolContext,
|
||||||
|
!!options.think);
|
||||||
break;
|
break;
|
||||||
case AiProvider.OLLAMA:
|
case AiProvider.OLLAMA:
|
||||||
const currentModel = config.ollamaChatTarget.model;
|
const currentModel = config.ollamaChatTarget.model;
|
||||||
@@ -259,7 +288,7 @@ export async function runUnifiedAi(options: UnifiedRunOptions): Promise<void> {
|
|||||||
aiLog("debug", "run.queue.target", {target: aiLogProviderTarget(queueTarget), cancelId: cancel.id});
|
aiLog("debug", "run.queue.target", {target: aiLogProviderTarget(queueTarget), cancelId: cancel.id});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const queueMessage = await streamMessage.start(Environment.getAiQueueText(options.provider, 0));
|
const queueMessage = await streamMessage.start(Environment.waitThinkText);
|
||||||
setAiCancelMessageId(cancel.id, queueMessage.message_id);
|
setAiCancelMessageId(cancel.id, queueMessage.message_id);
|
||||||
aiLog("info", "run.queue.enter", {
|
aiLog("info", "run.queue.enter", {
|
||||||
cancelId: cancel.id,
|
cancelId: cancel.id,
|
||||||
@@ -327,7 +356,3 @@ export async function runUnifiedAi(options: UnifiedRunOptions): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedAiRunner {
|
|
||||||
static run = runUnifiedAi;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,6 +16,6 @@ export class OpenAIChat extends ChatCommand {
|
|||||||
description = Environment.commandDescriptions.openAiChat;
|
description = Environment.commandDescriptions.openAiChat;
|
||||||
|
|
||||||
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
|
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
|
||||||
await runUnifiedAi({provider: AiProvider.OPENAI, msg: msg, text: match?.[3] ?? "", stream: true});
|
await runUnifiedAi({provider: AiProvider.OPENAI, msg: msg, text: match?.[3] ?? "", stream: true, think: true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1166,6 +1166,8 @@ export class Environment {
|
|||||||
const name = isString(toolCall) ? toolCall : toolCall.name;
|
const name = isString(toolCall) ? toolCall : toolCall.name;
|
||||||
return name === PYTHON_INTERPRETER_TOOL_NAME
|
return name === PYTHON_INTERPRETER_TOOL_NAME
|
||||||
? this.text("getUseToolText.python", "👨💻 Running `Python`")
|
? this.text("getUseToolText.python", "👨💻 Running `Python`")
|
||||||
|
: name === "code_interpreter"
|
||||||
|
? this.text("getUseToolText.codeInterpreter", "👨💻 Running `Code Interpreter`")
|
||||||
: this.text("getUseToolText.default", "🔧 Using tool `{name}`", {name});
|
: this.text("getUseToolText.default", "🔧 Using tool `{name}`", {name});
|
||||||
}).join("\n");
|
}).join("\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export class AiModelCapabilities {
|
|||||||
thinking: AiCapabilityInfo | undefined;
|
thinking: AiCapabilityInfo | undefined;
|
||||||
extendedThinking: AiCapabilityInfo | undefined;
|
extendedThinking: AiCapabilityInfo | undefined;
|
||||||
tools: AiCapabilityInfo | undefined;
|
tools: AiCapabilityInfo | undefined;
|
||||||
|
toolRank: AiCapabilityInfo | undefined;
|
||||||
audio: AiCapabilityInfo | undefined;
|
audio: AiCapabilityInfo | undefined;
|
||||||
documents: AiCapabilityInfo | undefined;
|
documents: AiCapabilityInfo | undefined;
|
||||||
outputImages: AiCapabilityInfo | undefined;
|
outputImages: AiCapabilityInfo | undefined;
|
||||||
|
|||||||
Reference in New Issue
Block a user