opencode-morphllm 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitleaks.toml +6 -0
- package/.husky/pre-commit +3 -1
- package/.prettierignore +1 -0
- package/.prettierrc +1 -1
- package/README.md +7 -2
- package/bun.lock +23 -0
- package/bunfig.toml +4 -0
- package/dist/index.js +11 -12
- package/dist/morph/mcps.js +11 -11
- package/dist/morph/mcps.test.js +32 -32
- package/dist/morph/router.d.ts +16 -21
- package/dist/morph/router.js +51 -55
- package/dist/morph/router.test.js +230 -231
- package/dist/shared/config.d.ts +16 -18
- package/dist/shared/config.js +62 -64
- package/dist/shared/config.test.js +183 -186
- package/dist/shared/opencode-config-dir.d.ts +18 -8
- package/dist/shared/opencode-config-dir.js +93 -47
- package/dist/shared/opencode-config-dir.test.d.ts +1 -0
- package/dist/shared/opencode-config-dir.test.js +310 -0
- package/package.json +3 -2
- package/src/index.ts +1 -2
- package/src/morph/router.test.ts +3 -2
- package/src/morph/router.ts +8 -3
- package/src/shared/config.test.ts +18 -6
- package/src/shared/config.ts +5 -5
- package/src/shared/opencode-config-dir.test.ts +404 -0
- package/src/shared/opencode-config-dir.ts +90 -11
package/.gitleaks.toml
ADDED
package/.husky/pre-commit
CHANGED
package/.prettierignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dist/
|
package/.prettierrc
CHANGED
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# MorphLLM OpenCode Plugin
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/opencode-morphllm)
|
|
4
|
+
[](https://www.npmjs.com/package/opencode-morphllm)
|
|
5
|
+
|
|
3
6
|
This is an OpenCode Plugin for [MorphLLM](https://morphllm.com/). This plugin just adds in `edit_file` and `warpgrep_codebase_search` from MorphLLM to your agent configs as well as the intelligent model router for choosing different models based on the difficulty of the prompt.
|
|
4
7
|
|
|
5
8
|
Github: https://github.com/VitoLin/opencode-morphllm
|
|
@@ -27,7 +30,8 @@ Example configs:
|
|
|
27
30
|
"MORPH_MODEL_EASY": "github-copilot/gpt-5-mini",
|
|
28
31
|
"MORPH_MODEL_MEDIUM": "opencode/minimax-m2.1-free",
|
|
29
32
|
"MORPH_MODEL_HARD": "github-copilot/gemini-2.5-pro",
|
|
30
|
-
"MORPH_ROUTER_ENABLED": true
|
|
33
|
+
"MORPH_ROUTER_ENABLED": true,
|
|
34
|
+
"MORPH_ROUTER_PROMPT_CACHING_AWARE": true
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
```
|
|
@@ -40,7 +44,8 @@ Legacy format (still supported):
|
|
|
40
44
|
"MORPH_MODEL_EASY": "github-copilot/gpt-5-mini",
|
|
41
45
|
"MORPH_MODEL_MEDIUM": "opencode/minimax-m2.1-free",
|
|
42
46
|
"MORPH_MODEL_HARD": "github-copilot/gemini-2.5-pro",
|
|
43
|
-
"MORPH_ROUTER_ENABLED": true
|
|
47
|
+
"MORPH_ROUTER_ENABLED": true,
|
|
48
|
+
"MORPH_ROUTER_PROMPT_CACHING_AWARE": true
|
|
44
49
|
}
|
|
45
50
|
```
|
|
46
51
|
|
package/bun.lock
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"bun-types": "^1.3.6",
|
|
13
|
+
"coverage-badges-cli": "^2.2.0",
|
|
13
14
|
"husky": "^8.0.3",
|
|
14
15
|
"lint-staged": "^15.2.0",
|
|
15
16
|
"prettier": "^3.2.5",
|
|
@@ -34,6 +35,12 @@
|
|
|
34
35
|
|
|
35
36
|
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
36
37
|
|
|
38
|
+
"@types/fs-extra": ["@types/fs-extra@11.0.4", "", { "dependencies": { "@types/jsonfile": "*", "@types/node": "*" } }, "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ=="],
|
|
39
|
+
|
|
40
|
+
"@types/jsonfile": ["@types/jsonfile@6.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ=="],
|
|
41
|
+
|
|
42
|
+
"@types/minimist": ["@types/minimist@1.2.5", "", {}, "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag=="],
|
|
43
|
+
|
|
37
44
|
"@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
|
|
38
45
|
|
|
39
46
|
"@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
|
|
@@ -62,6 +69,8 @@
|
|
|
62
69
|
|
|
63
70
|
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
|
64
71
|
|
|
72
|
+
"badgen": ["badgen@3.2.3", "", {}, "sha512-svDuwkc63E/z0ky3drpUppB83s/nlgDciH9m+STwwQoWyq7yCgew1qEfJ+9axkKdNq7MskByptWUN9j1PGMwFA=="],
|
|
73
|
+
|
|
65
74
|
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
|
66
75
|
|
|
67
76
|
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
|
@@ -92,6 +101,8 @@
|
|
|
92
101
|
|
|
93
102
|
"commander": ["commander@13.1.0", "", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="],
|
|
94
103
|
|
|
104
|
+
"coverage-badges-cli": ["coverage-badges-cli@2.2.0", "", { "dependencies": { "@types/fs-extra": "~11.0.0", "@types/minimist": "~1.2.2", "badgen": "~3.2.3", "fs-extra": "~11.2.0", "lodash.get": "^4.4.2", "mini-svg-data-uri": "^1.4.4", "minimist": "~1.2.5" }, "bin": { "coverage-badges": "bin/cli", "coverage-badges-cli": "bin/cli" } }, "sha512-7E7TtxiybFkK5/FRkzt4WTNZkopEv+hra7I7k52fnOD0YBweCnmV4b0FCCEvnO8567e+xCEZ3AdbIfeVlkh+hg=="],
|
|
105
|
+
|
|
95
106
|
"crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="],
|
|
96
107
|
|
|
97
108
|
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
|
@@ -144,6 +155,8 @@
|
|
|
144
155
|
|
|
145
156
|
"formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
|
|
146
157
|
|
|
158
|
+
"fs-extra": ["fs-extra@11.2.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw=="],
|
|
159
|
+
|
|
147
160
|
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
|
148
161
|
|
|
149
162
|
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
|
|
@@ -156,6 +169,8 @@
|
|
|
156
169
|
|
|
157
170
|
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
|
158
171
|
|
|
172
|
+
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
|
173
|
+
|
|
159
174
|
"has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
|
|
160
175
|
|
|
161
176
|
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
|
@@ -196,12 +211,16 @@
|
|
|
196
211
|
|
|
197
212
|
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
|
|
198
213
|
|
|
214
|
+
"jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
|
|
215
|
+
|
|
199
216
|
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
|
|
200
217
|
|
|
201
218
|
"lint-staged": ["lint-staged@15.5.2", "", { "dependencies": { "chalk": "^5.4.1", "commander": "^13.1.0", "debug": "^4.4.0", "execa": "^8.0.1", "lilconfig": "^3.1.3", "listr2": "^8.2.5", "micromatch": "^4.0.8", "pidtree": "^0.6.0", "string-argv": "^0.3.2", "yaml": "^2.7.0" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w=="],
|
|
202
219
|
|
|
203
220
|
"listr2": ["listr2@8.3.3", "", { "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" } }, "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ=="],
|
|
204
221
|
|
|
222
|
+
"lodash.get": ["lodash.get@4.4.2", "", {}, "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="],
|
|
223
|
+
|
|
205
224
|
"log-update": ["log-update@6.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="],
|
|
206
225
|
|
|
207
226
|
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
|
@@ -220,6 +239,8 @@
|
|
|
220
239
|
|
|
221
240
|
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
|
|
222
241
|
|
|
242
|
+
"mini-svg-data-uri": ["mini-svg-data-uri@1.4.4", "", { "bin": { "mini-svg-data-uri": "cli.js" } }, "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg=="],
|
|
243
|
+
|
|
223
244
|
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
|
224
245
|
|
|
225
246
|
"minimisted": ["minimisted@2.0.1", "", { "dependencies": { "minimist": "^1.2.5" } }, "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA=="],
|
|
@@ -304,6 +325,8 @@
|
|
|
304
325
|
|
|
305
326
|
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
|
306
327
|
|
|
328
|
+
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
|
|
329
|
+
|
|
307
330
|
"web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
|
|
308
331
|
|
|
309
332
|
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
package/bunfig.toml
ADDED
package/dist/index.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { createBuiltinMcps } from './morph/mcps';
|
|
2
2
|
import { createModelRouterHook } from './morph/router';
|
|
3
|
-
import { MORPH_ROUTER_ENABLED } from './shared/config';
|
|
4
3
|
const MorphOpenCodePlugin = async () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
const builtinMcps = createBuiltinMcps();
|
|
5
|
+
const routerHook = createModelRouterHook();
|
|
6
|
+
return {
|
|
7
|
+
config: async (currentConfig) => {
|
|
8
|
+
currentConfig.mcp = {
|
|
9
|
+
...currentConfig.mcp,
|
|
10
|
+
...builtinMcps,
|
|
11
|
+
};
|
|
12
|
+
},
|
|
13
|
+
...routerHook,
|
|
14
|
+
};
|
|
16
15
|
};
|
|
17
16
|
export default MorphOpenCodePlugin;
|
package/dist/morph/mcps.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { API_KEY } from '../shared/config';
|
|
2
2
|
export function createBuiltinMcps() {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
return {
|
|
4
|
+
morph_mcp: {
|
|
5
|
+
type: 'local',
|
|
6
|
+
command: ['npx', '-y', '@morphllm/morphmcp'],
|
|
7
|
+
environment: {
|
|
8
|
+
MORPH_API_KEY: API_KEY,
|
|
9
|
+
ENABLED_TOOLS: 'edit_file,warpgrep_codebase_search',
|
|
10
|
+
},
|
|
11
|
+
enabled: true,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
14
|
}
|
|
15
15
|
export default createBuiltinMcps;
|
package/dist/morph/mcps.test.js
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'bun:test';
|
|
2
2
|
vi.mock('../shared/config', () => ({
|
|
3
|
-
|
|
3
|
+
API_KEY: 'test-api-key-123',
|
|
4
4
|
}));
|
|
5
5
|
import { createBuiltinMcps } from './mcps';
|
|
6
6
|
describe('mcps.ts', () => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
describe('createBuiltinMcps', () => {
|
|
8
|
+
it('should create morph_mcp configuration', () => {
|
|
9
|
+
const mcps = createBuiltinMcps();
|
|
10
|
+
expect(mcps).toHaveProperty('morph_mcp');
|
|
11
|
+
expect(mcps.morph_mcp.type).toBe('local');
|
|
12
|
+
expect(mcps.morph_mcp.enabled).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
it('should set correct command for morph_mcp', () => {
|
|
15
|
+
const mcps = createBuiltinMcps();
|
|
16
|
+
expect(mcps.morph_mcp.command).toEqual([
|
|
17
|
+
'npx',
|
|
18
|
+
'-y',
|
|
19
|
+
'@morphllm/morphmcp',
|
|
20
|
+
]);
|
|
21
|
+
});
|
|
22
|
+
it('should set MORPH_API_KEY in environment', () => {
|
|
23
|
+
const mcps = createBuiltinMcps();
|
|
24
|
+
const env = mcps.morph_mcp.environment;
|
|
25
|
+
expect(env).toBeDefined();
|
|
26
|
+
expect(env?.MORPH_API_KEY).toBe('test-api-key-123');
|
|
27
|
+
});
|
|
28
|
+
it('should set ENABLED_TOOLS environment variable', () => {
|
|
29
|
+
const mcps = createBuiltinMcps();
|
|
30
|
+
const env = mcps.morph_mcp.environment;
|
|
31
|
+
expect(env).toBeDefined();
|
|
32
|
+
expect(env?.ENABLED_TOOLS).toBe('edit_file,warpgrep_codebase_search');
|
|
33
|
+
});
|
|
34
|
+
it('should only create morph_mcp key', () => {
|
|
35
|
+
const mcps = createBuiltinMcps();
|
|
36
|
+
expect(Object.keys(mcps)).toEqual(['morph_mcp']);
|
|
37
|
+
});
|
|
13
38
|
});
|
|
14
|
-
it('should set correct command for morph_mcp', () => {
|
|
15
|
-
const mcps = createBuiltinMcps();
|
|
16
|
-
expect(mcps.morph_mcp.command).toEqual([
|
|
17
|
-
'npx',
|
|
18
|
-
'-y',
|
|
19
|
-
'@morphllm/morphmcp',
|
|
20
|
-
]);
|
|
21
|
-
});
|
|
22
|
-
it('should set MORPH_API_KEY in environment', () => {
|
|
23
|
-
const mcps = createBuiltinMcps();
|
|
24
|
-
const env = mcps.morph_mcp.environment;
|
|
25
|
-
expect(env).toBeDefined();
|
|
26
|
-
expect(env?.MORPH_API_KEY).toBe('test-api-key-123');
|
|
27
|
-
});
|
|
28
|
-
it('should set ENABLED_TOOLS environment variable', () => {
|
|
29
|
-
const mcps = createBuiltinMcps();
|
|
30
|
-
const env = mcps.morph_mcp.environment;
|
|
31
|
-
expect(env).toBeDefined();
|
|
32
|
-
expect(env?.ENABLED_TOOLS).toBe('edit_file,warpgrep_codebase_search');
|
|
33
|
-
});
|
|
34
|
-
it('should only create morph_mcp key', () => {
|
|
35
|
-
const mcps = createBuiltinMcps();
|
|
36
|
-
expect(Object.keys(mcps)).toEqual(['morph_mcp']);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
39
|
});
|
package/dist/morph/router.d.ts
CHANGED
|
@@ -1,27 +1,22 @@
|
|
|
1
1
|
import type { Part, UserMessage } from '@opencode-ai/sdk';
|
|
2
2
|
import type { RouterInput, RawRouterResult } from '@morphllm/morphsdk';
|
|
3
3
|
export declare function createModelRouterHook(): {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
parts: Part[];
|
|
19
|
-
}
|
|
20
|
-
) => Promise<void>;
|
|
4
|
+
'chat.message': (input: {
|
|
5
|
+
sessionID: string;
|
|
6
|
+
agent?: string;
|
|
7
|
+
model?: {
|
|
8
|
+
providerID: string;
|
|
9
|
+
modelID: string;
|
|
10
|
+
};
|
|
11
|
+
messageID?: string;
|
|
12
|
+
variant?: string;
|
|
13
|
+
classify?: (args: RouterInput) => Promise<RawRouterResult>;
|
|
14
|
+
}, output: {
|
|
15
|
+
message: UserMessage;
|
|
16
|
+
parts: Part[];
|
|
17
|
+
}) => Promise<void>;
|
|
21
18
|
};
|
|
22
|
-
export declare function extractPromptText(
|
|
23
|
-
parts: Array<{
|
|
19
|
+
export declare function extractPromptText(parts: Array<{
|
|
24
20
|
type: string;
|
|
25
21
|
text?: string;
|
|
26
|
-
|
|
27
|
-
): string;
|
|
22
|
+
}>): string;
|
package/dist/morph/router.js
CHANGED
|
@@ -1,69 +1,65 @@
|
|
|
1
1
|
import { MorphClient } from '@morphllm/morphsdk';
|
|
2
|
-
import {
|
|
3
|
-
API_KEY,
|
|
4
|
-
MORPH_MODEL_EASY,
|
|
5
|
-
MORPH_MODEL_MEDIUM,
|
|
6
|
-
MORPH_MODEL_HARD,
|
|
7
|
-
MORPH_MODEL_DEFAULT,
|
|
8
|
-
MORPH_ROUTER_ONLY_FIRST_MESSAGE,
|
|
9
|
-
} from '../shared/config';
|
|
2
|
+
import { API_KEY, MORPH_MODEL_EASY, MORPH_MODEL_MEDIUM, MORPH_MODEL_HARD, MORPH_MODEL_DEFAULT, MORPH_ROUTER_PROMPT_CACHING_AWARE, MORPH_ROUTER_ENABLED, } from '../shared/config';
|
|
10
3
|
// Lazy initialization to allow mocking in tests
|
|
11
4
|
let morph = null;
|
|
12
5
|
const sessionsWithModelSelected = new Set();
|
|
13
6
|
function getMorphClient() {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
if (!morph) {
|
|
8
|
+
morph = new MorphClient({ apiKey: API_KEY });
|
|
9
|
+
}
|
|
10
|
+
return morph;
|
|
18
11
|
}
|
|
19
12
|
function parseModel(s) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
if (!s)
|
|
14
|
+
return { providerID: '', modelID: '' };
|
|
15
|
+
const [providerID = '', modelID = ''] = s.split('/');
|
|
16
|
+
return { providerID, modelID };
|
|
23
17
|
}
|
|
24
18
|
function pickModelForDifficulty(difficulty) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
19
|
+
const key = String(difficulty).toLowerCase();
|
|
20
|
+
switch (key) {
|
|
21
|
+
case 'easy':
|
|
22
|
+
return parseModel(MORPH_MODEL_EASY);
|
|
23
|
+
case 'medium':
|
|
24
|
+
return parseModel(MORPH_MODEL_MEDIUM);
|
|
25
|
+
case 'hard':
|
|
26
|
+
return parseModel(MORPH_MODEL_HARD);
|
|
27
|
+
default:
|
|
28
|
+
return parseModel(MORPH_MODEL_DEFAULT);
|
|
29
|
+
}
|
|
36
30
|
}
|
|
37
31
|
export function createModelRouterHook() {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
32
|
+
return {
|
|
33
|
+
'chat.message': async (input, output) => {
|
|
34
|
+
input.model = input.model ?? { providerID: '', modelID: '' };
|
|
35
|
+
if (!MORPH_ROUTER_ENABLED) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (MORPH_ROUTER_PROMPT_CACHING_AWARE) {
|
|
39
|
+
if (sessionsWithModelSelected.has(input.sessionID)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const promptText = extractPromptText(output.parts);
|
|
44
|
+
const classifier = input.classify ??
|
|
45
|
+
((args) => getMorphClient().routers.raw.classify(args));
|
|
46
|
+
const classification = await classifier({
|
|
47
|
+
input: promptText,
|
|
48
|
+
});
|
|
49
|
+
const chosen = pickModelForDifficulty(classification?.difficulty);
|
|
50
|
+
const finalProviderID = chosen.providerID || input.model.providerID;
|
|
51
|
+
const finalModelID = chosen.modelID || input.model.modelID;
|
|
52
|
+
input.model.providerID = finalProviderID;
|
|
53
|
+
input.model.modelID = finalModelID;
|
|
54
|
+
if (MORPH_ROUTER_ENABLED && MORPH_ROUTER_PROMPT_CACHING_AWARE) {
|
|
55
|
+
sessionsWithModelSelected.add(input.sessionID);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
63
59
|
}
|
|
64
60
|
export function extractPromptText(parts) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
return parts
|
|
62
|
+
.filter((p) => p.type === 'text')
|
|
63
|
+
.map((p) => p.text || '')
|
|
64
|
+
.join(' ');
|
|
69
65
|
}
|