Compare commits

...

11 Commits

Author SHA1 Message Date
535ab54128 使用 vite 重构 renderer 2025-05-15 22:30:02 +08:00
19872c25b3 change port 2025-05-15 19:06:55 +08:00
8f6d431ef1 update task-loop type 2025-05-11 23:49:39 +08:00
942755b3e6 update task-loop type 2025-05-11 23:24:47 +08:00
2db3ab8888 增加 toolcall 后的 hook registerOnToolCalled 2025-05-11 23:09:19 +08:00
de7d118d7b 发布 openmcp-sdk 0.0.3 2025-05-11 21:32:47 +08:00
eafc15bba6 发布 openmcp-sdk 0.0.3 2025-05-11 21:28:04 +08:00
d85880ea7a 发布 openmcp-sdk 2025-05-11 19:26:19 +08:00
1f55126c82 优化 streaming 架构 2025-05-11 18:26:15 +08:00
a622fdd1e2 验证 task-loop 2025-05-11 17:56:29 +08:00
295c45fa00 update 2025-05-11 00:17:45 +08:00
78 changed files with 3789 additions and 12490 deletions

1
.vscode/openmcp_connection.json vendored Normal file
View File

@ -0,0 +1 @@
{"items":[]}

File diff suppressed because one or more lines are too long

View File

@ -151,7 +151,7 @@ B <--mcp--> m(MCP Server)
./dev.ps1
```
> 端口占用: 8080 (renderer) + 8081 (service)
> 端口占用: 8282 (renderer) + 8081 (service)
### Extention Dev

407
package-lock.json generated
View File

@ -24,6 +24,7 @@
"@types/showdown": "^2.0.0",
"@types/vscode": "^1.72.0",
"copy-webpack-plugin": "^13.0.0",
"fork-ts-checker-webpack-plugin": "^9.1.0",
"null-loader": "^4.0.1",
"ts-loader": "^9.5.1",
"typescript": "^5.4.2",
@ -34,6 +35,31 @@
"vscode": "^1.95.0"
}
},
"node_modules/@babel/code-frame": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.27.1",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@discoveryjs/json-ext": {
"version": "0.5.7",
"resolved": "https://registry.npmmirror.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
@ -539,6 +565,13 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -571,6 +604,13 @@
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@ -607,6 +647,17 @@
"node": ">=18"
}
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
@ -722,6 +773,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001713",
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001713.tgz",
@ -758,6 +819,22 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/chrome-trace-event": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
@ -823,6 +900,13 @@
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
"license": "MIT"
},
"node_modules/content-disposition": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
@ -899,6 +983,33 @@
"node": ">= 0.10"
}
},
"node_modules/cosmiconfig": {
"version": "8.3.6",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
"integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
"dev": true,
"license": "MIT",
"dependencies": {
"import-fresh": "^3.3.0",
"js-yaml": "^4.1.0",
"parse-json": "^5.2.0",
"path-type": "^4.0.0"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/d-fischer"
},
"peerDependencies": {
"typescript": ">=4.9.5"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@ -930,6 +1041,16 @@
}
}
},
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@ -1046,6 +1167,16 @@
"node": ">=4"
}
},
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.2.1"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@ -1405,6 +1536,87 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/fork-ts-checker-webpack-plugin": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.1.0.tgz",
"integrity": "sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.16.7",
"chalk": "^4.1.2",
"chokidar": "^4.0.1",
"cosmiconfig": "^8.2.0",
"deepmerge": "^4.2.2",
"fs-extra": "^10.0.0",
"memfs": "^3.4.1",
"minimatch": "^3.0.4",
"node-abort-controller": "^3.0.1",
"schema-utils": "^3.1.1",
"semver": "^7.3.5",
"tapable": "^2.2.1"
},
"engines": {
"node": ">=14.21.3"
},
"peerDependencies": {
"typescript": ">3.6.0",
"webpack": "^5.11.0"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"ajv": "^6.9.1"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT"
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
@ -1457,6 +1669,28 @@
"node": ">= 0.8"
}
},
"node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/fs-monkey": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz",
"integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==",
"dev": true,
"license": "Unlicense"
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@ -1649,6 +1883,33 @@
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT"
},
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/import-fresh/node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/import-local": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/import-local/-/import-local-3.2.0.tgz",
@ -1708,6 +1969,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
"dev": true,
"license": "MIT"
},
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@ -1886,6 +2154,26 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
@ -1911,6 +2199,19 @@
"node": ">=6"
}
},
"node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz",
@ -1929,6 +2230,13 @@
"immediate": "~3.0.5"
}
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true,
"license": "MIT"
},
"node_modules/loader-runner": {
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/loader-runner/-/loader-runner-4.3.0.tgz",
@ -1992,6 +2300,19 @@
"node": ">= 0.8"
}
},
"node_modules/memfs": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz",
"integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==",
"dev": true,
"license": "Unlicense",
"dependencies": {
"fs-monkey": "^1.0.4"
},
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
@ -2044,6 +2365,19 @@
"node": ">= 0.6"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -2065,6 +2399,13 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
"node_modules/node-abort-controller": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
"integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==",
"dev": true,
"license": "MIT"
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
@ -2326,6 +2667,38 @@
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
"license": "(MIT AND Zlib)"
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"license": "MIT",
"dependencies": {
"callsites": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
"json-parse-even-better-errors": "^2.3.0",
"lines-and-columns": "^1.1.6"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@ -2368,6 +2741,16 @@
"node": ">=16"
}
},
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
@ -2493,6 +2876,20 @@
"node": ">= 0.8"
}
},
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/rechoir": {
"version": "0.8.0",
"resolved": "https://registry.npmmirror.com/rechoir/-/rechoir-0.8.0.tgz",
@ -3138,6 +3535,16 @@
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"license": "MIT"
},
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",

View File

@ -239,6 +239,7 @@
"@types/showdown": "^2.0.0",
"@types/vscode": "^1.72.0",
"copy-webpack-plugin": "^13.0.0",
"fork-ts-checker-webpack-plugin": "^9.1.0",
"null-loader": "^4.0.1",
"ts-loader": "^9.5.1",
"typescript": "^5.4.2",

View File

@ -1,4 +0,0 @@
> 1%
last 2 versions
not dead
not ie 11

View File

0
renderer/.env.production Normal file
View File

View File

@ -1,19 +0,0 @@
module.exports = {
root: true,
env: {
node: true,
},
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/typescript/recommended"
],
parserOptions: {
ecmaVersion: 2020,
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
'vue/multi-word-component-names': 'off'
},
};

30
renderer/.gitignore vendored
View File

@ -1,23 +1,31 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
update.sh

View File

@ -1,24 +1,33 @@
# app
# test-vite
## Project setup
```
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compiles and minifies for production
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@ -1,3 +0,0 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
};

1
renderer/env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

18
renderer/index.html Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.png">
<link rel="stylesheet" href="/default-dark.css">
<link rel="stylesheet" href="/vscode.css">
<link rel="stylesheet" href="/mcp.css">
<link rel="stylesheet" href="/iconfont.css">
<link rel="stylesheet" href="/animation.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

13659
renderer/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,16 @@
{
"name": "renderer",
"version": "0.1.0",
"name": "test-vite",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
"serve": "vite",
"serve:website": "vite --mode website",
"build": "run-p type-check \"build-only {@}\" --",
"build:website": "vite build --mode website",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build"
},
"dependencies": {
"core-js": "^3.8.3",
@ -15,29 +20,22 @@
"markdown-it": "^14.1.0",
"markdown-it-katex": "^2.0.3",
"openai": "^4.93.0",
"prismjs": "^1.30.0",
"uuid": "^11.1.0",
"vue": "^3.2.13",
"vue": "^3.5.13",
"vue-i18n": "^11.1.0",
"vue-router": "^4.0.3"
"vue-router": "^4.5.0"
},
"devDependencies": {
"@types/lodash": "^4.17.16",
"@types/markdown-it": "^14.1.2",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-typescript": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/eslint-config-typescript": "^9.1.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.0.3",
"prettier": "^2.4.1",
"typescript": "^4.4.3",
"unplugin-auto-import": "^0.17.5",
"unplugin-vue-components": "^0.26.0"
"@tsconfig/node22": "^22.0.1",
"@types/node": "^22.14.0",
"@types/prismjs": "^1.26.5",
"@vitejs/plugin-vue": "^5.2.3",
"@vue/tsconfig": "^0.7.0",
"npm-run-all2": "^7.0.2",
"typescript": "~5.8.0",
"vite": "^6.2.4",
"vite-plugin-vue-devtools": "^7.7.2",
"vue-tsc": "^2.2.8"
}
}

BIN
renderer/public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 824 834" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<path id="path_1" d="M300 0C465.708 0 600 134.292 600 300L600 300C600 465.708 465.708 600 300 600L300 600C134.292 600 0 465.708 0 300L0 300C0 134.292 134.292 0 300 0Z" />
<linearGradient id="gradient_2" gradientUnits="userSpaceOnUse" x1="300" y1="0" x2="300" y2="600">
<stop offset="0" stop-color="#A1A7F6" />
<stop offset="1" stop-color="#FFFFFF" stop-opacity="0.2" />
</linearGradient>
</defs>
<g>
<g transform="translate(186 116)">
<use p4:href="#path_1" fill="#5A00FF" xmlns:p4="http://www.w3.org/1999/xlink" />
<use p4:href="#path_1" fill="url(#gradient_2)" xmlns:p4="http://www.w3.org/1999/xlink" />
</g>
<path d="M0 110.5C0 49.4725 49.4725 0 110.5 0C171.527 0 221 49.4725 221 110.5C221 171.527 171.527 221 110.5 221C49.4725 221 0 171.527 0 110.5Z" fill="#FFFFFF" fill-rule="evenodd" fill-opacity="0.431" transform="translate(445 458)" />
<path d="M0 55.5C0 24.8482 24.8482 0 55.5 0C86.1518 0 111 24.8482 111 55.5C111 86.1518 86.1518 111 55.5 111C24.8482 111 0 86.1518 0 55.5Z" fill="#FFFFFF" fill-rule="evenodd" fill-opacity="0.431" transform="translate(199 386)" />
<path d="M0 182.5C0 81.708 81.708 0 182.5 0C283.292 0 365 81.708 365 182.5C365 283.292 283.292 365 182.5 365C81.708 365 0 283.292 0 182.5Z" fill="#FFFFFF" fill-rule="evenodd" fill-opacity="0.424" transform="translate(339 156)" />
<path d="M0 57C0 25.5198 25.5198 0 57 0C88.4802 0 114 25.5198 114 57C114 88.4802 88.4802 114 57 114C25.5198 114 0 88.4802 0 57Z" fill="#FFFFFF" fill-rule="evenodd" fill-opacity="0.431" transform="translate(521 188)" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,22 +0,0 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="referrer" content="no-referrer">
<link rel="icon" href="<%= BASE_URL %>favicon.svg">
<link rel="stylesheet" href="default-dark.css">
<link rel="stylesheet" href="vscode.css">
<link rel="stylesheet" href="mcp.css">
<link rel="stylesheet" href="iconfont.css">
<link rel="stylesheet" href="animation.css">
<title>OpenMCP Client</title>
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@ -4,6 +4,7 @@
<MainPanel></MainPanel>
<Tour v-if="!userHasReadGuide"/>
<PasswordDialog v-if="password"/>
</div>
</template>
@ -17,14 +18,13 @@ import MainPanel from '@/components/main-panel/index.vue';
import { setDefaultCss } from './hook/css';
import { greenLog, pinkLog } from './views/setting/util';
import { useMessageBridge } from './api/message-bridge';
import { doConnect, loadEnvVar } from './views/connect/connection';
import { getTour, loadSetting } from './hook/setting';
import { loadPanels } from './hook/panel';
import { initialise } from './views/connect/connection';
import { getPlatform } from './api/platform';
import Tour from '@/components/guide/tour.vue';
import { userHasReadGuide } from './components/guide/tour';
import { ElLoading } from 'element-plus';
import PasswordDialog from '@/components/password-dialog/index.vue';
import { privilegeStatus } from './components/password-dialog/status';
const bridge = useMessageBridge();
@ -37,23 +37,14 @@ bridge.addCommandListener('hello', data => {
const route = useRoute();
const router = useRouter();
onMounted(async () => {
const loading = ElLoading.service({
fullscreen: true,
lock: true,
text: 'Loading',
background: 'rgba(0, 0, 0, 0.7)'
});
const password = Boolean(import.meta.env.VITE_USE_PASSWORD);
privilegeStatus.allow = !Boolean(password);
onMounted(async () => {
// css
setDefaultCss();
document.addEventListener('click', () => {
Connection.showPanel = false;
});
pinkLog('OpenMCP Client 启动');
const platform = getPlatform();
//
@ -65,29 +56,18 @@ onMounted(async () => {
}
//
await bridge.awaitForWebsockt();
await bridge.awaitForWebsocket();
pinkLog('准备请求设置');
//
if (!privilegeStatus.allow) {
return;
}
//
loadSetting();
//
loadEnvVar();
//
getTour();
//
await doConnect({
namespace: platform,
updateCommandString: true
document.addEventListener('click', () => {
Connection.showPanel = false;
});
// loading panels
await loadPanels();
loading.close();
await initialise();
});
</script>

0
renderer/src/api/api.ts Normal file
View File

View File

@ -1,10 +1,12 @@
import { pinkLog, redLog } from '@/views/setting/util';
import { acquireVsCodeApi, electronApi, getPlatform } from './platform';
import { privilegeStatus } from '@/components/password-dialog/status';
export interface VSCodeMessage {
command: string;
data?: unknown;
callbackId?: string;
password?: string;
}
export interface RestFulResponse {
@ -24,11 +26,9 @@ export class MessageBridge {
private handlers = new Map<string, Set<CommandHandler>>();
private isConnected: Promise<boolean> | null = null;
constructor(private wsUrl: string = 'ws://localhost:8080') {
// 环境检测优先级:
// 1. VS Code WebView 环境
// 2. 浏览器 WebSocket 环境
constructor(
private setupSignature: any
) {
const platform = getPlatform();
@ -68,7 +68,14 @@ export class MessageBridge {
// WebSocket 环境连接
private setupWebSocket() {
this.ws = new WebSocket(this.wsUrl);
const wsUrl = this.setupSignature;
if (typeof wsUrl !== 'string') {
throw new Error('setupSignature must be a string');
}
this.ws = new WebSocket(wsUrl);
this.ws.onmessage = (event) => {
try {
@ -87,6 +94,7 @@ export class MessageBridge {
this.postMessage = (message) => {
if (this.ws?.readyState === WebSocket.OPEN) {
console.log('send', message);
message.password = privilegeStatus.password;
this.ws.send(JSON.stringify(message));
}
};
@ -100,7 +108,7 @@ export class MessageBridge {
});
}
public async awaitForWebsockt() {
public async awaitForWebsocket() {
if (this.isConnected) {
await this.isConnected;
}
@ -119,13 +127,18 @@ export class MessageBridge {
}
private setupNodejsListener() {
const EventEmitter = require('events');
const eventEmitter = new EventEmitter();
const emitter = this.setupSignature;
if (!emitter.on || !emitter.emit) {
return;
}
emitter.on('message/service', (message: VSCodeMessage) => {
this.dispatchMessage(message);
});
this.postMessage = (message) => {
eventEmitter.emit('server', message);
emitter.emit('message/renderer', message);
};
}
@ -209,16 +222,18 @@ export class MessageBridge {
}
// 单例实例
const messageBridge = new MessageBridge();
let messageBridge: MessageBridge;
export function createMessageBridge(setupSignature: any) {
messageBridge = new MessageBridge(setupSignature);
}
// 向外暴露一个独立函数,保证 MessageBridge 是单例的
export function useMessageBridge() {
if (!messageBridge && getPlatform() !== 'nodejs') {
messageBridge = new MessageBridge('ws://localhost:8282');
}
const bridge = messageBridge;
return {
postMessage: bridge.postMessage.bind(bridge),
addCommandListener: bridge.addCommandListener.bind(bridge),
commandRequest: bridge.commandRequest.bind(bridge),
awaitForWebsockt: bridge.awaitForWebsockt.bind(bridge)
};
return bridge;
}

View File

@ -1,5 +1,5 @@
import type { ToolCallContent, ToolItem } from "@/hook/type";
import { Ref, ref } from "vue";
import { type Ref, ref } from "vue";
import type { OpenAI } from 'openai';
type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
@ -51,6 +51,7 @@ interface EnableToolItem {
name: string;
description: string;
enabled: boolean;
inputSchema?: any;
}
export interface ChatSetting {
@ -117,17 +118,31 @@ export type IRenderMessage = ICommonRenderMessage | IToolRenderMessage;
export function getToolSchema(enableTools: EnableToolItem[]) {
const toolsSchema = [];
for (let i = 0; i < enableTools.length; i++) {
if (enableTools[i].enabled) {
const tool = allTools.value[i];
const enableTool = enableTools[i];
toolsSchema.push({
type: 'function',
function: {
name: tool.name,
description: tool.description || "",
parameters: tool.inputSchema
}
});
if (enableTool.enabled) {
if (enableTool.inputSchema) {
toolsSchema.push({
type: 'function',
function: {
name: enableTool.name,
description: enableTool.description || "",
parameters: enableTool.inputSchema
}
});
} else {
const tool = allTools.value[i];
toolsSchema.push({
type: 'function',
function: {
name: tool.name,
description: tool.description || "",
parameters: tool.inputSchema
}
});
}
}
}
return toolsSchema;

View File

@ -21,12 +21,13 @@
</template>
<script setup lang="ts">
import { provide, onMounted, onUnmounted, ref, defineEmits, defineProps, PropType, inject, Ref } from 'vue';
import { provide, onMounted, onUnmounted, ref, defineEmits, defineProps, type PropType, inject, type Ref } from 'vue';
import { useI18n } from 'vue-i18n';
import KRichTextarea from './rich-textarea.vue';
import { tabs } from '../../panel';
import { ChatMessage, ChatStorage, MessageState, ToolCall, RichTextItem } from './chat';
import type { ChatMessage, ChatStorage, ToolCall, RichTextItem } from './chat';
import { MessageState } from './chat';
import { TaskLoop } from '../core/task-loop';
import { llmManager, llms } from '@/views/setting/llm';
@ -93,7 +94,8 @@ function handleSend(newMessage?: string) {
isLoading.value = true;
autoScroll.value = true;
loop = new TaskLoop(streamingContent, streamingToolCalls);
loop = new TaskLoop();
loop.bindStreaming(streamingContent, streamingToolCalls);
loop.registerOnError((error) => {
console.log('error.msg');

View File

@ -25,7 +25,7 @@
<script setup lang="ts">
import { defineComponent, inject, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { ChatStorage } from '../chat';
import type { ChatStorage } from '../chat';
const { t } = useI18n();

View File

@ -26,8 +26,8 @@
<script setup lang="ts">
import { createApp, inject, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { ChatStorage, EditorContext } from '../chat';
import { PromptsGetResponse, PromptTemplate } from '@/hook/type';
import type { ChatStorage, EditorContext } from '../chat';
import type { PromptsGetResponse, PromptTemplate } from '@/hook/type';
import PromptTemplates from '@/components/main-panel/prompt/prompt-templates.vue';
import PromptReader from '@/components/main-panel/prompt/prompt-reader.vue';

View File

@ -24,8 +24,8 @@
<script setup lang="ts">
import { createApp, inject, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { ChatStorage, EditorContext } from '../chat';
import { Resources, ResourcesReadResponse, ResourceTemplate } from '@/hook/type';
import type { ChatStorage, EditorContext } from '../chat';
import type { Resources, ResourcesReadResponse, ResourceTemplate } from '@/hook/type';
import ResourceList from '@/components/main-panel/resource/resource-list.vue';
import ResourceReader from '@/components/main-panel/resource/resouce-reader.vue';

View File

@ -1,8 +1,13 @@
import { ToolCallResponse } from "@/hook/type";
import type { ToolCallContent, ToolCallResponse } from "@/hook/type";
import { callTool } from "../../tool/tools";
import { MessageState, ToolCall } from "../chat-box/chat";
import { MessageState, type ToolCall } from "../chat-box/chat";
export async function handleToolCalls(toolCall: ToolCall) {
export interface ToolCallResult {
state: MessageState;
content: ToolCallContent[];
}
export async function handleToolCalls(toolCall: ToolCall): Promise<ToolCallResult> {
// 反序列化 streaming 来的参数字符串
const toolName = toolCall.function.name;
const argsResult = deserializeToolCallResponse(toolCall.function.arguments);

View File

@ -1,18 +1,20 @@
/* eslint-disable */
import type { Ref } from "vue";
import { ToolCall, ChatStorage, getToolSchema, MessageState } from "../chat-box/chat";
import { useMessageBridge } from "@/api/message-bridge";
import { ref, type Ref } from "vue";
import { type ToolCall, type ChatStorage, getToolSchema, MessageState } from "../chat-box/chat";
import { useMessageBridge, MessageBridge, createMessageBridge } from "@/api/message-bridge";
import type { OpenAI } from 'openai';
import { llmManager, llms } from "@/views/setting/llm";
import { pinkLog, redLog } from "@/views/setting/util";
import { ElMessage } from "element-plus";
import { handleToolCalls } from "./handle-tool-calls";
import { handleToolCalls, type ToolCallResult } from "./handle-tool-calls";
import { getPlatform } from "@/api/platform";
export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string };
export interface TaskLoopOptions {
maxEpochs: number;
maxJsonParseRetry: number;
maxEpochs?: number;
maxJsonParseRetry?: number;
adapter?: any;
}
export interface IErrorMssage {
@ -29,21 +31,40 @@ export interface IDoConversationResult {
* @description
*/
export class TaskLoop {
private bridge = useMessageBridge();
private bridge: MessageBridge;
private streamingContent: Ref<string>;
private streamingToolCalls: Ref<ToolCall[]>;
private currentChatId = '';
private onError: (error: IErrorMssage) => void = (msg) => {};
private onChunk: (chunk: ChatCompletionChunk) => void = (chunk) => {};
private onDone: () => void = () => {};
private onToolCall: (toolCall: ToolCall) => ToolCall = toolCall => toolCall;
private onToolCalled: (toolCallResult: ToolCallResult) => ToolCallResult = toolCallResult => toolCallResult;
private onEpoch: () => void = () => {};
private completionUsage: ChatCompletionChunk['usage'] | undefined;
private llmConfig: any;
constructor(
private readonly streamingContent: Ref<string>,
private readonly streamingToolCalls: Ref<ToolCall[]>,
private onError: (error: IErrorMssage) => void = (msg) => {},
private onChunk: (chunk: ChatCompletionChunk) => void = (chunk) => {},
private onDone: () => void = () => {},
private onEpoch: () => void = () => {},
private readonly taskOptions: TaskLoopOptions = { maxEpochs: 20, maxJsonParseRetry: 3 },
private readonly taskOptions: TaskLoopOptions = { maxEpochs: 20, maxJsonParseRetry: 3, adapter: undefined },
) {
this.streamingContent = ref('');
this.streamingToolCalls = ref([]);
// 根据当前环境决定是否要开启 messageBridge
const platform = getPlatform();
if (platform === 'nodejs') {
const adapter = taskOptions.adapter;
if (!adapter) {
throw new Error('adapter is required');
}
createMessageBridge(adapter.emitter);
}
// web 环境下 bridge 会自动加载完成
this.bridge = useMessageBridge();
}
private handleChunkDeltaContent(chunk: ChatCompletionChunk) {
@ -135,7 +156,7 @@ export class TaskLoop {
}, { once: true });
console.log(chatData);
// console.log(chatData);
this.bridge.postMessage({
command: 'llm/chat/completions',
@ -200,6 +221,10 @@ export class TaskLoop {
this.streamingToolCalls.value = [];
}
/**
* @description error
* @param handler
*/
public registerOnError(handler: (msg: IErrorMssage) => void) {
this.onError = handler;
}
@ -208,14 +233,38 @@ export class TaskLoop {
this.onChunk = handler;
}
/**
* @description chat.completion
* @param handler
*/
public registerOnDone(handler: () => void) {
this.onDone = handler;
}
/**
* @description epoch
* @param handler
*/
public registerOnEpoch(handler: () => void) {
this.onEpoch = handler;
}
/**
* @description toolcall
* @param handler
*/
public registerOnToolCall(handler: (toolCall: ToolCall) => ToolCall) {
this.onToolCall = handler;
}
/**
* @description toolcall
* @param handler
*/
public registerOnToolCalled(handler: (toolCallResult: ToolCallResult) => ToolCallResult) {
this.onToolCalled = handler;
}
public setMaxEpochs(maxEpochs: number) {
this.taskOptions.maxEpochs = maxEpochs;
}
@ -235,6 +284,11 @@ export class TaskLoop {
this.llmConfig = config;
}
public bindStreaming(content: Ref<string>, toolCalls: Ref<ToolCall[]>) {
this.streamingContent = content;
this.streamingToolCalls = toolCalls;
}
public getLlmConfig() {
if (this.llmConfig) {
return this.llmConfig;
@ -262,8 +316,9 @@ export class TaskLoop {
});
let jsonParseErrorRetryCount = 0;
const maxEpochs = this.taskOptions.maxEpochs || 20;
for (let i = 0; i < this.taskOptions.maxEpochs; ++ i) {
for (let i = 0; i < maxEpochs; ++ i) {
this.onEpoch();
@ -285,9 +340,9 @@ export class TaskLoop {
// 发送请求
const doConverationResult = await this.doConversation(chatData);
console.log('[doConverationResult] Response');
console.log(doConverationResult);
// 如果存在需要调度的工具
if (this.streamingToolCalls.value.length > 0) {
@ -304,8 +359,11 @@ export class TaskLoop {
pinkLog('调用工具数量:' + this.streamingToolCalls.value.length);
for (const toolCall of this.streamingToolCalls.value || []) {
const toolCallResult = await handleToolCalls(toolCall);
for (let toolCall of this.streamingToolCalls.value || []) {
toolCall = this.onToolCall(toolCall);
let toolCallResult = await handleToolCalls(toolCall);
toolCallResult = this.onToolCalled(toolCallResult);
if (toolCallResult.state === MessageState.ParseJsonError) {
// 如果是因为解析 JSON 错误,则重新开始
@ -315,7 +373,7 @@ export class TaskLoop {
redLog('解析 JSON 错误 ' + toolCall?.function?.arguments);
// 如果因为 JSON 错误而失败太多,就只能中断了
if (jsonParseErrorRetryCount >= this.taskOptions.maxJsonParseRetry) {
if (jsonParseErrorRetryCount >= (this.taskOptions.maxJsonParseRetry || 3)) {
tabStorage.messages.push({
role: 'assistant',
content: `解析 JSON 错误,无法继续调用工具 (累计错误次数 ${this.taskOptions.maxJsonParseRetry})`,

View File

@ -1,4 +1,4 @@
import { IExtraInfo } from "../chat-box/chat";
import type { IExtraInfo } from "../chat-box/chat";
export interface UsageStatistic {
input: number;

View File

@ -57,9 +57,11 @@
<script setup lang="ts">
import { ref, onMounted, defineComponent, defineProps, onUnmounted, computed, nextTick, watch, provide } from 'vue';
import { useI18n } from 'vue-i18n';
import { ElMessage, ScrollbarInstance } from 'element-plus';
import { ElMessage, type ScrollbarInstance } from 'element-plus';
import { tabs } from '../panel';
import { ChatMessage, ChatStorage, IRenderMessage, MessageState, ToolCall } from './chat-box/chat';
import type { ChatMessage, ChatStorage, IRenderMessage, ToolCall } from './chat-box/chat';
import { MessageState } from './chat-box/chat';
import * as Message from './message';
import ChatBox from './chat-box/index.vue';

View File

@ -1,4 +1,4 @@
import Prism from "./prism";
import Prism from 'prismjs';
// 定义 escapeHtml 函数
function escapeHtml(unsafe: string) {

View File

@ -1,4 +1,4 @@
const Prism: {
export const Prism: {
highlight: (code: string, language: any, lang: string) => string;
languages: Record<string, any>;
// 添加其他需要的类型定义

View File

@ -32,7 +32,7 @@
</template>
<script setup lang="ts">
import { defineProps, ref, PropType, inject } from 'vue';
import { defineProps, ref, type PropType, inject } from 'vue';
import { tabs } from '../../panel';
import type { ChatStorage, IRenderMessage } from '../chat-box/chat';

View File

@ -27,10 +27,10 @@
<script setup lang="ts">
import { useMessageBridge } from '@/api/message-bridge';
import { CasualRestAPI, PromptTemplate, PromptsListResponse } from '@/hook/type';
import type { CasualRestAPI, PromptTemplate, PromptsListResponse } from '@/hook/type';
import { onMounted, onUnmounted, defineProps, defineEmits, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import { promptsManager, PromptStorage } from './prompts';
import { promptsManager, type PromptStorage } from './prompts';
import { tabs } from '../panel';
import { ElMessage } from 'element-plus';

View File

@ -1,4 +1,4 @@
import { PromptsGetResponse, PromptTemplate } from '@/hook/type';
import type { PromptsGetResponse, PromptTemplate } from '@/hook/type';
import { reactive } from 'vue';
export const promptsManager = reactive<{

View File

@ -38,8 +38,8 @@ import { defineComponent, defineProps, watch, ref, computed, reactive, defineEmi
import { useI18n } from 'vue-i18n';
import type { FormInstance, FormRules } from 'element-plus';
import { tabs } from '../panel';
import { parseResourceTemplate, resourcesManager, ResourceStorage } from './resources';
import { CasualRestAPI, ResourcesReadResponse } from '@/hook/type';
import { parseResourceTemplate, resourcesManager, type ResourceStorage } from './resources';
import type{ CasualRestAPI, ResourcesReadResponse } from '@/hook/type';
import { useMessageBridge } from '@/api/message-bridge';
import { getDefaultValue, normaliseJavascriptType } from '@/hook/mcp';

View File

@ -30,10 +30,10 @@
<script setup lang="ts">
import { useMessageBridge } from '@/api/message-bridge';
import { CasualRestAPI, ResourceTemplate, ResourceTemplatesListResponse } from '@/hook/type';
import type { CasualRestAPI, ResourceTemplate, ResourceTemplatesListResponse } from '@/hook/type';
import { onMounted, onUnmounted, defineProps, ref, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import { resourcesManager, ResourceStorage } from './resources';
import { resourcesManager, type ResourceStorage } from './resources';
import { tabs } from '../panel';
import { ElMessage } from 'element-plus';

View File

@ -27,10 +27,10 @@
<script setup lang="ts">
import { useMessageBridge } from '@/api/message-bridge';
import { CasualRestAPI, Resources, ResourcesListResponse } from '@/hook/type';
import type { CasualRestAPI, Resources, ResourcesListResponse } from '@/hook/type';
import { onMounted, onUnmounted, defineProps, defineEmits, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import { resourcesManager, ResourceStorage } from './resources';
import { resourcesManager, type ResourceStorage } from './resources';
import { tabs } from '../panel';
import { ElMessage } from 'element-plus';

View File

@ -1,4 +1,4 @@
import { ResourcesReadResponse, ResourceTemplate, Resources } from '@/hook/type';
import type { ResourcesReadResponse, ResourceTemplate, Resources } from '@/hook/type';
import { reactive } from 'vue';

View File

@ -31,10 +31,10 @@
<script setup lang="ts">
import { useMessageBridge } from '@/api/message-bridge';
import { CasualRestAPI, ToolsListResponse } from '@/hook/type';
import type { CasualRestAPI, ToolsListResponse } from '@/hook/type';
import { onMounted, onUnmounted, defineProps } from 'vue';
import { useI18n } from 'vue-i18n';
import { toolsManager, ToolStorage } from './tools';
import { toolsManager, type ToolStorage } from './tools';
import { tabs } from '../panel';
import { ElMessage } from 'element-plus';

View File

@ -1,6 +1,6 @@
import { useMessageBridge } from '@/api/message-bridge';
import { mcpSetting } from '@/hook/mcp';
import { ToolsListResponse, ToolCallResponse, CasualRestAPI } from '@/hook/type';
import type { ToolsListResponse, ToolCallResponse, CasualRestAPI } from '@/hook/type';
import { pinkLog } from '@/views/setting/util';
import { reactive } from 'vue';
@ -16,9 +16,9 @@ export interface ToolStorage {
formData: Record<string, any>;
}
const bridge = useMessageBridge();
export function callTool(toolName: string, toolArgs: Record<string, any>) {
const bridge = useMessageBridge();
return new Promise<ToolCallResponse>((resolve, reject) => {
bridge.addCommandListener('tools/call', (data: CasualRestAPI<ToolCallResponse>) => {
console.log(data.msg);

View File

@ -0,0 +1,55 @@
<template>
<el-dialog v-model="dialogVisible" title="请输入密码" :close-on-click-modal="false" :close-on-press-escape="false"
:show-close="false" width="30%" top="20vh">
<br>
<el-input v-model="privilegeStatus.password"
type="password"
placeholder="请输入密码"
@keyup.enter.prevent="handleSubmit"
/>
<template #footer>
<el-button type="primary" @click="handleSubmit">确认</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import { privilegeStatus } from './status';
import { useMessageBridge } from '@/api/message-bridge';
import { initialise } from '@/views/connect/connection';
const dialogVisible = ref(true);
const handleSubmit = async () => {
const bridge = useMessageBridge();
const res = await bridge.commandRequest('ciallo', { password: privilegeStatus.password });
if (res.code === 200) {
ElMessage.success('密码验证成功,欢迎回来锦恢');
dialogVisible.value = false;
initialise();
} else {
ElMessage.error('密码验证失败,请重新输入');
}
};
onMounted(() => {
dialogVisible.value = true;
});
</script>
<style scoped>
.el-dialog {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
z-index: 9999;
}
</style>

View File

@ -0,0 +1,6 @@
import { reactive } from "vue";
export const privilegeStatus = reactive({
allow: true,
password: ''
});

View File

@ -1,4 +1,4 @@
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import { createRouter, createWebHistory, type RouteRecordRaw } from "vue-router";
const routes: Array<RouteRecordRaw> = [
{
@ -33,7 +33,7 @@ const routes: Array<RouteRecordRaw> = [
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
history: createWebHistory('/'),
routes,
});

View File

@ -1,8 +1,10 @@
import { useMessageBridge } from '@/api/message-bridge';
import { reactive, ref } from 'vue';
import { pinkLog } from '../setting/util';
import { ElMessage } from 'element-plus';
import { OpenMcpSupportPlatform } from '@/api/platform';
import { ElLoading, ElMessage } from 'element-plus';
import { getPlatform, type OpenMcpSupportPlatform } from '@/api/platform';
import { getTour, loadSetting } from '@/hook/setting';
import { loadPanels } from '@/hook/panel';
export const connectionMethods = reactive({
current: 'STDIO',
@ -98,7 +100,7 @@ export async function doConnect(
}
} else {
connectionMethods.current = 'SSE';
connectionArgs.urlString = connectionItem.url;
connectionArgs.urlString = connectionItem.url || '';
if (connectionArgs.urlString.length === 0) {
return;
@ -346,3 +348,36 @@ export async function handleEnvSwitch(enabled: boolean) {
export async function loadEnvVar() {
return await handleEnvSwitch(true);
}
export async function initialise() {
pinkLog('准备请求设置');
const loading = ElLoading.service({
fullscreen: true,
lock: true,
text: 'Loading',
background: 'rgba(0, 0, 0, 0.7)'
});
const platform = getPlatform();
// 加载全局设置
loadSetting();
// 设置环境变量
await loadEnvVar();
// 获取引导状态
await getTour();
// 尝试进行初始化连接
await doConnect({
namespace: platform,
updateCommandString: true
});
// loading panels
await loadPanels();
loading.close();
}

View File

@ -1,4 +1,4 @@
import { ChatStorage } from '@/components/main-panel/chat/chat-box/chat';
import type { ChatStorage } from '@/components/main-panel/chat/chat-box/chat';
import { TaskLoop } from '@/components/main-panel/chat/core/task-loop';
import { llmManager } from './llm';
import { reactive, ref } from 'vue';
@ -26,9 +26,7 @@ export async function makeSimpleTalk() {
// 使用最简单的 hello 来测试
const testMessage = 'hello';
const s1 = ref('');
const s2 = ref([]);
const loop = new TaskLoop(s1, s2);
const loop = new TaskLoop();
const chatStorage: ChatStorage = {
messages: [],
@ -62,7 +60,7 @@ export async function makeSimpleTalk() {
await loop.start(chatStorage, testMessage);
const costTime = (performance.now() - startTime!) / 1000;
const message = chatStorage.messages.at(-1);
const message = chatStorage.messages[chatStorage.messages.length - 1];
console.log(chatStorage.messages);
if (message?.extraInfo) {
@ -72,5 +70,4 @@ export async function makeSimpleTalk() {
simpleTestResult.tps = (total / costTime).toFixed(2);
}
}
}

View File

@ -1,7 +1,7 @@
import { markRaw, reactive } from 'vue';
import { createTab, debugModes, tabs } from '@/components/main-panel/panel';
import { ToolStorage } from '@/components/main-panel/tool/tools';
import { ToolCall } from '@/components/main-panel/chat/chat-box/chat';
import type { ToolStorage } from '@/components/main-panel/tool/tools';
import type { ToolCall } from '@/components/main-panel/chat/chat-box/chat';
import I18n from '@/i18n';
const { t } = I18n.global;

View File

@ -0,0 +1,19 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": [
"env.d.ts",
"src/**/*",
"src/**/*.vue"
],
"exclude": [
"src/**/__tests__/*"
],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"paths": {
"@/*": [
"./src/*"
]
}
}
}

View File

@ -1,41 +1,17 @@
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"strict": true,
"jsx": "preserve",
"moduleResolution": "nodenext",
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"useDefineForClassFields": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
"es2022",
"dom"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}

View File

@ -0,0 +1,20 @@
{
"extends": "@tsconfig/node22/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*",
"eslint.config.*"
],
"compilerOptions": {
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": [
"node"
]
}
}

18
renderer/vite.config.ts Normal file
View File

@ -0,0 +1,18 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})

View File

@ -1,43 +0,0 @@
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
transpileDependencies: true,
publicPath: process.env.NODE_ENV === 'production' ? '' : '/',
configureWebpack: {
optimization: {
splitChunks: false
}
},
chainWebpack: config => {
// 删除所有预设的代码分割规则
config.optimization.delete('splitChunks');
// 确保路由组件同步加载
config.plugins.delete('prefetch');
config.plugins.delete('preload');
// 删除 public 下指定的 css 文件
config.plugin('copy').tap(args => {
args[0].patterns = args[0].patterns.map((pattern) => {
if (pattern.from === "public") {
// 忽略指定的 CSS 文件
pattern.globOptions = {
ignore: [
"vscode.css",
"default-light.css",
"default-dark.css",
],
};
}
return pattern;
});
return args;
});
},
css: {
extract: false
},
devServer: {
port: 8081
}
});

View File

@ -1,22 +0,0 @@
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
module.exports = {
plugins: [
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
new webpack.DefinePlugin({
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false,
})
],
resolve: {
fallback: {
"stream": false
}
},
};

View File

@ -0,0 +1,95 @@
<div align="center">
<img src="./icons/openmcp.png" height="200px"/>
<h3>OpenMCP: 一体化 MCP Server 调试器</h3>
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: #CB81DA; color: white; border-radius: .5em; text-decoration: none;">👉 加入 OpenMCP正式级技术组</a>
<a href="https://discord.gg/af5cfB9a" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: rgb(84, 176, 84); color: white; border-radius: .5em; text-decoration: none;"> 加入 OpenMCP Discord频道</a>
</div>
## 安装
```bash
npm install openmcp-sdk
```
## 使用
文件名main.ts
```typescript
import { TaskLoop } from 'openmcp-sdk/task-loop';
import { TaskLoopAdapter } from 'openmcp-sdk/service';
async function main() {
// 创建适配器,负责通信和 mcp 连接
const adapter = new TaskLoopAdapter();
// 连接 mcp 服务器
await adapter.connectMcpServer({
connectionType: 'STDIO',
command: 'node',
args: [
'~/projects/mcp/servers/src/puppeteer/dist/index.js'
]
});
// 获取工具列表
const tools = await adapter.listTools();
// 创建事件循环驱动器
const taskLoop = new TaskLoop({ adapter });
// 配置改次事件循环使用的大模型
taskLoop.setLlmConfig({
id: 'deepseek',
baseUrl: 'https://api.deepseek.com/v1',
userToken: process.env['DEEPSEEK_API_TOKEN'],
userModel: 'deepseek-chat'
});
// 创建当前事件循环对应的上下文,并且配置当前上下文的设置
const storage = {
messages: [],
settings: {
temperature: 0.7,
enableTools: tools,
systemPrompt: 'you are a clever bot',
contextLength: 20
}
};
// 本次发出的问题
const message = 'hello world';
// 事件循环结束的句柄
taskLoop.registerOnDone(() => {
console.log('taskLoop done');
});
// 事件循环每一次 epoch 开始的句柄
taskLoop.registerOnError((error) => {
console.log('taskLoop error', error);
});
// 事件循环出现 error 时的句柄(出现 error 不一定会停止事件循环)
taskLoop.registerOnEpoch(() => {
console.log('taskLoop epoch');
});
// 开启事件循环
await taskLoop.start(storage, message);
// 打印上下文,最终的回答在 messages.at(-1) 中
console.log(storage.messages);
}
main();
```
star 我们的项目https://github.com/LSTM-Kirigaya/openmcp-client

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -0,0 +1,38 @@
{
"name": "openmcp-sdk",
"version": "0.0.4",
"description": "openmcp-sdk",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://github.com/LSTM-Kirigaya/openmcp-client"
},
"keywords": [
"mcp",
"openmcp",
"openmcp-sdk",
"llm framework",
"agent",
"ai agent"
],
"author": "kirigaya",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/LSTM-Kirigaya/openmcp-client/issues"
},
"homepage": "https://document.kirigaya.cn/blogs/openmcp/main.html",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.10.2",
"@seald-io/nedb": "^4.1.1",
"@vue/server-renderer": "^3.5.13",
"axios": "^1.7.7",
"bson": "^6.8.0",
"openai": "^4.93.0",
"pako": "^2.1.0",
"tesseract.js": "^6.0.1",
"uuid": "^11.1.0",
"ws": "^8.18.1"
}
}

View File

@ -0,0 +1,154 @@
/* eslint-disable */
import type { OpenAI } from 'openai';
export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string };
export interface TaskLoopOptions {
maxEpochs?: number;
maxJsonParseRetry?: number;
adapter?: any;
}
export type Ref<T> = {
value: T;
};
export interface ToolCall {
id?: string;
index?: number;
type: string;
function: {
name: string;
arguments: string;
}
}
export interface ToolCallContent {
type: string;
text: string;
[key: string]: any;
}
export interface ToolCallResult {
state: MessageState;
content: ToolCallContent[];
}
export enum MessageState {
ServerError = 'server internal error',
ReceiveChunkError = 'receive chunk error',
Timeout = 'timeout',
MaxEpochs = 'max epochs',
Unknown = 'unknown error',
Abort = 'abort',
ToolCall = 'tool call failed',
None = 'none',
Success = 'success',
ParseJsonError = 'parse json error'
}
export interface IErrorMssage {
state: MessageState;
msg: string;
}
export interface IDoConversationResult {
stop: boolean;
}
/**
* @description
*/
export class TaskLoop {
private streamingContent;
private streamingToolCalls;
private readonly taskOptions;
private bridge;
private currentChatId;
private onError;
private onChunk;
private onDone;
private onToolCalled;
private onEpoch;
private completionUsage;
private llmConfig;
constructor(taskOptions?: TaskLoopOptions);
private handleChunkDeltaContent;
private handleChunkDeltaToolCalls;
private handleChunkUsage;
private doConversation;
makeChatData(tabStorage: any): ChatCompletionCreateParamsBase | undefined;
abort(): void;
/**
* @description error
* @param handler
*/
registerOnError(handler: (msg: IErrorMssage) => void): void;
registerOnChunk(handler: (chunk: ChatCompletionChunk) => void): void;
/**
* @description chat.completion
* @param handler
*/
registerOnDone(handler: () => void): void;
/**
* @description epoch
* @param handler
*/
registerOnEpoch(handler: () => void): void;
/**
* @description toolcall
* @param handler
*/
registerOnToolCalled(handler: (toolCallResult: ToolCallResult) => ToolCallResult): void;
/**
* @description toolcall
* @param handler
*/
registerOnToolCall(handler: (toolCall: ToolCall) => ToolCall): void;
/**
* @description LLM
*/
getLlmConfig(): any;
/**
* @description LLM nodejs
* @param config
* @example
* setLlmConfig({
* id: 'openai',
* baseUrl: 'https://api.openai.com/v1',
* userToken: 'sk-xxx',
* userModel: 'gpt-3.5-turbo',
* })
*/
setLlmConfig(config: any): void;
/**
* @description epoch
* @param maxEpochs
*/
setMaxEpochs(maxEpochs: number): void;
bindStreaming(content: Ref<string>, toolCalls: Ref<ToolCall[]>): void;
connectToService(): Promise<void>;
/**
* @description DOM
*/
start(tabStorage: any, userMessage: string): Promise<void>;
}
export declare const getToolSchema: any;
export declare const useMessageBridge: any;
export declare const llmManager: any;
export declare const llms: any;
export declare const pinkLog: any;
export declare const redLog: any;
export declare const ElMessage: any;
export declare const handleToolCalls: any;
export declare const getPlatform: any;

View File

@ -0,0 +1,8 @@
module.exports = {
ElMessage: {
error: console.error,
warning: console.warn,
info: console.info,
success: console.log
}
}

3
service/.gitignore vendored
View File

@ -26,4 +26,5 @@ setting.json
*.traineddata
.env
tabs.example-servers_puppeteer.json
tabs.*
update.sh

View File

@ -3,7 +3,7 @@
```mermaid
graph LR
renderer <--ws://localhost:8080--> router
renderer <--ws://localhost:8282--> router
subgraph service
router <--setting/save--> app_service
router <--resources/read--> mcp_service
@ -39,7 +39,7 @@ post_process --tool response--> storage
```mermaid
graph LR
renderer <--ws://localhost:8080--> llm_service
renderer <--ws://localhost:8282--> llm_service
subgraph llm_service_body
llm_service --type image --> handle_image
llm_service --type text --> handle_text

View File

@ -1,77 +0,0 @@
/**
* source: https://gist.github.com/Laci21/9dd074f3a5a461ab04adb7db678534d3
* issue: https://github.com/modelcontextprotocol/typescript-sdk/issues/217
*
* This script fixes the MCP SDK issue with pkce-challenge ES Module
* It replaces the static require with a dynamic import in the auth.js file
*/
const fs = require('fs');
const path = require('path');
// Path to the file that needs patching
const authFilePath = path.resolve(
__dirname,
'node_modules',
'@modelcontextprotocol',
'sdk',
'dist',
'cjs',
'client',
'auth.js'
);
console.log('Checking if MCP SDK patch is needed...');
// Check if the file exists
if (!fs.existsSync(authFilePath)) {
console.error(`Error: File not found at ${authFilePath}`);
console.log('Make sure you have installed @modelcontextprotocol/sdk package');
process.exit(1);
}
// Read the file content
const fileContent = fs.readFileSync(authFilePath, 'utf8');
// Check if the file already contains our patch
if (fileContent.includes('loadPkceChallenge')) {
console.log('MCP SDK is already patched!');
process.exit(0);
}
// Check if the file contains the problematic require
if (!fileContent.includes("require(\"pkce-challenge\")")) {
console.log('The MCP SDK file does not contain the expected require statement.');
console.log('This patch may not be needed or the SDK has been updated.');
process.exit(0);
}
console.log('Applying patch to MCP SDK...');
// The code to replace the problematic require
const requireLine = "const pkce_challenge_1 = __importDefault(require(\"pkce-challenge\"));";
const replacementCode = `let pkce_challenge_1 = { default: null };
async function loadPkceChallenge() {
if (!pkce_challenge_1.default) {
const mod = await import("pkce-challenge");
pkce_challenge_1.default = mod.default;
}
}`;
// Replace the require line
let patchedContent = fileContent.replace(requireLine, replacementCode);
// Replace the function call to add the loading step
const challengeCall = "const challenge = await (0, pkce_challenge_1.default)();";
const replacementCall = "await loadPkceChallenge();\n const challenge = await pkce_challenge_1.default();";
patchedContent = patchedContent.replace(challengeCall, replacementCall);
// Write the patched content back to the file
fs.writeFileSync(authFilePath, patchedContent, 'utf8');
console.log('✅ MCP SDK patched successfully!');
console.log('The patch changes:');
console.log('1. Replaced static require with dynamic import for pkce-challenge');
console.log('2. Added async loading function to handle the import');
console.log('\nYou should now be able to run the application without ESM errors.');

View File

@ -1,5 +1,5 @@
import { requestHandlerStorage } from ".";
import { PostMessageble } from "../hook/adapter";
import type { PostMessageble } from "../hook/adapter";
import { LlmController } from "../llm/llm.controller";
import { ClientController } from "../mcp/client.controller";
import { ConnectController } from "../mcp/connect.controller";

View File

@ -1,9 +1,13 @@
import { WebSocket } from 'ws';
import { EventEmitter } from 'events';
import { routeMessage } from '../common/router';
import { McpOptions } from '../mcp/client.dto';
import { client, connectService } from '../mcp/connect.service';
// WebSocket 消息格式
export interface WebSocketMessage {
command: string;
data: any;
data: any;
}
// 服务器返回的消息格式
@ -63,3 +67,76 @@ export class VSCodeWebViewLike {
};
}
}
export class TaskLoopAdapter {
public emitter: EventEmitter;
private messageHandlers: Set<MessageHandler>;
constructor(option?: any) {
this.emitter = new EventEmitter(option);
this.messageHandlers = new Set();
this.emitter.on('message/renderer', (message: WebSocketMessage) => {
this.messageHandlers.forEach((handler) => handler(message));
});
// 默认需要将监听的消息导入到 routeMessage 中
this.onDidReceiveMessage((message) => {
const { command, data } = message;
routeMessage(command, data, this);
});
}
/**
* @description
* @param message - command args
*/
public postMessage(message: WebSocketMessage): void {
this.emitter.emit('message/service', message);
}
/**
* @description
* @param callback -
* @returns {{ dispose: () => void }} -
*/
public onDidReceiveMessage(callback: MessageHandler): { dispose: () => void } {
this.messageHandlers.add(callback);
return {
dispose: () => this.messageHandlers.delete(callback),
};
}
/**
* @description mcp
* @param mcpOption
*/
public async connectMcpServer(mcpOption: McpOptions) {
const res = await connectService(undefined, mcpOption);
if (res.code === 200) {
console.log('✅ 成功连接 mcp 服务器: ' + res.msg);
const version = client?.getServerVersion();
console.log(version);
} else {
console.error('❌ 连接 mcp 服务器失败:' + res.msg);
}
}
/**
* @description mcp
* @returns
*/
public async listTools() {
const tools = await client?.listTools();
if (tools?.tools) {
return tools.tools.map((tool) => {
const enabledTools = { ...tool, enabled: true };
return enabledTools;
});
}
return [];
}
}

View File

@ -1,5 +1,5 @@
export { routeMessage } from './common/router';
export { VSCodeWebViewLike } from './hook/adapter';
export { VSCodeWebViewLike, TaskLoopAdapter } from './hook/adapter';
export { setVscodeWorkspace, setRunningCWD } from './hook/setting';
// TODO: 更加规范
export { client } from './mcp/connect.service';

View File

@ -6,7 +6,9 @@ export class LlmController {
@Controller('llm/chat/completions')
async chatCompletion(client: RequestClientType, data: any, webview: PostMessageble) {
if (!client) {
let { tools = [] } = data;
if (tools.length > 0 && !client) {
return {
code: 501,
msg:'mcp client 尚未连接'

View File

@ -26,7 +26,7 @@ const logger = pino({
});
export type MessageHandler = (message: VSCodeMessage) => void;
const wss = new (WebSocket as any).Server({ port: 8080 });
const wss = new (WebSocket as any).Server({ port: 8282 });
interface IStdioLaunchSignature {
type: 'stdio';
@ -98,7 +98,7 @@ function updateConnectionOption(data: any) {
const devHome = path.join(__dirname, '..', '..');
setRunningCWD(devHome);
wss.on('connection', ws => {
wss.on('connection', (ws: any) => {
// 仿造 webview 进行统一接口访问
const webview = new VSCodeWebViewLike(ws);

View File

@ -38,6 +38,16 @@ export async function connectService(
try {
console.log('ready to connect', option);
// 对于特殊表示的路径,进行特殊的支持
if (option.args) {
option.args = option.args.map(arg => {
if (arg.startsWith('~/')) {
return arg.replace('~', process.env.HOME || '');
}
return arg;
});
}
client = await connect(option);
const connectResult = {
code: 200,
@ -47,6 +57,10 @@ export async function connectService(
return connectResult;
} catch (error) {
console.log('meet error');
console.log(error);
// TODO: 这边获取到的 error 不够精致,如何才能获取到更加精准的错误
// 比如 error: Failed to spawn: `server.py`
// Caused by: No such file or directory (os error 2)

200
service/src/server.ts Normal file
View File

@ -0,0 +1,200 @@
import WebSocket from 'ws';
import pino from 'pino';
import { routeMessage } from './common/router';
import { VSCodeWebViewLike } from './hook/adapter';
import path from 'node:path';
import * as fs from 'node:fs';
import { setRunningCWD } from './hook/setting';
import { exit } from 'node:process';
export interface VSCodeMessage {
command: string;
data?: unknown;
callbackId?: string;
}
const logger = pino({
transport: {
target: 'pino-pretty', // 启用 pino-pretty
options: {
colorize: true, // 开启颜色
levelFirst: true, // 先打印日志级别
translateTime: 'SYS:yyyy-mm-dd HH:MM:ss', // 格式化时间
ignore: 'pid,hostname', // 忽略部分字段
}
}
});
export type MessageHandler = (message: VSCodeMessage) => void;
const wss = new (WebSocket as any).Server({ port: 8282 });
interface IStdioLaunchSignature {
type: 'stdio';
commandString: string;
cwd: string;
}
interface ISSELaunchSignature {
type:'sse';
url: string;
oauth: string;
}
export type ILaunchSigature = IStdioLaunchSignature | ISSELaunchSignature;
function refreshConnectionOption(envPath: string) {
const defaultOption = {
type:'stdio',
command: 'mcp',
args: ['run', 'main.py'],
cwd: '../server'
};
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4));
return defaultOption;
}
function getInitConnectionOption() {
const envPath = path.join(__dirname, '..', '.env');
if (!fs.existsSync(envPath)) {
return refreshConnectionOption(envPath);
}
try {
const option = JSON.parse(fs.readFileSync(envPath, 'utf-8'));
return option;
} catch (error) {
logger.error('读取 .env 配置文件');
return refreshConnectionOption(envPath);
}
}
if (!fs.existsSync(path.join(__dirname, '..', '.env.website.local'))) {
console.log('.env.website.local 不存在!');
exit(0);
}
const authPassword = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '.env.website.local'), 'utf-8')).password;
function updateConnectionOption(data: any) {
const envPath = path.join(__dirname, '..', '.env');
if (data.connectionType === 'STDIO') {
const connectionItem = {
type: 'stdio',
command: data.command,
args: data.args,
cwd: data.cwd.replace(/\\/g, '/')
};
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
} else {
const connectionItem = {
type: 'sse',
url: data.url,
oauth: data.oauth
};
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
}
}
const devHome = path.join(__dirname, '..', '..');
setRunningCWD(devHome);
wss.on('connection', (ws: any) => {
// 仿造 webview 进行统一接口访问
const webview = new VSCodeWebViewLike(ws);
// 先发送成功建立的消息
webview.postMessage({
command: 'hello',
data: {
version: '0.0.1',
name: '消息桥连接完成'
}
});
const option = getInitConnectionOption();
// 注册消息接受的管线
webview.onDidReceiveMessage(message => {
logger.info(`command: [${message.command || 'No Command'}]`);
const { command, data, password } = message;
console.log(command, data);
if (command === 'ciallo') {
if (data.password === authPassword) {
webview.postMessage({
command,
data: {
code: 200,
msg: 'ciallo'
}
});
} else {
webview.postMessage({
command,
data: {
code: 403,
msg: '没有权限'
}
});
}
return;
}
if (password !== authPassword) {
webview.postMessage({
command,
data: {
code: 403,
msg: '没有权限'
}
});
return;
}
switch (command) {
case 'web/launch-signature':
const launchResultMessage: ILaunchSigature = option.type === 'stdio' ?
{
type: 'stdio',
commandString: option.command + ' ' + option.args.join(' '),
cwd: option.cwd || ''
} :
{
type: 'sse',
url: option.url,
oauth: option.oauth || ''
};
const launchResult = {
code: 200,
msg: launchResultMessage
};
webview.postMessage({
command: 'web/launch-signature',
data: launchResult
});
break;
case 'web/update-connection-sigature':
updateConnectionOption(data);
break;
default:
routeMessage(command, data, webview);
break;
}
});
});

View File

@ -17,7 +17,6 @@ export class SettingController {
@Controller('setting/load')
async loadSetting(client: RequestClientType, data: any, webview: PostMessageble) {
console.log('enter loading');
const config = loadSetting();
return {
@ -41,7 +40,6 @@ export class SettingController {
@Controller('setting/get-tour')
async getTourController(client: RequestClientType, data: any, webview: PostMessageble) {
console.log('enter');
const { userHasReadGuide } = getTour();

View File

@ -1,175 +0,0 @@
{
"currentIndex": 2,
"tabs": [
{
"name": "资源",
"icon": "icon-file",
"type": "blank",
"componentIndex": 0,
"storage": {
"formData": {}
}
},
{
"name": "工具",
"icon": "icon-tool",
"type": "blank",
"componentIndex": 2,
"storage": {
"currentToolName": "image_crawler",
"formData": {
"key_word": "",
"image_num": 0
}
}
},
{
"name": "交互测试",
"icon": "icon-robot",
"type": "blank",
"componentIndex": 3,
"storage": {
"messages": [
{
"role": "user",
"content": "帮我找几张 明日方舟 m3 的图片,渲染出来",
"extraInfo": {
"created": 1745730609368,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_660r5ehyhh86cjtqnako0mnt",
"index": 0,
"type": "function",
"function": {
"name": "image_crawler",
"arguments": "{\"key_word\":\"明日方舟 m3\",\"image_num\":5}"
}
}
],
"extraInfo": {
"created": 1745730610263,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "tool",
"tool_call_id": "call_660r5ehyhh86cjtqnako0mnt",
"content": [
{
"type": "text",
"text": "[ImageResult(url='https://gd-hbimg.huaban.com/0c97d53a94f333fad66ca70f32601d8c34c2d4db8f53e0-netl2f_fw658', title='', source='bing'), ImageResult(url='https://ts1.tc.mm.bing.net/th/id/R-C.db1a4364345d8b082d4de48cae32ae05?rik=rhE9iKxzhvwzDg', title='', source='bing'), ImageResult(url='https://gd-hbimg.huaban.com/a4b669d3e68da3ee91a12afe7a0bd689038bdc262aece-yasxcI_fw658', title='', source='bing'), ImageResult(url='https://ts1.tc.mm.bing.net/th/id/R-C.d9dc5fcd4b628e805d4b6a0edb0f9a48?rik=RiUbfVRpViaT%2fw', title='', source='bing'), ImageResult(url='https://gd-hbimg.huaban.com/205d12dbf22aec83ff34bd533616575f0939b561136c6-jwF7XH_fw658', title='', source='bing')]"
}
],
"extraInfo": {
"created": 1745730610855,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "assistant",
"content": "以下是根据关键词“明日方舟 m3”找到的图片\n\n1. ![Image 1](https://gd-hbimg.huaban.com/0c97d53a94f333fad66ca70f32601d8c34c2d4db8f53e0-netl2f_fw658)\n2. ![Image 2](https://ts1.tc.mm.bing.net/th/id/R-C.db1a4364345d8b082d4de48cae32ae05?rik=rhE9iKxzhvwzDg)\n3. ![Image 3](https://gd-hbimg.huaban.com/a4b669d3e68da3ee91a12afe7a0bd689038bdc262aece-yasxcI_fw658)\n4. ![Image 4](https://ts1.tc.mm.bing.net/th/id/R-C.d9dc5fcd4b628e805d4b6a0edb0f9a48?rik=RiUbfVRpViaT%2fw)\n5. ![Image 5](https://gd-hbimg.huaban.com/205d12dbf22aec83ff34bd533616575f0939b561136c6-jwF7XH_fw658)\n\n你可以点击链接查看这些图片。",
"extraInfo": {
"created": 1745730615968,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "user",
"content": "图源是哪里",
"extraInfo": {
"created": 1745730806510,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "assistant",
"content": "这些图片的来源是Bing搜索引擎和Huaban网站。具体来源如下\n\n1. **Bing**:\n - [Image 2](https://ts1.tc.mm.bing.net/th/id/R-C.db1a4364345d8b082d4de48cae32ae05?rik=rhE9iKxzhvwzDg)\n - [Image 4](https://ts1.tc.mm.bing.net/th/id/R-C.d9dc5fcd4b628e805d4b6a0edb0f9a48?rik=RiUbfVRpViaT%2fw)\n\n2. **Huaban**:\n - [Image 1](https://gd-hbimg.huaban.com/0c97d53a94f333fad66ca70f32601d8c34c2d4db8f53e0-netl2f_fw658)\n - [Image 3](https://gd-hbimg.huaban.com/a4b669d3e68da3ee91a12afe7a0bd689038bdc262aece-yasxcI_fw658)\n - [Image 5](https://gd-hbimg.huaban.com/205d12dbf22aec83ff34bd533616575f0939b561136c6-jwF7XH_fw658)\n\n你可以通过点击链接查看图片的详细信息。",
"extraInfo": {
"created": 1745730812395,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "user",
"content": "帮我找几张 明日方舟 白面鸮 的图片,渲染出来",
"extraInfo": {
"created": 1745730909927,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_ho74cop478w5gbfc0jnuo12f",
"index": 0,
"type": "function",
"function": {
"name": "image_crawler",
"arguments": "{\"key_word\":\"明日方舟 白面鸮\",\"image_num\":5}"
}
}
],
"extraInfo": {
"created": 1745730910906,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "tool",
"tool_call_id": "call_ho74cop478w5gbfc0jnuo12f",
"content": [
{
"type": "text",
"text": "[ImageResult(url='https://c-ssl.duitang.com/uploads/item/201910/22/20191022190241_dixsc.thumb.1000_0.jpg', title='', source='bing'), ImageResult(url='https://img-baofun.zhhainiao.com/pcwallpaper_ugc_mobile/live/e06866505212ddcce0dabf9afebce225.mp4.jpg', title='', source='bing'), ImageResult(url='https://c-ssl.dtstatic.com/uploads/item/202007/15/20200715183732_iomsj.thumb.1000_0.jpg', title='', source='bing'), ImageResult(url='https://img-baofun.zhhainiao.com/pcwallpaper_ugc_mobile/live/710b67dfa125248f816d63617f551452_preview.jpg', title='', source='bing'), ImageResult(url='https://n.sinaimg.cn/sinacn10122/670/w400h270/20200104/db75-imrkkfx7540303.jpg', title='', source='bing')]"
}
],
"extraInfo": {
"created": 1745730911376,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
},
{
"role": "assistant",
"content": "以下是根据关键词“明日方舟 白面鸮”找到的图片:\n\n1. ![Image 1](https://c-ssl.duitang.com/uploads/item/201910/22/20191022190241_dixsc.thumb.1000_0.jpg)\n2. ![Image 2](https://img-baofun.zhhainiao.com/pcwallpaper_ugc_mobile/live/e06866505212ddcce0dabf9afebce225.mp4.jpg)\n3. ![Image 3](https://c-ssl.dtstatic.com/uploads/item/202007/15/20200715183732_iomsj.thumb.1000_0.jpg)\n4. ![Image 4](https://img-baofun.zhhainiao.com/pcwallpaper_ugc_mobile/live/710b67dfa125248f816d63617f551452_preview.jpg)\n5. ![Image 5](https://n.sinaimg.cn/sinacn10122/670/w400h270/20200104/db75-imrkkfx7540303.jpg)\n\n你可以点击链接查看这些图片。",
"extraInfo": {
"created": 1745730916072,
"state": "success",
"serverName": "Huoshan DeepSeek"
}
}
],
"settings": {
"modelIndex": 8,
"enableTools": [
{
"name": "image_crawler",
"description": "根据关键词从指定搜索引擎爬取图片",
"enabled": true
}
],
"enableWebSearch": false,
"temperature": 0.7,
"contextLength": 10,
"systemPrompt": ""
}
}
}
]
}

View File

@ -0,0 +1,58 @@
{
"currentIndex": 0,
"tabs": [
{
"name": "交互测试",
"icon": "icon-robot",
"type": "blank",
"componentIndex": 3,
"storage": {
"messages": [],
"settings": {
"modelIndex": 7,
"enableTools": [
{
"name": "k_navigate",
"description": "Navigate to a URL",
"enabled": true
},
{
"name": "k_screenshot",
"description": "Take a screenshot of the current page or a specific element",
"enabled": true
},
{
"name": "k_click",
"description": "Click an element on the page",
"enabled": true
},
{
"name": "k_fill",
"description": "Fill out an input field",
"enabled": true
},
{
"name": "k_select",
"description": "Select an element on the page with Select tag",
"enabled": true
},
{
"name": "k_hover",
"description": "Hover an element on the page",
"enabled": true
},
{
"name": "k_evaluate",
"description": "Execute JavaScript in the browser console",
"enabled": true
}
],
"enableWebSearch": false,
"temperature": 0.7,
"contextLength": 20,
"systemPrompt": ""
}
}
}
]
}

View File

@ -1,12 +0,0 @@
{
"currentIndex": 0,
"tabs": [
{
"name": "Blank test 1",
"icon": "icon-blank",
"type": "blank",
"componentIndex": -1,
"storage": {}
}
]
}

View File

@ -1,38 +0,0 @@
{
"currentIndex": 0,
"tabs": [
{
"name": "资源",
"icon": "icon-file",
"type": "blank",
"componentIndex": 0,
"storage": {
"formData": {},
"currentType": "resource",
"currentResourceName": "network_log",
"lastResourceReadResponse": {
"contents": [
{
"uri": "network://log",
"mimeType": "text/plain",
"text": "Response from ..."
}
]
}
}
},
{
"name": "工具",
"icon": "icon-tool",
"type": "blank",
"componentIndex": 2,
"storage": {
"formData": {
"a": 0,
"b": 0
},
"currentToolName": "add"
}
}
]
}

View File

@ -1,31 +0,0 @@
const path = require('path');
module.exports = {
entry: './src/index.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
libraryTarget: 'commonjs2' // 使用 commonjs2 模块系统
},
resolve: {
extensions: ['.ts', '.js'],
fallback: {
bufferutil: false,
'utf-8-validate': false
}
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: 'ts-loader'
}
]
},
target: 'node', // 指定目标环境为 Node.js
mode: 'production',
optimization: {
minimize: true
}
};

View File

@ -109,7 +109,7 @@ export async function acquireInstalledConnection(): Promise<IConnectionItem | un
// 获取 url
const url = await vscode.window.showInputBox({
prompt: '请输入连接的 URL',
placeHolder: '例如: https://127.0.0.1:8080'
placeHolder: '例如: https://127.0.0.1:8282'
});
if (!url) {

View File

@ -59,7 +59,7 @@ export async function acquireUserCustomConnection(): Promise<IConnectionItem | u
// 获取 url
const url = await vscode.window.showInputBox({
prompt: '请输入连接的 URL',
placeHolder: '例如: https://127.0.0.1:8080'
placeHolder: '例如: https://127.0.0.1:8282'
});
if (!url) {

View File

@ -1,6 +1,7 @@
const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
mode: 'development', // 设置为 development 模式
@ -34,10 +35,15 @@ module.exports = {
],
},
optimization: {
minimize: false, // 禁用代码压缩
minimize: true, // Enable code compression
minimizer: [
new TerserPlugin({
extractComments: false, // 禁用提取许可证文件
extractComments: false, // Disable extracting license files
terserOptions: {
compress: {
drop_console: true, // Remove all console.* calls
},
},
}),
],
},
@ -53,9 +59,17 @@ module.exports = {
}
}
}),
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, '../resources/openmcp-sdk-release'),
to: path.resolve(__dirname, '../openmcp-sdk')
}
]
})
],
externals: {
vue: 'vue', // 不打包 vue 库
'element-plus': './tool.js'
'element-plus': './tools.js'
},
};