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
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { getOpenCodeConfigDir, getOpenCodeConfigPaths, isDevBuild, detectExistingConfigDir, TAURI_APP_IDENTIFIER, TAURI_APP_IDENTIFIER_DEV, } from './opencode-config-dir';
|
|
5
|
+
describe('opencode-config-dir', () => {
|
|
6
|
+
let originalPlatform;
|
|
7
|
+
let originalEnv;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
originalPlatform = process.platform;
|
|
10
|
+
originalEnv = {
|
|
11
|
+
APPDATA: process.env.APPDATA,
|
|
12
|
+
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME,
|
|
13
|
+
XDG_DATA_HOME: process.env.XDG_DATA_HOME,
|
|
14
|
+
OPENCODE_CONFIG_DIR: process.env.OPENCODE_CONFIG_DIR,
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
Object.defineProperty(process, 'platform', { value: originalPlatform });
|
|
19
|
+
for (const [key, value] of Object.entries(originalEnv)) {
|
|
20
|
+
if (value !== undefined) {
|
|
21
|
+
process.env[key] = value;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
delete process.env[key];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
describe('OPENCODE_CONFIG_DIR environment variable', () => {
|
|
29
|
+
test('returns OPENCODE_CONFIG_DIR when env var is set', () => {
|
|
30
|
+
// #given OPENCODE_CONFIG_DIR is set to a custom path
|
|
31
|
+
process.env.OPENCODE_CONFIG_DIR = '/custom/opencode/path';
|
|
32
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
33
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
34
|
+
const result = getOpenCodeConfigDir({
|
|
35
|
+
binary: 'opencode',
|
|
36
|
+
version: '1.0.200',
|
|
37
|
+
});
|
|
38
|
+
// #then returns the custom path
|
|
39
|
+
expect(result).toBe('/custom/opencode/path');
|
|
40
|
+
});
|
|
41
|
+
test('falls back to default when env var is not set', () => {
|
|
42
|
+
// #given OPENCODE_CONFIG_DIR is not set, platform is Linux
|
|
43
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
44
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
45
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
46
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
47
|
+
const result = getOpenCodeConfigDir({
|
|
48
|
+
binary: 'opencode',
|
|
49
|
+
version: '1.0.200',
|
|
50
|
+
});
|
|
51
|
+
// #then returns default ~/.config/opencode
|
|
52
|
+
expect(result).toBe(join(homedir(), '.config', 'opencode'));
|
|
53
|
+
});
|
|
54
|
+
test('falls back to default when env var is empty string', () => {
|
|
55
|
+
// #given OPENCODE_CONFIG_DIR is set to empty string
|
|
56
|
+
process.env.OPENCODE_CONFIG_DIR = '';
|
|
57
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
58
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
59
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
60
|
+
const result = getOpenCodeConfigDir({
|
|
61
|
+
binary: 'opencode',
|
|
62
|
+
version: '1.0.200',
|
|
63
|
+
});
|
|
64
|
+
// #then returns default ~/.config/opencode
|
|
65
|
+
expect(result).toBe(join(homedir(), '.config', 'opencode'));
|
|
66
|
+
});
|
|
67
|
+
test('falls back to default when env var is whitespace only', () => {
|
|
68
|
+
// #given OPENCODE_CONFIG_DIR is set to whitespace only
|
|
69
|
+
process.env.OPENCODE_CONFIG_DIR = ' ';
|
|
70
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
71
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
72
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
73
|
+
const result = getOpenCodeConfigDir({
|
|
74
|
+
binary: 'opencode',
|
|
75
|
+
version: '1.0.200',
|
|
76
|
+
});
|
|
77
|
+
// #then returns default ~/.config/opencode
|
|
78
|
+
expect(result).toBe(join(homedir(), '.config', 'opencode'));
|
|
79
|
+
});
|
|
80
|
+
test('resolves relative path to absolute path', () => {
|
|
81
|
+
// #given OPENCODE_CONFIG_DIR is set to a relative path
|
|
82
|
+
process.env.OPENCODE_CONFIG_DIR = './my-opencode-config';
|
|
83
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
84
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
85
|
+
const result = getOpenCodeConfigDir({
|
|
86
|
+
binary: 'opencode',
|
|
87
|
+
version: '1.0.200',
|
|
88
|
+
});
|
|
89
|
+
// #then returns resolved absolute path
|
|
90
|
+
expect(result).toBe(resolve('./my-opencode-config'));
|
|
91
|
+
});
|
|
92
|
+
test('OPENCODE_CONFIG_DIR takes priority over XDG_CONFIG_HOME', () => {
|
|
93
|
+
// #given both OPENCODE_CONFIG_DIR and XDG_CONFIG_HOME are set
|
|
94
|
+
process.env.OPENCODE_CONFIG_DIR = '/custom/opencode/path';
|
|
95
|
+
process.env.XDG_CONFIG_HOME = '/xdg/config';
|
|
96
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
97
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
98
|
+
const result = getOpenCodeConfigDir({
|
|
99
|
+
binary: 'opencode',
|
|
100
|
+
version: '1.0.200',
|
|
101
|
+
});
|
|
102
|
+
// #then OPENCODE_CONFIG_DIR takes priority
|
|
103
|
+
expect(result).toBe('/custom/opencode/path');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('isDevBuild', () => {
|
|
107
|
+
test('returns false for null version', () => {
|
|
108
|
+
expect(isDevBuild(null)).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
test('returns false for undefined version', () => {
|
|
111
|
+
expect(isDevBuild(undefined)).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
test('returns false for production version', () => {
|
|
114
|
+
expect(isDevBuild('1.0.200')).toBe(false);
|
|
115
|
+
expect(isDevBuild('2.1.0')).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
test('returns true for version containing -dev', () => {
|
|
118
|
+
expect(isDevBuild('1.0.0-dev')).toBe(true);
|
|
119
|
+
expect(isDevBuild('1.0.0-dev.123')).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
test('returns true for version containing .dev', () => {
|
|
122
|
+
expect(isDevBuild('1.0.0.dev')).toBe(true);
|
|
123
|
+
expect(isDevBuild('1.0.0.dev.456')).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe('getOpenCodeConfigDir', () => {
|
|
127
|
+
describe('for opencode CLI binary', () => {
|
|
128
|
+
test('returns ~/.config/opencode on Linux', () => {
|
|
129
|
+
// #given opencode CLI binary detected, platform is Linux
|
|
130
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
131
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
132
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
133
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
134
|
+
const result = getOpenCodeConfigDir({
|
|
135
|
+
binary: 'opencode',
|
|
136
|
+
version: '1.0.200',
|
|
137
|
+
});
|
|
138
|
+
// #then returns ~/.config/opencode
|
|
139
|
+
expect(result).toBe(join(homedir(), '.config', 'opencode'));
|
|
140
|
+
});
|
|
141
|
+
test('returns $XDG_CONFIG_HOME/opencode on Linux when XDG_CONFIG_HOME is set', () => {
|
|
142
|
+
// #given opencode CLI binary detected, platform is Linux with XDG_CONFIG_HOME set
|
|
143
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
144
|
+
process.env.XDG_CONFIG_HOME = '/custom/config';
|
|
145
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
146
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
147
|
+
const result = getOpenCodeConfigDir({
|
|
148
|
+
binary: 'opencode',
|
|
149
|
+
version: '1.0.200',
|
|
150
|
+
});
|
|
151
|
+
// #then returns $XDG_CONFIG_HOME/opencode
|
|
152
|
+
expect(result).toBe('/custom/config/opencode');
|
|
153
|
+
});
|
|
154
|
+
test('returns ~/.config/opencode on macOS', () => {
|
|
155
|
+
// #given opencode CLI binary detected, platform is macOS
|
|
156
|
+
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
|
157
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
158
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
159
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
160
|
+
const result = getOpenCodeConfigDir({
|
|
161
|
+
binary: 'opencode',
|
|
162
|
+
version: '1.0.200',
|
|
163
|
+
});
|
|
164
|
+
// #then returns ~/.config/opencode
|
|
165
|
+
expect(result).toBe(join(homedir(), '.config', 'opencode'));
|
|
166
|
+
});
|
|
167
|
+
test('returns ~/.config/opencode on Windows by default', () => {
|
|
168
|
+
// #given opencode CLI binary detected, platform is Windows
|
|
169
|
+
Object.defineProperty(process, 'platform', { value: 'win32' });
|
|
170
|
+
delete process.env.APPDATA;
|
|
171
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
172
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
173
|
+
const result = getOpenCodeConfigDir({
|
|
174
|
+
binary: 'opencode',
|
|
175
|
+
version: '1.0.200',
|
|
176
|
+
checkExisting: false,
|
|
177
|
+
});
|
|
178
|
+
// #then returns ~/.config/opencode (cross-platform default)
|
|
179
|
+
expect(result).toBe(join(homedir(), '.config', 'opencode'));
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
describe('for opencode-desktop Tauri binary', () => {
|
|
183
|
+
test('returns ~/.config/ai.opencode.desktop on Linux', () => {
|
|
184
|
+
// #given opencode-desktop binary detected, platform is Linux
|
|
185
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
186
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
187
|
+
// #when getOpenCodeConfigDir is called with binary="opencode-desktop"
|
|
188
|
+
const result = getOpenCodeConfigDir({
|
|
189
|
+
binary: 'opencode-desktop',
|
|
190
|
+
version: '1.0.200',
|
|
191
|
+
checkExisting: false,
|
|
192
|
+
});
|
|
193
|
+
// #then returns ~/.config/ai.opencode.desktop
|
|
194
|
+
expect(result).toBe(join(homedir(), '.config', TAURI_APP_IDENTIFIER));
|
|
195
|
+
});
|
|
196
|
+
test('returns ~/Library/Application Support/ai.opencode.desktop on macOS', () => {
|
|
197
|
+
// #given opencode-desktop binary detected, platform is macOS
|
|
198
|
+
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
|
199
|
+
// #when getOpenCodeConfigDir is called with binary="opencode-desktop"
|
|
200
|
+
const result = getOpenCodeConfigDir({
|
|
201
|
+
binary: 'opencode-desktop',
|
|
202
|
+
version: '1.0.200',
|
|
203
|
+
checkExisting: false,
|
|
204
|
+
});
|
|
205
|
+
// #then returns ~/Library/Application Support/ai.opencode.desktop
|
|
206
|
+
expect(result).toBe(join(homedir(), 'Library', 'Application Support', TAURI_APP_IDENTIFIER));
|
|
207
|
+
});
|
|
208
|
+
test('returns %APPDATA%/ai.opencode.desktop on Windows', () => {
|
|
209
|
+
// #given opencode-desktop binary detected, platform is Windows
|
|
210
|
+
Object.defineProperty(process, 'platform', { value: 'win32' });
|
|
211
|
+
process.env.APPDATA = 'C:\\Users\\TestUser\\AppData\\Roaming';
|
|
212
|
+
// #when getOpenCodeConfigDir is called with binary="opencode-desktop"
|
|
213
|
+
const result = getOpenCodeConfigDir({
|
|
214
|
+
binary: 'opencode-desktop',
|
|
215
|
+
version: '1.0.200',
|
|
216
|
+
checkExisting: false,
|
|
217
|
+
});
|
|
218
|
+
// #then returns %APPDATA%/ai.opencode.desktop
|
|
219
|
+
expect(result).toBe(join('C:\\Users\\TestUser\\AppData\\Roaming', TAURI_APP_IDENTIFIER));
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
describe('dev build detection', () => {
|
|
223
|
+
test('returns ai.opencode.desktop.dev path when dev version detected', () => {
|
|
224
|
+
// #given opencode-desktop dev version
|
|
225
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
226
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
227
|
+
// #when getOpenCodeConfigDir is called with dev version
|
|
228
|
+
const result = getOpenCodeConfigDir({
|
|
229
|
+
binary: 'opencode-desktop',
|
|
230
|
+
version: '1.0.0-dev.123',
|
|
231
|
+
checkExisting: false,
|
|
232
|
+
});
|
|
233
|
+
// #then returns path with ai.opencode.desktop.dev
|
|
234
|
+
expect(result).toBe(join(homedir(), '.config', TAURI_APP_IDENTIFIER_DEV));
|
|
235
|
+
});
|
|
236
|
+
test('returns ai.opencode.desktop.dev on macOS for dev build', () => {
|
|
237
|
+
// #given opencode-desktop dev version on macOS
|
|
238
|
+
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
|
239
|
+
// #when getOpenCodeConfigDir is called with dev version
|
|
240
|
+
const result = getOpenCodeConfigDir({
|
|
241
|
+
binary: 'opencode-desktop',
|
|
242
|
+
version: '1.0.0-dev',
|
|
243
|
+
checkExisting: false,
|
|
244
|
+
});
|
|
245
|
+
// #then returns path with ai.opencode.desktop.dev
|
|
246
|
+
expect(result).toBe(join(homedir(), 'Library', 'Application Support', TAURI_APP_IDENTIFIER_DEV));
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
describe('getOpenCodeConfigPaths', () => {
|
|
251
|
+
test('returns all config paths for CLI binary', () => {
|
|
252
|
+
// #given opencode CLI binary on Linux
|
|
253
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
254
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
255
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
256
|
+
// #when getOpenCodeConfigPaths is called
|
|
257
|
+
const paths = getOpenCodeConfigPaths({
|
|
258
|
+
binary: 'opencode',
|
|
259
|
+
version: '1.0.200',
|
|
260
|
+
});
|
|
261
|
+
// #then returns all expected paths
|
|
262
|
+
const expectedDir = join(homedir(), '.config', 'opencode');
|
|
263
|
+
expect(paths.configDir).toBe(expectedDir);
|
|
264
|
+
expect(paths.configJson).toBe(join(expectedDir, 'opencode.json'));
|
|
265
|
+
expect(paths.configJsonc).toBe(join(expectedDir, 'opencode.jsonc'));
|
|
266
|
+
expect(paths.packageJson).toBe(join(expectedDir, 'package.json'));
|
|
267
|
+
expect(paths.omoConfig).toBe(join(expectedDir, 'oh-my-opencode.json'));
|
|
268
|
+
});
|
|
269
|
+
test('returns all config paths for desktop binary', () => {
|
|
270
|
+
// #given opencode-desktop binary on macOS
|
|
271
|
+
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
|
272
|
+
// #when getOpenCodeConfigPaths is called
|
|
273
|
+
const paths = getOpenCodeConfigPaths({
|
|
274
|
+
binary: 'opencode-desktop',
|
|
275
|
+
version: '1.0.200',
|
|
276
|
+
checkExisting: false,
|
|
277
|
+
});
|
|
278
|
+
// #then returns all expected paths
|
|
279
|
+
const expectedDir = join(homedir(), 'Library', 'Application Support', TAURI_APP_IDENTIFIER);
|
|
280
|
+
expect(paths.configDir).toBe(expectedDir);
|
|
281
|
+
expect(paths.configJson).toBe(join(expectedDir, 'opencode.json'));
|
|
282
|
+
expect(paths.configJsonc).toBe(join(expectedDir, 'opencode.jsonc'));
|
|
283
|
+
expect(paths.packageJson).toBe(join(expectedDir, 'package.json'));
|
|
284
|
+
expect(paths.omoConfig).toBe(join(expectedDir, 'oh-my-opencode.json'));
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
describe('detectExistingConfigDir', () => {
|
|
288
|
+
test('returns null when no config exists', () => {
|
|
289
|
+
// #given no config files exist
|
|
290
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
291
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
292
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
293
|
+
// #when detectExistingConfigDir is called
|
|
294
|
+
const result = detectExistingConfigDir('opencode', '1.0.200');
|
|
295
|
+
// #then result is either null or a valid string path
|
|
296
|
+
expect(result === null || typeof result === 'string').toBe(true);
|
|
297
|
+
});
|
|
298
|
+
test('includes OPENCODE_CONFIG_DIR in search locations when set', () => {
|
|
299
|
+
// #given OPENCODE_CONFIG_DIR is set to a custom path
|
|
300
|
+
process.env.OPENCODE_CONFIG_DIR = '/custom/opencode/path';
|
|
301
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
302
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
303
|
+
// #when detectExistingConfigDir is called
|
|
304
|
+
const result = detectExistingConfigDir('opencode', '1.0.200');
|
|
305
|
+
// #then result is either null (no config file exists) or a valid string path
|
|
306
|
+
// The important thing is that the function doesn't throw
|
|
307
|
+
expect(result === null || typeof result === 'string').toBe(true);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-morphllm",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"author": "Vito Lin",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"description": "OpenCode plugin for MorphLLM",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"lint-staged": {
|
|
18
18
|
"*.{js,jsx,ts,tsx,json,css,md}": [
|
|
19
|
-
"prettier --write"
|
|
19
|
+
"bunx prettier --write"
|
|
20
20
|
]
|
|
21
21
|
},
|
|
22
22
|
"type": "module",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"bun-types": "^1.3.6",
|
|
30
|
+
"coverage-badges-cli": "^2.2.0",
|
|
30
31
|
"husky": "^8.0.3",
|
|
31
32
|
"lint-staged": "^15.2.0",
|
|
32
33
|
"prettier": "^3.2.5",
|
package/src/index.ts
CHANGED
|
@@ -3,11 +3,10 @@ import type { McpLocalConfig } from '@opencode-ai/sdk';
|
|
|
3
3
|
|
|
4
4
|
import { createBuiltinMcps } from './morph/mcps';
|
|
5
5
|
import { createModelRouterHook } from './morph/router';
|
|
6
|
-
import { MORPH_ROUTER_ENABLED } from './shared/config';
|
|
7
6
|
|
|
8
7
|
const MorphOpenCodePlugin: Plugin = async () => {
|
|
9
8
|
const builtinMcps: Record<string, McpLocalConfig> = createBuiltinMcps();
|
|
10
|
-
const routerHook =
|
|
9
|
+
const routerHook = createModelRouterHook();
|
|
11
10
|
|
|
12
11
|
return {
|
|
13
12
|
config: async (currentConfig: any) => {
|
package/src/morph/router.test.ts
CHANGED
|
@@ -17,7 +17,8 @@ vi.mock('../shared/config', () => ({
|
|
|
17
17
|
MORPH_MODEL_MEDIUM: 'medium/medium',
|
|
18
18
|
MORPH_MODEL_HARD: 'hard/hard',
|
|
19
19
|
MORPH_MODEL_DEFAULT: 'default/default',
|
|
20
|
-
|
|
20
|
+
MORPH_ROUTER_PROMPT_CACHING_AWARE: false,
|
|
21
|
+
MORPH_ROUTER_ENABLED: true,
|
|
21
22
|
}));
|
|
22
23
|
|
|
23
24
|
import { createModelRouterHook, extractPromptText } from './router';
|
|
@@ -234,7 +235,7 @@ describe('router.ts', () => {
|
|
|
234
235
|
expect((input as any).model.modelID).toBe('hard');
|
|
235
236
|
});
|
|
236
237
|
|
|
237
|
-
it('should route all messages when
|
|
238
|
+
it('should route all messages when MORPH_ROUTER_PROMPT_CACHING_AWARE is disabled', async () => {
|
|
238
239
|
const hook = createModelRouterHook();
|
|
239
240
|
const classify = vi
|
|
240
241
|
.fn()
|
package/src/morph/router.ts
CHANGED
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
MORPH_MODEL_MEDIUM,
|
|
7
7
|
MORPH_MODEL_HARD,
|
|
8
8
|
MORPH_MODEL_DEFAULT,
|
|
9
|
-
|
|
9
|
+
MORPH_ROUTER_PROMPT_CACHING_AWARE,
|
|
10
|
+
MORPH_ROUTER_ENABLED,
|
|
10
11
|
} from '../shared/config';
|
|
11
12
|
import type { Part, UserMessage } from '@opencode-ai/sdk';
|
|
12
13
|
import type {
|
|
@@ -65,7 +66,11 @@ export function createModelRouterHook() {
|
|
|
65
66
|
): Promise<void> => {
|
|
66
67
|
input.model = input.model ?? { providerID: '', modelID: '' };
|
|
67
68
|
|
|
68
|
-
if (
|
|
69
|
+
if (!MORPH_ROUTER_ENABLED) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (MORPH_ROUTER_PROMPT_CACHING_AWARE) {
|
|
69
74
|
if (sessionsWithModelSelected.has(input.sessionID)) {
|
|
70
75
|
return;
|
|
71
76
|
}
|
|
@@ -89,7 +94,7 @@ export function createModelRouterHook() {
|
|
|
89
94
|
input.model.providerID = finalProviderID;
|
|
90
95
|
input.model.modelID = finalModelID;
|
|
91
96
|
|
|
92
|
-
if (
|
|
97
|
+
if (MORPH_ROUTER_ENABLED && MORPH_ROUTER_PROMPT_CACHING_AWARE) {
|
|
93
98
|
sessionsWithModelSelected.add(input.sessionID);
|
|
94
99
|
}
|
|
95
100
|
},
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
expect,
|
|
5
5
|
beforeEach,
|
|
6
6
|
afterEach,
|
|
7
|
-
vi,
|
|
8
7
|
beforeAll,
|
|
9
8
|
afterAll,
|
|
10
9
|
} from 'bun:test';
|
|
@@ -17,15 +16,15 @@ import {
|
|
|
17
16
|
} from 'node:fs';
|
|
18
17
|
import { join } from 'node:path';
|
|
19
18
|
import { tmpdir } from 'node:os';
|
|
19
|
+
import { env } from 'node:process';
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
const mockConfigDir = join(tmpdir(), 'mock-
|
|
21
|
+
// Use a real temporary directory for testing - no mocking needed
|
|
22
|
+
const mockConfigDir = join(tmpdir(), 'mock-morph-config-test');
|
|
23
23
|
const mockMorphJson = join(mockConfigDir, 'morph.json');
|
|
24
24
|
const mockMorphJsonc = join(mockConfigDir, 'morph.jsonc');
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}));
|
|
26
|
+
// Save original environment
|
|
27
|
+
let originalEnv: Record<string, string | undefined>;
|
|
29
28
|
|
|
30
29
|
import {
|
|
31
30
|
getMorphPluginConfigPath,
|
|
@@ -35,6 +34,12 @@ import {
|
|
|
35
34
|
|
|
36
35
|
describe('config.ts', () => {
|
|
37
36
|
beforeAll(() => {
|
|
37
|
+
// Save original environment and set custom config dir
|
|
38
|
+
originalEnv = {
|
|
39
|
+
OPENCODE_CONFIG_DIR: env.OPENCODE_CONFIG_DIR,
|
|
40
|
+
};
|
|
41
|
+
env.OPENCODE_CONFIG_DIR = mockConfigDir;
|
|
42
|
+
|
|
38
43
|
// Create mock config directory
|
|
39
44
|
if (!existsSync(mockConfigDir)) {
|
|
40
45
|
mkdirSync(mockConfigDir, { recursive: true });
|
|
@@ -42,6 +47,13 @@ describe('config.ts', () => {
|
|
|
42
47
|
});
|
|
43
48
|
|
|
44
49
|
afterAll(() => {
|
|
50
|
+
// Restore original environment
|
|
51
|
+
if (originalEnv.OPENCODE_CONFIG_DIR !== undefined) {
|
|
52
|
+
env.OPENCODE_CONFIG_DIR = originalEnv.OPENCODE_CONFIG_DIR;
|
|
53
|
+
} else {
|
|
54
|
+
delete env.OPENCODE_CONFIG_DIR;
|
|
55
|
+
}
|
|
56
|
+
|
|
45
57
|
// Cleanup
|
|
46
58
|
try {
|
|
47
59
|
if (existsSync(mockMorphJson)) rmSync(mockMorphJson);
|
package/src/shared/config.ts
CHANGED
|
@@ -11,7 +11,7 @@ export function getMorphPluginConfigPath(): string {
|
|
|
11
11
|
|
|
12
12
|
interface MorphRouterConfigs {
|
|
13
13
|
MORPH_ROUTER_ENABLED?: boolean;
|
|
14
|
-
|
|
14
|
+
MORPH_ROUTER_PROMPT_CACHING_AWARE?: boolean;
|
|
15
15
|
MORPH_MODEL_EASY?: string;
|
|
16
16
|
MORPH_MODEL_MEDIUM?: string;
|
|
17
17
|
MORPH_MODEL_HARD?: string;
|
|
@@ -23,7 +23,7 @@ interface MorphConfig {
|
|
|
23
23
|
MORPH_ROUTER_CONFIGS?: MorphRouterConfigs;
|
|
24
24
|
// Legacy fields for backward compatibility
|
|
25
25
|
MORPH_ROUTER_ENABLED?: boolean;
|
|
26
|
-
|
|
26
|
+
MORPH_ROUTER_PROMPT_CACHING_AWARE?: boolean;
|
|
27
27
|
MORPH_MODEL_EASY?: string;
|
|
28
28
|
MORPH_MODEL_MEDIUM?: string;
|
|
29
29
|
MORPH_MODEL_HARD?: string;
|
|
@@ -112,7 +112,7 @@ export const MORPH_MODEL_DEFAULT =
|
|
|
112
112
|
MORPH_MODEL_MEDIUM;
|
|
113
113
|
export const MORPH_ROUTER_ENABLED =
|
|
114
114
|
routerConfigs.MORPH_ROUTER_ENABLED ?? config.MORPH_ROUTER_ENABLED ?? true;
|
|
115
|
-
export const
|
|
116
|
-
routerConfigs.
|
|
117
|
-
config.
|
|
115
|
+
export const MORPH_ROUTER_PROMPT_CACHING_AWARE =
|
|
116
|
+
routerConfigs.MORPH_ROUTER_PROMPT_CACHING_AWARE ??
|
|
117
|
+
config.MORPH_ROUTER_PROMPT_CACHING_AWARE ??
|
|
118
118
|
false;
|