feat: Redis tool cache, Service Bus alerts, doc-creator agent for edit/translate

This commit is contained in:
gongzhiyong
2026-04-12 16:15:45 +08:00
parent 33cd268003
commit fb23b64df2
9 changed files with 605 additions and 53 deletions
+4 -2
View File
@@ -21,8 +21,8 @@
"@assistant-ui/react": "^0.8.0",
"@assistant-ui/react-markdown": "^0.8.0",
"@assistant-ui/react-syntax-highlighter": "^0.7.2",
"@azure/service-bus": "^7.9.5",
"@azure/storage-blob": "^12.31.0",
"@faker-js/faker": "^9.5.1",
"@langchain/anthropic": "^0.3.18",
"@langchain/core": "^0.3.45",
"@langchain/google-genai": "^0.1.10",
@@ -46,11 +46,11 @@
"esbuild": "^0.25.0",
"esbuild-plugin-tailwindcss": "^2.0.1",
"framer-motion": "^12.4.9",
"ioredis": "^5.10.1",
"katex": "^0.16.21",
"lucide-react": "^0.476.0",
"next-themes": "^0.4.4",
"pdf-parse": "^2.4.5",
"prettier": "^3.5.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-markdown": "^10.0.1",
@@ -71,6 +71,7 @@
},
"devDependencies": {
"@eslint/js": "^9.19.0",
"@faker-js/faker": "^9.5.1",
"@types/node": "^22.13.5",
"@types/pdf-parse": "^1.1.5",
"@types/react": "^19.0.8",
@@ -83,6 +84,7 @@
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.18",
"globals": "^15.14.0",
"prettier": "^3.5.2",
"tailwind-scrollbar": "^4.0.1",
"tailwindcss": "^4.0.6",
"typescript": "~5.7.2",
+352 -6
View File
@@ -17,12 +17,12 @@ importers:
'@assistant-ui/react-syntax-highlighter':
specifier: ^0.7.2
version: 0.7.10(@assistant-ui/react-markdown@0.8.0(@assistant-ui/react@0.8.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@assistant-ui/react@0.8.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react-syntax-highlighter@15.5.13)(@types/react@19.0.10)(react-syntax-highlighter@15.6.1(react@19.0.0))(react@19.0.0)
'@azure/service-bus':
specifier: ^7.9.5
version: 7.9.5
'@azure/storage-blob':
specifier: ^12.31.0
version: 12.31.0
'@faker-js/faker':
specifier: ^9.5.1
version: 9.5.1
'@langchain/anthropic':
specifier: ^0.3.18
version: 0.3.18(@langchain/core@0.3.45(openai@4.95.1(zod@3.24.2)))
@@ -92,6 +92,9 @@ importers:
framer-motion:
specifier: ^12.4.9
version: 12.4.9(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
ioredis:
specifier: ^5.10.1
version: 5.10.1
katex:
specifier: ^0.16.21
version: 0.16.21
@@ -104,9 +107,6 @@ importers:
pdf-parse:
specifier: ^2.4.5
version: 2.4.5
prettier:
specifier: ^3.5.2
version: 3.5.2
react:
specifier: ^19.0.0
version: 19.0.0
@@ -162,6 +162,9 @@ importers:
'@eslint/js':
specifier: ^9.19.0
version: 9.21.0
'@faker-js/faker':
specifier: ^9.5.1
version: 9.5.1
'@types/node':
specifier: ^22.13.5
version: 22.13.5
@@ -198,6 +201,9 @@ importers:
globals:
specifier: ^15.14.0
version: 15.15.0
prettier:
specifier: ^3.5.2
version: 3.5.2
tailwind-scrollbar:
specifier: ^4.0.1
version: 4.0.1(react@19.0.0)(tailwindcss@4.0.9)
@@ -273,10 +279,18 @@ packages:
'@types/react-dom':
optional: true
'@azure/abort-controller@1.1.0':
resolution: {integrity: sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==}
engines: {node: '>=12.0.0'}
'@azure/abort-controller@2.1.2':
resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==}
engines: {node: '>=18.0.0'}
'@azure/core-amqp@4.4.1':
resolution: {integrity: sha512-eiVwGOMpHWPS6YsX0kjW4rfH+f0Pb5L2EKNDbuXldVkuFKSEfROdl81xHLsMAl5PP5wiiTjErcMcKsJqwyaRqw==}
engines: {node: '>=20.0.0'}
'@azure/core-auth@1.10.1':
resolution: {integrity: sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==}
engines: {node: '>=20.0.0'}
@@ -320,6 +334,10 @@ packages:
resolution: {integrity: sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==}
engines: {node: '>=20.0.0'}
'@azure/service-bus@7.9.5':
resolution: {integrity: sha512-R5Af+4jtZZII2snLomaddMyElFtTCBRZp2qERPlP8PuISLU87eFYFM7xWzxjNd0yeiyQUBkamx/ZhOC8eWhCHA==}
engines: {node: '>=18.0.0'}
'@azure/storage-blob@12.31.0':
resolution: {integrity: sha512-DBgNv10aCSxopt92DkTDD0o9xScXeBqPKGmR50FPZQaEcH4JLQ+GEOGEDv19V5BMkB7kxr+m4h6il/cCDPvmHg==}
engines: {node: '>=20.0.0'}
@@ -665,6 +683,9 @@ packages:
resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==}
engines: {node: '>=18.18'}
'@ioredis/commands@1.5.1':
resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==}
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
@@ -1374,6 +1395,9 @@ packages:
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
'@types/is-buffer@2.0.2':
resolution: {integrity: sha512-G6OXy83Va+xEo8XgqAJYOuvOMxeey9xM5XKkvwJNmN8rVdcB+r15HvHsG86hl86JvU0y1aa7Z2ERkNFYWw9ySg==}
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@@ -1600,6 +1624,10 @@ packages:
peerDependencies:
postcss: ^8.1.0
available-typed-arrays@1.0.7:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
bail@2.0.2:
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
@@ -1627,6 +1655,9 @@ packages:
buffer-crc32@0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
bundle-name@4.1.0:
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
engines: {node: '>=18'}
@@ -1639,6 +1670,14 @@ packages:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
call-bind@1.0.9:
resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==}
engines: {node: '>= 0.4'}
call-bound@1.0.4:
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
engines: {node: '>= 0.4'}
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
@@ -1708,6 +1747,10 @@ packages:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
cluster-key-slot@1.1.2:
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
engines: {node: '>=0.10.0'}
codepage@1.15.0:
resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
engines: {node: '>=0.8'}
@@ -1888,6 +1931,10 @@ packages:
resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==}
engines: {node: '>=18'}
define-data-property@1.1.4:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'}
define-lazy-prop@3.0.0:
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
engines: {node: '>=12'}
@@ -1896,6 +1943,10 @@ packages:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
denque@2.1.0:
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
engines: {node: '>=0.10'}
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
@@ -2165,6 +2216,10 @@ packages:
fn.name@1.1.0:
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
for-each@0.3.5:
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
engines: {node: '>= 0.4'}
foreground-child@3.3.1:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
@@ -2213,6 +2268,10 @@ packages:
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
generator-function@2.0.1:
resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
engines: {node: '>= 0.4'}
generic-names@4.0.0:
resolution: {integrity: sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==}
@@ -2281,6 +2340,9 @@ packages:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
has-property-descriptors@1.0.2:
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
@@ -2363,6 +2425,9 @@ packages:
peerDependencies:
postcss: ^8.1.0
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@@ -2385,6 +2450,10 @@ packages:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
ioredis@5.10.1:
resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==}
engines: {node: '>=12.22.0'}
is-alphabetical@1.0.4:
resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==}
@@ -2397,9 +2466,21 @@ packages:
is-alphanumerical@2.0.1:
resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
is-arguments@1.2.0:
resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==}
engines: {node: '>= 0.4'}
is-arrayish@0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
is-buffer@2.0.5:
resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==}
engines: {node: '>=4'}
is-callable@1.2.7:
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
engines: {node: '>= 0.4'}
is-decimal@1.0.4:
resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==}
@@ -2419,6 +2500,10 @@ packages:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
is-generator-function@1.1.2:
resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
engines: {node: '>= 0.4'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
@@ -2442,6 +2527,10 @@ packages:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
is-regex@1.2.1:
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
engines: {node: '>= 0.4'}
is-stream@2.0.1:
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
engines: {node: '>=8'}
@@ -2450,6 +2539,10 @@ packages:
resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==}
engines: {node: '>=18'}
is-typed-array@1.1.15:
resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
engines: {node: '>= 0.4'}
is-unicode-supported@2.1.0:
resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
engines: {node: '>=18'}
@@ -2507,6 +2600,9 @@ packages:
engines: {node: '>=6'}
hasBin: true
jssha@3.3.1:
resolution: {integrity: sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==}
katex@0.16.21:
resolution: {integrity: sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==}
hasBin: true
@@ -2612,6 +2708,12 @@ packages:
lodash.camelcase@4.3.0:
resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
lodash.defaults@4.2.0:
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
lodash.isarguments@3.1.0:
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
@@ -2622,6 +2724,9 @@ packages:
resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==}
engines: {node: '>= 12.0.0'}
long@5.3.2:
resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
@@ -3036,6 +3141,10 @@ packages:
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
possible-typed-array-names@1.1.0:
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
engines: {node: '>= 0.4'}
postcss-modules-extract-imports@3.1.0:
resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==}
engines: {node: ^10 || ^12 || >= 14}
@@ -3118,6 +3227,10 @@ packages:
resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
engines: {node: '>=6'}
process@0.11.10:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
@@ -3252,6 +3365,14 @@ packages:
react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
redis-errors@1.2.0:
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
engines: {node: '>=4'}
redis-parser@3.0.0:
resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
engines: {node: '>=4'}
refractor@3.6.0:
resolution: {integrity: sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==}
@@ -3291,6 +3412,12 @@ packages:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
rhea-promise@3.0.3:
resolution: {integrity: sha512-a875P5YcMkePSTEWMsnmCQS7Y4v/XvIw7ZoMtJxqtQRZsqSA6PsZxuz4vktyRykPuUgdNsA6F84dS3iEXZoYnQ==}
rhea@3.0.4:
resolution: {integrity: sha512-n3kw8syCdrsfJ72w3rohpoHHlmv/RZZEP9VY5BVjjo0sEGIt4YSKypBgaiA+OUSgJAzLjOECYecsclG5xbYtZw==}
rimraf@5.0.10:
resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==}
hasBin: true
@@ -3310,6 +3437,10 @@ packages:
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
safe-regex-test@1.1.0:
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
engines: {node: '>= 0.4'}
safe-stable-stringify@2.5.0:
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
engines: {node: '>=10'}
@@ -3332,6 +3463,10 @@ packages:
serialize-query-params@2.0.2:
resolution: {integrity: sha512-1chMo1dST4pFA9RDXAtF0Rbjaut4is7bzFbI1Z26IuMub68pNCILku85aYmeFhvnY//BXUPUhoRMjYcsT93J/Q==}
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@@ -3387,6 +3522,9 @@ packages:
resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==}
engines: {node: '>=6'}
standard-as-callback@2.1.0:
resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
std-env@3.10.0:
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
@@ -3658,6 +3796,9 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
util@0.12.5:
resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==}
uuid@10.0.0:
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
hasBin: true
@@ -3768,6 +3909,10 @@ packages:
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
which-typed-array@1.1.20:
resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==}
engines: {node: '>= 0.4'}
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@@ -3952,10 +4097,30 @@ snapshots:
- immer
- use-sync-external-store
'@azure/abort-controller@1.1.0':
dependencies:
tslib: 2.8.1
'@azure/abort-controller@2.1.2':
dependencies:
tslib: 2.8.1
'@azure/core-amqp@4.4.1':
dependencies:
'@azure/abort-controller': 2.1.2
'@azure/core-auth': 1.10.1
'@azure/core-util': 1.13.1
'@azure/logger': 1.3.0
buffer: 6.0.3
events: 3.3.0
process: 0.11.10
rhea: 3.0.4
rhea-promise: 3.0.3
tslib: 2.8.1
util: 0.12.5
transitivePeerDependencies:
- supports-color
'@azure/core-auth@1.10.1':
dependencies:
'@azure/abort-controller': 2.1.2
@@ -4031,6 +4196,29 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@azure/service-bus@7.9.5':
dependencies:
'@azure/abort-controller': 1.1.0
'@azure/core-amqp': 4.4.1
'@azure/core-auth': 1.10.1
'@azure/core-client': 1.10.1
'@azure/core-paging': 1.6.2
'@azure/core-rest-pipeline': 1.23.0
'@azure/core-tracing': 1.3.1
'@azure/core-util': 1.13.1
'@azure/core-xml': 1.5.1
'@azure/logger': 1.3.0
'@types/is-buffer': 2.0.2
buffer: 6.0.3
is-buffer: 2.0.5
jssha: 3.3.1
long: 5.3.2
process: 0.11.10
rhea-promise: 3.0.3
tslib: 2.8.1
transitivePeerDependencies:
- supports-color
'@azure/storage-blob@12.31.0':
dependencies:
'@azure/abort-controller': 2.1.2
@@ -4353,6 +4541,8 @@ snapshots:
'@humanwhocodes/retry@0.4.2': {}
'@ioredis/commands@1.5.1': {}
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
@@ -5049,6 +5239,10 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
'@types/is-buffer@2.0.2':
dependencies:
'@types/node': 22.13.5
'@types/json-schema@7.0.15': {}
'@types/katex@0.16.7': {}
@@ -5316,6 +5510,10 @@ snapshots:
postcss: 8.5.3
postcss-value-parser: 4.2.0
available-typed-arrays@1.0.7:
dependencies:
possible-typed-array-names: 1.1.0
bail@2.0.2: {}
balanced-match@1.0.2: {}
@@ -5344,6 +5542,11 @@ snapshots:
buffer-crc32@0.2.13: {}
buffer@6.0.3:
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
bundle-name@4.1.0:
dependencies:
run-applescript: 7.0.0
@@ -5355,6 +5558,18 @@ snapshots:
es-errors: 1.3.0
function-bind: 1.1.2
call-bind@1.0.9:
dependencies:
call-bind-apply-helpers: 1.0.2
es-define-property: 1.0.1
get-intrinsic: 1.3.0
set-function-length: 1.2.2
call-bound@1.0.4:
dependencies:
call-bind-apply-helpers: 1.0.2
get-intrinsic: 1.3.0
callsites@3.1.0: {}
camelcase@6.3.0: {}
@@ -5411,6 +5626,8 @@ snapshots:
clsx@2.1.1: {}
cluster-key-slot@1.1.2: {}
codepage@1.15.0: {}
color-convert@1.9.3:
@@ -5549,10 +5766,18 @@ snapshots:
bundle-name: 4.1.0
default-browser-id: 5.0.0
define-data-property@1.1.4:
dependencies:
es-define-property: 1.0.1
es-errors: 1.3.0
gopd: 1.2.0
define-lazy-prop@3.0.0: {}
delayed-stream@1.0.0: {}
denque@2.1.0: {}
dequal@2.0.3: {}
detect-libc@1.0.3: {}
@@ -5860,6 +6085,10 @@ snapshots:
fn.name@1.1.0: {}
for-each@0.3.5:
dependencies:
is-callable: 1.2.7
foreground-child@3.3.1:
dependencies:
cross-spawn: 7.0.6
@@ -5899,6 +6128,8 @@ snapshots:
function-bind@1.1.2: {}
generator-function@2.0.1: {}
generic-names@4.0.0:
dependencies:
loader-utils: 3.3.1
@@ -5969,6 +6200,10 @@ snapshots:
has-flag@4.0.0: {}
has-property-descriptors@1.0.2:
dependencies:
es-define-property: 1.0.1
has-symbols@1.1.0: {}
has-tostringtag@1.0.2:
@@ -6101,6 +6336,8 @@ snapshots:
dependencies:
postcss: 8.5.3
ieee754@1.2.1: {}
ignore@5.3.2: {}
import-fresh@3.3.1:
@@ -6116,6 +6353,20 @@ snapshots:
internmap@2.0.3: {}
ioredis@5.10.1:
dependencies:
'@ioredis/commands': 1.5.1
cluster-key-slot: 1.1.2
debug: 4.4.3
denque: 2.1.0
lodash.defaults: 4.2.0
lodash.isarguments: 3.1.0
redis-errors: 1.2.0
redis-parser: 3.0.0
standard-as-callback: 2.1.0
transitivePeerDependencies:
- supports-color
is-alphabetical@1.0.4: {}
is-alphabetical@2.0.1: {}
@@ -6130,8 +6381,17 @@ snapshots:
is-alphabetical: 2.0.1
is-decimal: 2.0.1
is-arguments@1.2.0:
dependencies:
call-bound: 1.0.4
has-tostringtag: 1.0.2
is-arrayish@0.3.2: {}
is-buffer@2.0.5: {}
is-callable@1.2.7: {}
is-decimal@1.0.4: {}
is-decimal@2.0.1: {}
@@ -6142,6 +6402,14 @@ snapshots:
is-fullwidth-code-point@3.0.0: {}
is-generator-function@1.1.2:
dependencies:
call-bound: 1.0.4
generator-function: 2.0.1
get-proto: 1.0.1
has-tostringtag: 1.0.2
safe-regex-test: 1.1.0
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
@@ -6158,10 +6426,21 @@ snapshots:
is-plain-obj@4.1.0: {}
is-regex@1.2.1:
dependencies:
call-bound: 1.0.4
gopd: 1.2.0
has-tostringtag: 1.0.2
hasown: 2.0.2
is-stream@2.0.1: {}
is-stream@4.0.1: {}
is-typed-array@1.1.15:
dependencies:
which-typed-array: 1.1.20
is-unicode-supported@2.1.0: {}
is-what@4.1.16: {}
@@ -6204,6 +6483,8 @@ snapshots:
json5@2.2.3: {}
jssha@3.3.1: {}
katex@0.16.21:
dependencies:
commander: 8.3.0
@@ -6295,6 +6576,10 @@ snapshots:
lodash.camelcase@4.3.0: {}
lodash.defaults@4.2.0: {}
lodash.isarguments@3.1.0: {}
lodash.merge@4.6.2: {}
lodash@4.17.21: {}
@@ -6308,6 +6593,8 @@ snapshots:
safe-stable-stringify: 2.5.0
triple-beam: 1.4.1
long@5.3.2: {}
longest-streak@3.1.0: {}
loose-envify@1.4.0:
@@ -6933,6 +7220,8 @@ snapshots:
picomatch@4.0.4: {}
possible-typed-array-names@1.1.0: {}
postcss-modules-extract-imports@3.1.0(postcss@8.5.3):
dependencies:
postcss: 8.5.3
@@ -7007,6 +7296,8 @@ snapshots:
prismjs@1.29.0: {}
process@0.11.10: {}
prop-types@15.8.1:
dependencies:
loose-envify: 1.4.0
@@ -7177,6 +7468,12 @@ snapshots:
tiny-invariant: 1.3.3
victory-vendor: 36.9.2
redis-errors@1.2.0: {}
redis-parser@3.0.0:
dependencies:
redis-errors: 1.2.0
refractor@3.6.0:
dependencies:
hastscript: 6.0.0
@@ -7246,6 +7543,20 @@ snapshots:
reusify@1.1.0: {}
rhea-promise@3.0.3:
dependencies:
debug: 4.4.3
rhea: 3.0.4
tslib: 2.8.1
transitivePeerDependencies:
- supports-color
rhea@3.0.4:
dependencies:
debug: 4.4.3
transitivePeerDependencies:
- supports-color
rimraf@5.0.10:
dependencies:
glob: 10.4.5
@@ -7283,6 +7594,12 @@ snapshots:
safe-buffer@5.2.1: {}
safe-regex-test@1.1.0:
dependencies:
call-bound: 1.0.4
es-errors: 1.3.0
is-regex: 1.2.1
safe-stable-stringify@2.5.0: {}
scheduler@0.25.0: {}
@@ -7295,6 +7612,15 @@ snapshots:
serialize-query-params@2.0.2: {}
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
es-errors: 1.3.0
function-bind: 1.1.2
get-intrinsic: 1.3.0
gopd: 1.2.0
has-property-descriptors: 1.0.2
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
@@ -7336,6 +7662,8 @@ snapshots:
dependencies:
type-fest: 0.7.1
standard-as-callback@2.1.0: {}
std-env@3.10.0: {}
string-hash@1.1.3: {}
@@ -7588,6 +7916,14 @@ snapshots:
util-deprecate@1.0.2: {}
util@0.12.5:
dependencies:
inherits: 2.0.4
is-arguments: 1.2.0
is-generator-function: 1.1.2
is-typed-array: 1.1.15
which-typed-array: 1.1.20
uuid@10.0.0: {}
uuid@11.1.0: {}
@@ -7713,6 +8049,16 @@ snapshots:
tr46: 0.0.3
webidl-conversions: 3.0.1
which-typed-array@1.1.20:
dependencies:
available-typed-arrays: 1.0.7
call-bind: 1.0.9
call-bound: 1.0.4
for-each: 0.3.5
get-proto: 1.0.1
gopd: 1.2.0
has-tostringtag: 1.0.2
which@2.0.2:
dependencies:
isexe: 2.0.0
@@ -32,6 +32,8 @@ import {
} from "./tool-defs.js";
import { executeWithRetry, formatToolError } from "@/agent/utils/retry";
import { getToolConfig } from "@/agent/utils/toolConfig";
import { redisGet, redisSet } from "@/agent/utils/redis-client";
import crypto from "crypto";
/** Semantic sort order: lower = shown first.
* error(100) → chart(200) → knowledge(300) → ticket(400) → search(500) → code(600) → doc(700) → actions(900)
@@ -427,6 +429,18 @@ export async function toolExecutorNode(
const kbCfg = getToolConfig("kb_search");
let kbRetryCount = 0;
// Redis cache: check for cached result before calling API
const cacheKey = `tool:kb_search:${crypto.createHash("md5").update(parsed.query).digest("hex")}`;
const cached = await redisGet(cacheKey);
if (cached) {
try {
kbData = JSON.parse(cached);
} catch {
kbData = null;
}
}
if (!kbData) {
try {
kbData = await withTimeout(
() =>
@@ -438,6 +452,10 @@ export async function toolExecutorNode(
kbCfg.timeoutMs,
runSignal,
);
// Write successful result to cache (30min TTL)
if (kbData) {
await redisSet(cacheKey, JSON.stringify(kbData), 1800);
}
} catch (kbError) {
// Push friendly error card, then try fallback to google_search
ui.push(
@@ -547,8 +565,9 @@ export async function toolExecutorNode(
};
}
}
} // close if (!kbData)
const results = (kbData.results ?? []).slice(0, 5).map((r) => ({
const results = (kbData!.results ?? []).slice(0, 5).map((r) => ({
title: r.title,
category: r.category,
snippet: r.content?.slice(0, 200) ?? "",
+20
View File
@@ -68,6 +68,12 @@ function validate() {
);
}
}
// Optional infrastructure services — tool-level warn, not fatal
if (!process.env.REDIS_CONNECTION_STRING) console.warn("[config] REDIS_CONNECTION_STRING not set — tool result cache disabled");
if (!process.env.SERVICEBUS_CONNECTION_STRING) console.warn("[config] SERVICEBUS_CONNECTION_STRING not set — alert notifications disabled");
if (!process.env.AZURE_STORAGE_CONNECTION_STRING) console.warn("[config] AZURE_STORAGE_CONNECTION_STRING not set — file upload disabled");
if (!process.env.DOC_CREATOR_API_KEY) console.warn("[config] DOC_CREATOR_API_KEY not set — doc-creator disabled, will fallback to LLM");
}
// Run at import time
@@ -106,4 +112,18 @@ export const config = {
apiKey: process.env.DAYTONA_API_KEY ?? "",
apiUrl: process.env.DAYTONA_API_URL ?? "https://app.daytona.io/api",
},
redis: {
connectionString: process.env.REDIS_CONNECTION_STRING ?? "",
},
serviceBus: {
connectionString: process.env.SERVICEBUS_CONNECTION_STRING ?? "",
},
storage: {
connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING ?? "",
},
docCreator: {
endpoint: process.env.DOC_CREATOR_ENDPOINT ?? "",
apiKey: process.env.DOC_CREATOR_API_KEY ?? "",
fallbackEndpoint: process.env.DOC_CREATOR_FALLBACK_ENDPOINT ?? "",
},
} as const;
@@ -0,0 +1,42 @@
import { config } from "@/agent/utils/config";
interface DocCreatorParams {
type: "edit" | "translate";
content: string;
instructions?: string;
targetLanguage?: string;
title?: string;
}
export async function callDocCreator(params: DocCreatorParams): Promise<string> {
const { endpoint, apiKey } = config.docCreator;
if (!endpoint || !apiKey) throw new Error("doc-creator not configured");
const body = {
inputs: {
title: params.title ?? "文档",
content: params.content,
type: params.type,
...(params.instructions ? { instructions: params.instructions } : {}),
...(params.targetLanguage ? { target_language: params.targetLanguage } : {}),
},
query: params.instructions ?? `将文档翻译为${params.targetLanguage}`,
response_mode: "blocking",
conversation_id: "",
};
const resp = await fetch(`${endpoint}/api/chat-messages`, {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
signal: AbortSignal.timeout(30000),
});
if (!resp.ok) throw new Error(`doc-creator API failed: ${resp.status}`);
const data = await resp.json() as { answer?: string };
if (!data.answer) throw new Error("doc-creator returned empty answer");
return data.answer;
}
+50
View File
@@ -0,0 +1,50 @@
import Redis from "ioredis";
import { config } from "@/agent/utils/config";
let _redis: Redis | null = null;
function parseAzureRedisConnectionString(connStr: string): { host: string; port: number; password: string } | null {
if (!connStr) return null;
try {
// Format: "host:port,password=xxx,ssl=True,abortConnect=False"
const parts = connStr.split(",");
const [host, portStr] = parts[0].split(":");
const password = parts.find(p => p.startsWith("password="))?.split("=").slice(1).join("=") ?? "";
return { host, port: parseInt(portStr ?? "6380"), password };
} catch {
return null;
}
}
export function getRedisClient(): Redis | null {
if (_redis) return _redis;
const parsed = parseAzureRedisConnectionString(config.redis.connectionString);
if (!parsed) return null;
_redis = new Redis({
host: parsed.host,
port: parsed.port,
password: parsed.password,
tls: {},
lazyConnect: true,
enableOfflineQueue: false,
connectTimeout: 3000,
});
_redis.on("error", (e) => console.error("[redis] connection error:", e.message));
return _redis;
}
/** Get cached value, returns null on miss or error */
export async function redisGet(key: string): Promise<string | null> {
try {
return await getRedisClient()?.get(key) ?? null;
} catch {
return null;
}
}
/** Set value with TTL in seconds, silently fails on error */
export async function redisSet(key: string, value: string, ttlSeconds: number): Promise<void> {
try {
await getRedisClient()?.set(key, value, "EX", ttlSeconds);
} catch { /* ignore */ }
}
+14
View File
@@ -137,5 +137,19 @@ export function formatToolError(toolName: string, error: unknown): string {
}
// Log raw error for debugging but return friendly message to LLM
console.error(`[tool-error] ${toolName} (${kind}):`, error);
// Fire-and-forget Service Bus alert for auth/server errors
if (kind === "auth" || kind === "server") {
import("@/agent/utils/service-bus-client").then(({ sendAlertAsync }) => {
sendAlertAsync({
level: "error",
tool: toolName,
errorCode: kind.toUpperCase(),
errorMessage: error instanceof Error ? error.message : String(error),
timestamp: Date.now(),
});
}).catch(() => { /* ignore import failure */ });
}
return friendly;
}
@@ -0,0 +1,38 @@
import { ServiceBusClient, ServiceBusSender } from "@azure/service-bus";
import { config } from "@/agent/utils/config";
let _client: ServiceBusClient | null = null;
let _sender: ServiceBusSender | null = null;
const QUEUE_NAME = "soc-alerts";
function getSender(): ServiceBusSender | null {
if (_sender) return _sender;
if (!config.serviceBus.connectionString) return null;
try {
_client = new ServiceBusClient(config.serviceBus.connectionString);
_sender = _client.createSender(QUEUE_NAME);
return _sender;
} catch {
return null;
}
}
export interface AlertPayload {
level: "error" | "warn";
tool: string;
errorCode?: string;
errorMessage: string;
traceId?: string;
timestamp: number;
}
/** Fire-and-forget alert, never throws */
export function sendAlertAsync(payload: AlertPayload): void {
const sender = getSender();
if (!sender) return;
sender.sendMessages({
body: payload,
contentType: "application/json",
subject: `soc-alert:${payload.level}:${payload.tool}`,
}).catch((e: Error) => console.error("[service-bus] send failed:", e.message));
}
@@ -16,6 +16,7 @@ import {
replyDraftSchema,
} from "./tool-defs.js";
import { formatToolError } from "@/agent/utils/retry";
import { callDocCreator } from "@/agent/utils/doc-creator-client";
/**
* Search backwards through messages to find the most recent canvas-doc content
@@ -131,11 +132,21 @@ export async function writerToolExecutorNode(
const parsed = docEditSchema.parse(args);
const existing = findDocContent(state, parsed.doc_id);
const editLlm = createLlm({ modelMode, maxTokens: 8192 });
const originalContent = existing?.content ?? "(原文档内容不可用)";
const originalTitle = existing?.title ?? "文档";
const docType = existing?.type ?? "markdown";
let editedContent: string;
try {
editedContent = await callDocCreator({
type: "edit",
content: originalContent,
instructions: parsed.instructions,
title: originalTitle,
});
} catch (docCreatorErr) {
console.warn("[writer] doc-creator failed, falling back to LLM:", docCreatorErr);
const editLlm = createLlm({ modelMode, maxTokens: 8192 });
const editResult = await editLlm.invoke([
{
role: "system",
@@ -147,11 +158,11 @@ export async function writerToolExecutorNode(
content: `原文档标题: ${originalTitle}\n原文档内容:\n${originalContent}\n\n编辑指令: ${parsed.instructions}\n\n请直接输出修改后的完整文档内容:`,
},
]);
const editedContent =
editedContent =
typeof editResult.content === "string"
? editResult.content
: "";
}
ui.push(
{
@@ -193,11 +204,21 @@ export async function writerToolExecutorNode(
const parsed = docTranslateSchema.parse(args);
const existing = findDocContent(state, parsed.doc_id);
const translateLlm = createLlm({ modelMode, maxTokens: 8192 });
const originalContent = existing?.content ?? "(原文档内容不可用)";
const originalTitle = existing?.title ?? "文档";
const docType = existing?.type ?? "markdown";
let translatedContent: string;
try {
translatedContent = await callDocCreator({
type: "translate",
content: originalContent,
targetLanguage: parsed.target_language,
title: originalTitle,
});
} catch (docCreatorErr) {
console.warn("[writer] doc-creator failed for translate, falling back to LLM:", docCreatorErr);
const translateLlm = createLlm({ modelMode, maxTokens: 8192 });
const translateResult = await translateLlm.invoke([
{
role: "system",
@@ -208,11 +229,11 @@ export async function writerToolExecutorNode(
content: originalContent,
},
]);
const translatedContent =
translatedContent =
typeof translateResult.content === "string"
? translateResult.content
: "";
}
const translatedTitle = `${originalTitle} (${parsed.target_language})`;