opencode-models-manager 0.1.0 → 0.1.1
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/README.md +12 -6
- package/package.json +1 -1
- package/src/models.ts +77 -13
package/README.md
CHANGED
|
@@ -22,10 +22,10 @@ This repo was created to provide a simple solution for managing AI models for ev
|
|
|
22
22
|
This branch is the OpenCode v1 line and requires OpenCode 1.18.0 or later. Pin 0.1.0 explicitly so a future v2 release on the latest dist-tag is never installed into a v1 host:
|
|
23
23
|
|
|
24
24
|
```
|
|
25
|
-
opencode plugin opencode-
|
|
25
|
+
opencode plugin opencode-models-manager@0.1.1
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Once the opencode-v1 dist-tag is published, `opencode-
|
|
28
|
+
Once the opencode-v1 dist-tag is published, `opencode-models-manager@opencode-v1` resolves to this line and can replace the pinned version above.
|
|
29
29
|
|
|
30
30
|
Restart OpenCode after installation.
|
|
31
31
|
|
|
@@ -35,7 +35,7 @@ Add to `~/.config/opencode/opencode.json` (global) or `opencode.json` (project):
|
|
|
35
35
|
|
|
36
36
|
```json
|
|
37
37
|
{
|
|
38
|
-
"plugin": ["opencode-
|
|
38
|
+
"plugin": ["opencode-models-manager@0.1.1"]
|
|
39
39
|
}
|
|
40
40
|
```
|
|
41
41
|
|
|
@@ -55,12 +55,18 @@ Create `.opencode/models.json` in the project root:
|
|
|
55
55
|
|
|
56
56
|
```json
|
|
57
57
|
{
|
|
58
|
-
"
|
|
59
|
-
|
|
58
|
+
"agent": {
|
|
59
|
+
"chat": "opencode-go/deepseek-v4.1-flash",
|
|
60
|
+
"chat2": "opencode-go/mimo-v2.6-flash"
|
|
61
|
+
},
|
|
62
|
+
"commands": {
|
|
63
|
+
"document": "opencode-go/deepseek-v4.1-flash",
|
|
64
|
+
"document2": "opencode-go/mimo-v2.6-flash"
|
|
65
|
+
}
|
|
60
66
|
}
|
|
61
67
|
```
|
|
62
68
|
|
|
63
|
-
|
|
69
|
+
The `agent` section targets agents in `.opencode/agent` and the `commands` section targets commands in `.opencode/commands`. The plugin merges the model into each existing configuration, so prompts, permissions, frontmatter, and other settings are preserved. A flat map such as `{ "chat": "provider/model" }` is still supported and applies to agents.
|
|
64
70
|
|
|
65
71
|
Invalid entries are logged and skipped. Restart OpenCode after changing `models.json` so the config hook runs again.
|
|
66
72
|
|
package/package.json
CHANGED
package/src/models.ts
CHANGED
|
@@ -4,6 +4,16 @@ import type { Plugin } from "@opencode-ai/plugin"
|
|
|
4
4
|
|
|
5
5
|
const CONFIG_FILE = path.join(".opencode", "models.json")
|
|
6
6
|
|
|
7
|
+
type Section = "agent" | "command"
|
|
8
|
+
type Models = Record<Section, Record<string, string>>
|
|
9
|
+
|
|
10
|
+
const SECTIONS: Record<string, Section> = {
|
|
11
|
+
agent: "agent",
|
|
12
|
+
agents: "agent",
|
|
13
|
+
command: "command",
|
|
14
|
+
commands: "command",
|
|
15
|
+
}
|
|
16
|
+
|
|
7
17
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
8
18
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
9
19
|
}
|
|
@@ -13,7 +23,33 @@ function isModelRef(value: string): boolean {
|
|
|
13
23
|
return slash > 0 && slash < value.length - 1
|
|
14
24
|
}
|
|
15
25
|
|
|
16
|
-
function
|
|
26
|
+
function isEntryName(name: string): boolean {
|
|
27
|
+
return name.trim() !== ""
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function loadSection(
|
|
31
|
+
file: string,
|
|
32
|
+
section: Section,
|
|
33
|
+
value: Record<string, unknown>
|
|
34
|
+
): Record<string, string> {
|
|
35
|
+
const models: Record<string, string> = {}
|
|
36
|
+
for (const [name, model] of Object.entries(value)) {
|
|
37
|
+
if (!isEntryName(name)) {
|
|
38
|
+
console.error(`[model-manager] ${file} ${section}s contains an empty name`)
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
if (typeof model !== "string" || !isModelRef(model)) {
|
|
42
|
+
console.error(
|
|
43
|
+
`[model-manager] ${file} "${section}.${name}" must be "provider/model", got ${JSON.stringify(model)}`
|
|
44
|
+
)
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
models[name] = model
|
|
48
|
+
}
|
|
49
|
+
return models
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function loadModels(file: string): Models | null {
|
|
17
53
|
let raw: string
|
|
18
54
|
try {
|
|
19
55
|
raw = readFileSync(file, "utf8")
|
|
@@ -34,19 +70,33 @@ function loadModels(file: string): Record<string, string> | null {
|
|
|
34
70
|
return null
|
|
35
71
|
}
|
|
36
72
|
|
|
37
|
-
const models:
|
|
38
|
-
for (const [
|
|
39
|
-
if (
|
|
40
|
-
|
|
73
|
+
const models: Models = { agent: {}, command: {} }
|
|
74
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
75
|
+
if (typeof value === "string") {
|
|
76
|
+
if (!isEntryName(key)) {
|
|
77
|
+
console.error(`[model-manager] ${file} contains an empty agent name`)
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
80
|
+
if (!isModelRef(value)) {
|
|
81
|
+
console.error(
|
|
82
|
+
`[model-manager] ${file} "${key}" must be "provider/model", got ${JSON.stringify(value)}`
|
|
83
|
+
)
|
|
84
|
+
continue
|
|
85
|
+
}
|
|
86
|
+
models.agent[key] = value
|
|
41
87
|
continue
|
|
42
88
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
)
|
|
89
|
+
|
|
90
|
+
const section = Object.hasOwn(SECTIONS, key) ? SECTIONS[key] : undefined
|
|
91
|
+
if (!section) {
|
|
92
|
+
console.error(`[model-manager] ${file} has unknown key "${key}"`)
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
if (!isRecord(value)) {
|
|
96
|
+
console.error(`[model-manager] ${file} "${key}" must be a JSON object`)
|
|
47
97
|
continue
|
|
48
98
|
}
|
|
49
|
-
models[
|
|
99
|
+
Object.assign(models[section], loadSection(file, section, value))
|
|
50
100
|
}
|
|
51
101
|
|
|
52
102
|
return models
|
|
@@ -59,9 +109,23 @@ export default (async ({ directory, worktree }) => ({
|
|
|
59
109
|
(root === directory ? null : loadModels(path.join(directory, CONFIG_FILE)))
|
|
60
110
|
if (!models) return
|
|
61
111
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
112
|
+
if (Object.keys(models.agent).length > 0) {
|
|
113
|
+
cfg.agent ??= {}
|
|
114
|
+
for (const [name, model] of Object.entries(models.agent)) {
|
|
115
|
+
cfg.agent[name] = { ...cfg.agent[name], model }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (Object.keys(models.command).length > 0) {
|
|
120
|
+
cfg.command ??= {}
|
|
121
|
+
for (const [name, model] of Object.entries(models.command)) {
|
|
122
|
+
const command = cfg.command[name]
|
|
123
|
+
if (!command) {
|
|
124
|
+
console.error(`[model-manager] command "${name}" was not found`)
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
127
|
+
cfg.command[name] = { ...command, model }
|
|
128
|
+
}
|
|
65
129
|
}
|
|
66
130
|
},
|
|
67
131
|
})) satisfies Plugin
|