opencode-morphllm 0.0.9 → 0.1.0

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/DEVELOPMENT.md ADDED
@@ -0,0 +1,32 @@
1
+ # Development
2
+
3
+ To work on the plugin locally:
4
+
5
+ 1. Clone the repository
6
+ 2. Point OpenCode to your local copy in `~/.config/opencode/opencode.json`:
7
+
8
+ ```json
9
+ {
10
+ "plugins": ["/path/to/morph-opencode-plugin/"]
11
+ }
12
+ ```
13
+
14
+ 3. Changes are immediately reflected when you run OpenCode
15
+
16
+ ## Scripts
17
+
18
+ ```bash
19
+ # Build
20
+ bun run build
21
+
22
+ # Test
23
+ bun test
24
+
25
+ # Format
26
+ bun run format
27
+ ```
28
+
29
+ ## Developer Notes
30
+
31
+ - The config logic now exposes a small helper function (computeRouterEnabled) that encapsulates the decision logic for whether the router is enabled. This is exported primarily to make the behavior easily testable and to avoid brittle module-cache workarounds in tests.
32
+ - If you change how config values are consumed, prefer adding unit tests that call computeRouterEnabled with a trimmed test fixture rather than trying to reload module-level constants.
package/README.md CHANGED
@@ -1,65 +1,203 @@
1
1
  # MorphLLM OpenCode Plugin
2
2
 
3
- [![coverage](https://github.com/VitoLin/opencode-morphllm/blob/main/coverage/coverage.svg)](https://www.npmjs.com/package/opencode-morphllm)
3
+ [![coverage](coverage/coverage.svg)](https://www.npmjs.com/package/opencode-morphllm)
4
4
  [![npm](https://img.shields.io/npm/v/opencode-morphllm?style=flat-square)](https://www.npmjs.com/package/opencode-morphllm)
5
5
  [![downloads](https://img.shields.io/npm/dt/opencode-morphllm?style=flat-square)](https://www.npmjs.com/package/opencode-morphllm)
6
6
 
7
- 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.
7
+ An [OpenCode](https://opencode.ai/) plugin that integrates [MorphLLM](https://morphllm.com/)'s intelligent model routing and powerful MCP tools into your AI coding workflow.
8
8
 
9
- Github: https://github.com/VitoLin/opencode-morphllm
9
+ - **GitHub**: https://github.com/VitoLin/opencode-morphllm
10
+ - **NPM**: https://www.npmjs.com/package/opencode-morphllm
10
11
 
11
- NPM: https://www.npmjs.com/package/opencode-morphllm
12
+ ## Features
13
+
14
+ ### Intelligent Model Router
15
+
16
+ Automatically classifies your prompts by complexity (easy/medium/hard) and routes them to the most cost-effective and appropriate model. Save money on simple tasks while using powerful models only when needed.
17
+
18
+ ### MCP Tools
19
+
20
+ Adds MorphLLM's specialized MCP tools to OpenCode:
21
+
22
+ - **`edit_file`** - Fast, precise file editing tool optimized for code modifications
23
+ - **`warpgrep_codebase_search`** - Advanced codebase search with contextual understanding
24
+
25
+ ### Prompt Caching Optimization
26
+
27
+ Optional mode that maintains model consistency within a session, reducing costs through prompt caching.
12
28
 
13
29
  ## Installation
14
30
 
15
- In `~/.config/opencode/opencode.json`, add the following config:
31
+ ### 1. Install the Plugin
16
32
 
17
- ```
18
- "plugin": [
19
- "opencode-morphllm"
20
- ]
33
+ Add the plugin to your OpenCode configuration at `~/.config/opencode/opencode.json`:
34
+
35
+ ```json
36
+ {
37
+ "plugins": ["opencode-morphllm"]
38
+ }
21
39
  ```
22
40
 
23
- You can also set the `opencode-morphllm` config variables by creating a `json` file at `~/.config/opencode/morph.json`. You can find the provider and model ids from https://models.dev
41
+ ### 2. Configure MorphLLM
24
42
 
25
- Example configs:
43
+ Create a configuration file at `~/.config/opencode/morph.json` or for repo level configs, you can create the config at your repo root at `.opencode/morph.json`.
44
+
45
+ Example:
26
46
 
27
47
  ```json
28
48
  {
29
- "MORPH_API_KEY": "YOUR_API_KEY_HERE",
49
+ "MORPH_API_KEY": "your_morph_api_key_here",
30
50
  "MORPH_ROUTER_CONFIGS": {
31
51
  "MORPH_MODEL_EASY": "github-copilot/gpt-5-mini",
32
52
  "MORPH_MODEL_MEDIUM": "opencode/minimax-m2.1-free",
33
53
  "MORPH_MODEL_HARD": "github-copilot/gemini-2.5-pro",
34
54
  "MORPH_ROUTER_ENABLED": true,
35
- "MORPH_ROUTER_PROMPT_CACHING_AWARE": true
55
+ "MORPH_ROUTER_PROMPT_CACHING_AWARE": false
36
56
  }
37
57
  }
38
58
  ```
39
59
 
40
- Legacy format (still supported):
60
+ Find available models at [models.dev](https://models.dev).
61
+
62
+ ## Configuration Reference
63
+
64
+ ### Core Settings
65
+
66
+ | Option | Description | Required |
67
+ | --------------- | --------------------- | -------- |
68
+ | `MORPH_API_KEY` | Your MorphLLM API key | Yes |
69
+
70
+ ### Router Settings (inside `MORPH_ROUTER_CONFIGS`)
71
+
72
+ | Option | Description | Default |
73
+ | ----------------------------------- | --------------------------------------------------------- | ------------------------------- |
74
+ | `MORPH_ROUTER_ENABLED` | Enable/disable the intelligent router | `true` |
75
+ | `MORPH_MODEL_EASY` | Model for easy prompts (simple questions, formatting) | `your currently selected model` |
76
+ | `MORPH_MODEL_MEDIUM` | Model for medium prompts (standard coding tasks) | `your currently selected model` |
77
+ | `MORPH_MODEL_HARD` | Model for hard prompts (complex architecture, debugging) | `your currently selected model` |
78
+ | `MORPH_MODEL_DEFAULT` | Fallback model when classification fails | `MORPH_MODEL_MEDIUM` |
79
+ | `MORPH_ROUTER_PROMPT_CACHING_AWARE` | Stick to first model per session for caching optimization | `false` |
80
+
81
+ > Note about router enablement
82
+ >
83
+ > - The router will be automatically disabled if none of the model slots (`MORPH_MODEL_EASY`, `MORPH_MODEL_MEDIUM`, `MORPH_MODEL_HARD`) are configured (i.e., all are empty strings). This prevents the router from being active when there are no target models configured. An explicit `MORPH_ROUTER_ENABLED: false` in your config will also disable the router even if models are present.
84
+
85
+ ### System Message Customization
86
+
87
+ | Option | Description | Default |
88
+ | ---------------------- | ---------------------------------------------------------- | --------- |
89
+ | `MORPH_SYSTEM_MESSAGE` | Custom system message appended to OpenCode's system prompt | See below |
90
+
91
+ The `MORPH_SYSTEM_MESSAGE` setting allows you to customize or override the default system message that MorphLLM appends to OpenCode's system prompt. This message guides the AI on using Morph's MCP tools effectively.
92
+
93
+ **Default message:**
94
+
95
+ ```
96
+ You **MUST** consider using morph_mcp. For editing files, consider using morph_mcp_edit_file. For searching the code base, consider using warpgrep_codebase_search
97
+ ```
98
+
99
+ **When to customize:**
100
+
101
+ - You want to emphasize different tool preferences for your workflow
102
+ - You need to add project-specific conventions or guidelines
103
+ - You want to disable the default guidance entirely (set to empty string `""`)
104
+
105
+ **Example:**
106
+
107
+ ```json
108
+ {
109
+ "MORPH_API_KEY": "your_morph_api_key_here",
110
+ "MORPH_SYSTEM_MESSAGE": "Your custom system message here"
111
+ }
112
+ ```
113
+
114
+ ### Model Format
115
+
116
+ Models are specified as `provider/model-id`. Examples:
117
+
118
+ - `github-copilot/gpt-5-mini`
119
+ - `github-copilot/gemini-2.5-pro`
120
+ - `opencode/minimax-m2.1-free`
121
+
122
+ ### Legacy Format
123
+
124
+ For backward compatibility, you can also use flat configuration:
41
125
 
42
126
  ```json
43
127
  {
44
- "MORPH_API_KEY": "YOUR_API_KEY_HERE",
128
+ "MORPH_API_KEY": "your_key",
45
129
  "MORPH_MODEL_EASY": "github-copilot/gpt-5-mini",
46
130
  "MORPH_MODEL_MEDIUM": "opencode/minimax-m2.1-free",
47
131
  "MORPH_MODEL_HARD": "github-copilot/gemini-2.5-pro",
48
- "MORPH_ROUTER_ENABLED": true,
49
- "MORPH_ROUTER_PROMPT_CACHING_AWARE": true
132
+ "MORPH_ROUTER_ENABLED": true
50
133
  }
51
134
  ```
52
135
 
136
+ ### Project-Level Configuration
137
+
138
+ You can also configure per-project settings by creating `.opencode/morph.json` in your project root. Project settings override user settings.
139
+
140
+ ```
141
+ my-project/
142
+ ├── .opencode/
143
+ │ └── morph.json # Project-specific Morph config
144
+ └── src/
145
+ ```
146
+
147
+ Both `.json` and `.jsonc` (with comments) formats are supported.
148
+
149
+ ## How It Works
150
+
151
+ ### Intelligent Routing
152
+
153
+ When you send a prompt, the plugin:
154
+
155
+ 1. **Classifies** the prompt complexity using MorphLLM's classification engine
156
+ 2. **Selects** the appropriate model based on your configuration
157
+ 3. **Routes** the request to the chosen model
158
+
159
+ Example routing:
160
+
161
+ - "Format this JSON" → Easy → `MORPH_MODEL_EASY` (cheaper, faster)
162
+ - "Add a React component" → Medium → `MORPH_MODEL_MEDIUM`
163
+ - "Debug this race condition" → Hard → `MORPH_MODEL_HARD` (powerful)
164
+
165
+ ### Prompt Caching Mode
166
+
167
+ When `MORPH_ROUTER_PROMPT_CACHING_AWARE` is enabled:
168
+
169
+ - The first prompt in a session determines the model for all subsequent prompts
170
+ - Reduces costs through prompt caching with compatible providers
171
+ - Best for long conversations on similar topics
172
+
53
173
  ## Development
54
174
 
55
- After pulling down the package, set the OpenCode config path to the local repo
175
+ See [DEVELOPMENT.md](DEVELOPMENT.md) for setup instructions and development notes.
176
+
177
+ ## Troubleshooting
178
+
179
+ ### Plugin not loading
56
180
 
57
- `~/.config/opencode/opencode.json`
181
+ Ensure the plugin is properly listed in your OpenCode config:
58
182
 
59
183
  ```json
60
- "plugin": [
61
- "/path/to/morph-opencode-plugin/"
62
- ]
184
+ {
185
+ "plugins": ["opencode-morphllm"]
186
+ }
63
187
  ```
64
188
 
65
- Now your changes will be reflected when you run OpenCode.
189
+ ### Router not working
190
+
191
+ Check that:
192
+
193
+ 1. `MORPH_API_KEY` is set correctly
194
+ 2. `MORPH_ROUTER_ENABLED` is not set to `false`
195
+ 3. At least one model is configured
196
+
197
+ ### Where to find models
198
+
199
+ Visit [models.dev](https://models.dev) to browse available models and their IDs.
200
+
201
+ ## License
202
+
203
+ [MIT](LICENSE)
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { createBuiltinMcps } from './morph/mcps';
2
2
  import { createModelRouterHook } from './morph/router';
3
+ import { createSystemTransformHook } from './morph/system-transform';
3
4
  const MorphOpenCodePlugin = async () => {
4
5
  const builtinMcps = createBuiltinMcps();
6
+ const systemTransformHook = createSystemTransformHook();
5
7
  const routerHook = createModelRouterHook();
6
8
  return {
7
9
  config: async (currentConfig) => {
@@ -10,6 +12,7 @@ const MorphOpenCodePlugin = async () => {
10
12
  ...builtinMcps,
11
13
  };
12
14
  },
15
+ ...systemTransformHook,
13
16
  ...routerHook,
14
17
  };
15
18
  };
@@ -0,0 +1,5 @@
1
+ export declare function createSystemTransformHook(): {
2
+ 'experimental.chat.system.transform': (_input: unknown, output: {
3
+ system: string[];
4
+ }) => Promise<void>;
5
+ };
@@ -0,0 +1,8 @@
1
+ import { MORPH_SYSTEM_MESSAGE } from '../shared/config';
2
+ export function createSystemTransformHook() {
3
+ return {
4
+ 'experimental.chat.system.transform': async (_input, output) => {
5
+ output.system.push(MORPH_SYSTEM_MESSAGE);
6
+ },
7
+ };
8
+ }
@@ -9,6 +9,7 @@ interface MorphRouterConfigs {
9
9
  }
10
10
  interface MorphConfig {
11
11
  MORPH_API_KEY?: string;
12
+ MORPH_SYSTEM_MESSAGE?: string;
12
13
  MORPH_ROUTER_CONFIGS?: MorphRouterConfigs;
13
14
  MORPH_ROUTER_ENABLED?: boolean;
14
15
  MORPH_ROUTER_PROMPT_CACHING_AWARE?: boolean;
@@ -24,6 +25,12 @@ export declare const MORPH_MODEL_EASY: string;
24
25
  export declare const MORPH_MODEL_MEDIUM: string;
25
26
  export declare const MORPH_MODEL_HARD: string;
26
27
  export declare const MORPH_MODEL_DEFAULT: string;
28
+ /**
29
+ * Computes whether the router should be enabled based on config and model availability.
30
+ * Exported for testing purposes.
31
+ */
32
+ export declare function computeRouterEnabled(routerConfigs: MorphRouterConfigs, config: MorphConfig): boolean;
27
33
  export declare const MORPH_ROUTER_ENABLED: boolean;
28
34
  export declare const MORPH_ROUTER_PROMPT_CACHING_AWARE: boolean;
35
+ export declare const MORPH_SYSTEM_MESSAGE: string;
29
36
  export {};
@@ -74,7 +74,22 @@ export const MORPH_MODEL_HARD = routerConfigs.MORPH_MODEL_HARD || config.MORPH_M
74
74
  export const MORPH_MODEL_DEFAULT = routerConfigs.MORPH_MODEL_DEFAULT ||
75
75
  config.MORPH_MODEL_DEFAULT ||
76
76
  MORPH_MODEL_MEDIUM;
77
- export const MORPH_ROUTER_ENABLED = routerConfigs.MORPH_ROUTER_ENABLED ?? config.MORPH_ROUTER_ENABLED ?? true;
77
+ /**
78
+ * Computes whether the router should be enabled based on config and model availability.
79
+ * Exported for testing purposes.
80
+ */
81
+ export function computeRouterEnabled(routerConfigs, config) {
82
+ const easy = routerConfigs.MORPH_MODEL_EASY || config.MORPH_MODEL_EASY || '';
83
+ const medium = routerConfigs.MORPH_MODEL_MEDIUM || config.MORPH_MODEL_MEDIUM || '';
84
+ const hard = routerConfigs.MORPH_MODEL_HARD || config.MORPH_MODEL_HARD || '';
85
+ // Router should be disabled if all model values are empty strings
86
+ const allModelsEmpty = !easy && !medium && !hard;
87
+ return (!allModelsEmpty &&
88
+ (routerConfigs.MORPH_ROUTER_ENABLED ?? config.MORPH_ROUTER_ENABLED ?? true));
89
+ }
90
+ export const MORPH_ROUTER_ENABLED = computeRouterEnabled(routerConfigs, config);
78
91
  export const MORPH_ROUTER_PROMPT_CACHING_AWARE = routerConfigs.MORPH_ROUTER_PROMPT_CACHING_AWARE ??
79
92
  config.MORPH_ROUTER_PROMPT_CACHING_AWARE ??
80
93
  false;
94
+ const DEFAULT_SYSTEM_MESSAGE = '\nYou **MUST** consider using morph_mcp. For editing files, consider using morph_mcp_edit_file. For searching the code base, consider using warpgrep_codebase_search';
95
+ export const MORPH_SYSTEM_MESSAGE = config.MORPH_SYSTEM_MESSAGE || DEFAULT_SYSTEM_MESSAGE;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-morphllm",
3
- "version": "0.0.9",
3
+ "version": "0.1.0",
4
4
  "author": "Vito Lin",
5
5
  "main": "dist/index.js",
6
6
  "description": "OpenCode plugin for MorphLLM",