opencode-resolve 0.1.3 → 0.1.5
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.ko.md +921 -0
- package/README.md +498 -119
- package/dist/index.js +223 -26
- package/opencode-resolve.example.json +19 -5
- package/opencode-resolve.reference.jsonc +96 -31
- package/package.json +1 -1
- package/scripts/install-local.mjs +97 -5
- package/scripts/postinstall.mjs +110 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { constants } from "node:fs"
|
|
2
|
-
import { access,
|
|
2
|
+
import { access, mkdir, readFile, symlink, unlink, writeFile } from "node:fs/promises"
|
|
3
3
|
import { homedir } from "node:os"
|
|
4
4
|
import { dirname, join, resolve } from "node:path"
|
|
5
5
|
import { fileURLToPath } from "node:url"
|
|
@@ -8,8 +8,9 @@ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..")
|
|
|
8
8
|
const pluginTarget = join(root, "dist", "index.js")
|
|
9
9
|
const configDir = join(homedir(), ".config", "opencode")
|
|
10
10
|
const pluginLink = join(configDir, "plugins", "opencode-resolve.js")
|
|
11
|
-
const
|
|
11
|
+
const resolveConfigPath = join(configDir, "resolve.json")
|
|
12
12
|
const exampleConfig = join(root, "opencode-resolve.example.json")
|
|
13
|
+
const opencodeConfigPath = join(configDir, "opencode.json")
|
|
13
14
|
|
|
14
15
|
await assertFile(pluginTarget)
|
|
15
16
|
await mkdir(dirname(pluginLink), { recursive: true })
|
|
@@ -22,12 +23,99 @@ try {
|
|
|
22
23
|
|
|
23
24
|
await symlink(pluginTarget, pluginLink)
|
|
24
25
|
|
|
25
|
-
if (!(await exists(
|
|
26
|
-
await
|
|
26
|
+
if (!(await exists(resolveConfigPath))) {
|
|
27
|
+
await createAdaptiveResolveConfig()
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
console.log(`Linked plugin: ${pluginLink} -> ${pluginTarget}`)
|
|
30
|
-
console.log(`Resolve config: ${
|
|
31
|
+
console.log(`Resolve config: ${resolveConfigPath}`)
|
|
32
|
+
|
|
33
|
+
async function createAdaptiveResolveConfig() {
|
|
34
|
+
const raw = await readFile(exampleConfig, "utf8")
|
|
35
|
+
const example = JSON.parse(raw)
|
|
36
|
+
|
|
37
|
+
let opencodeConfig = {}
|
|
38
|
+
try {
|
|
39
|
+
const configRaw = await readFile(opencodeConfigPath, "utf8")
|
|
40
|
+
opencodeConfig = JSON.parse(configRaw)
|
|
41
|
+
} catch {
|
|
42
|
+
// opencode.json not found or unreadable — use empty config
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const currentModel = detectOpenCodeModel(opencodeConfig)
|
|
46
|
+
const preset = buildModelPreset(currentModel)
|
|
47
|
+
|
|
48
|
+
const resolveConfig = { ...example }
|
|
49
|
+
if (preset && Object.keys(preset).length > 0) {
|
|
50
|
+
resolveConfig.models = preset
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await mkdir(configDir, { recursive: true })
|
|
54
|
+
await writeFile(resolveConfigPath, `${JSON.stringify(resolveConfig, null, 2)}\n`)
|
|
55
|
+
|
|
56
|
+
const label = getPresetLabel(currentModel)
|
|
57
|
+
console.log(`Created ${resolveConfigPath} (preset: ${label})`)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function detectOpenCodeModel(config) {
|
|
61
|
+
if (typeof config.model === "string" && config.model.length > 0) {
|
|
62
|
+
return config.model
|
|
63
|
+
}
|
|
64
|
+
if (isObject(config.models)) {
|
|
65
|
+
for (const value of Object.values(config.models)) {
|
|
66
|
+
if (typeof value === "string" && value.length > 0) return value
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (isObject(config.agent)) {
|
|
70
|
+
for (const agentConfig of Object.values(config.agent)) {
|
|
71
|
+
if (isObject(agentConfig) && typeof agentConfig.model === "string" && agentConfig.model.length > 0) {
|
|
72
|
+
return agentConfig.model
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function buildModelPreset(currentModel) {
|
|
80
|
+
if (!currentModel) return {}
|
|
81
|
+
const lower = currentModel.toLowerCase()
|
|
82
|
+
if (lower.includes("glm") || lower.includes("zai")) {
|
|
83
|
+
return {
|
|
84
|
+
glm: "zai-coding-plan/glm-5.1",
|
|
85
|
+
gpt: "openai/gpt-5.5",
|
|
86
|
+
fast: "glm",
|
|
87
|
+
strong: "gpt",
|
|
88
|
+
coder: "glm",
|
|
89
|
+
resolver: "gpt",
|
|
90
|
+
reviewer: "gpt",
|
|
91
|
+
"deep-reviewer": "gpt",
|
|
92
|
+
explorer: "fast",
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (lower.includes("openai/") || lower.includes("gpt")) {
|
|
96
|
+
return {
|
|
97
|
+
gpt: currentModel,
|
|
98
|
+
fast: "gpt",
|
|
99
|
+
strong: "gpt",
|
|
100
|
+
mini: "gpt",
|
|
101
|
+
codex: "gpt",
|
|
102
|
+
coder: "gpt",
|
|
103
|
+
resolver: "gpt",
|
|
104
|
+
explorer: "gpt",
|
|
105
|
+
reviewer: "gpt",
|
|
106
|
+
"deep-reviewer": "gpt",
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return {}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function getPresetLabel(currentModel) {
|
|
113
|
+
if (!currentModel) return "inherited"
|
|
114
|
+
const lower = currentModel.toLowerCase()
|
|
115
|
+
if (lower.includes("glm") || lower.includes("zai")) return "glm+gpt"
|
|
116
|
+
if (lower.includes("openai/") || lower.includes("gpt")) return "gpt-only"
|
|
117
|
+
return "inherited"
|
|
118
|
+
}
|
|
31
119
|
|
|
32
120
|
async function assertFile(path) {
|
|
33
121
|
await access(path, constants.R_OK)
|
|
@@ -46,3 +134,7 @@ async function exists(path) {
|
|
|
46
134
|
function isMissingFileError(error) {
|
|
47
135
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT"
|
|
48
136
|
}
|
|
137
|
+
|
|
138
|
+
function isObject(value) {
|
|
139
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
140
|
+
}
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { constants } from "node:fs"
|
|
2
|
-
import { access,
|
|
2
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises"
|
|
3
3
|
import { homedir } from "node:os"
|
|
4
4
|
import { dirname, join, resolve } from "node:path"
|
|
5
5
|
import { fileURLToPath } from "node:url"
|
|
@@ -20,13 +20,27 @@ if (process.env.OPENCODE_RESOLVE_SKIP_POSTINSTALL === "1") {
|
|
|
20
20
|
process.exit(0)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
const pluginVersion = await readOwnVersion()
|
|
24
|
+
console.log(`[${packageName}] installing v${pluginVersion}`)
|
|
25
|
+
|
|
23
26
|
try {
|
|
24
27
|
await registerPlugin()
|
|
28
|
+
console.log(`[${packageName}] v${pluginVersion} install complete — restart OpenCode to load the plugin`)
|
|
25
29
|
} catch (error) {
|
|
26
30
|
console.warn(`[${packageName}] automatic OpenCode registration skipped: ${formatError(error)}`)
|
|
27
31
|
console.warn(`[${packageName}] add "${packageName}" to your OpenCode plugin list manually if needed.`)
|
|
28
32
|
}
|
|
29
33
|
|
|
34
|
+
async function readOwnVersion() {
|
|
35
|
+
try {
|
|
36
|
+
const raw = await readFile(join(root, "package.json"), "utf8")
|
|
37
|
+
const parsed = JSON.parse(raw)
|
|
38
|
+
return typeof parsed?.version === "string" ? parsed.version : "unknown"
|
|
39
|
+
} catch {
|
|
40
|
+
return "unknown"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
30
44
|
async function registerPlugin() {
|
|
31
45
|
await mkdir(configDir, { recursive: true })
|
|
32
46
|
|
|
@@ -41,15 +55,107 @@ async function registerPlugin() {
|
|
|
41
55
|
}
|
|
42
56
|
|
|
43
57
|
if (!(await exists(resolveConfigPath))) {
|
|
44
|
-
await
|
|
45
|
-
await copyFile(exampleConfigPath, resolveConfigPath)
|
|
46
|
-
console.log(`[${packageName}] created ${resolveConfigPath}`)
|
|
58
|
+
await createAdaptiveResolveConfig(config)
|
|
47
59
|
return
|
|
48
60
|
}
|
|
49
61
|
|
|
50
62
|
await migrateResolveConfig()
|
|
51
63
|
}
|
|
52
64
|
|
|
65
|
+
async function createAdaptiveResolveConfig(opencodeConfig) {
|
|
66
|
+
await assertReadable(exampleConfigPath)
|
|
67
|
+
const raw = await readFile(exampleConfigPath, "utf8")
|
|
68
|
+
const example = JSON.parse(raw)
|
|
69
|
+
|
|
70
|
+
const currentModel = detectOpenCodeModel(opencodeConfig)
|
|
71
|
+
const preset = buildModelPreset(currentModel)
|
|
72
|
+
|
|
73
|
+
const resolveConfig = { ...example }
|
|
74
|
+
if (preset && Object.keys(preset).length > 0) {
|
|
75
|
+
resolveConfig.models = preset
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await writeFile(resolveConfigPath, `${JSON.stringify(resolveConfig, null, 2)}\n`)
|
|
79
|
+
|
|
80
|
+
const label = getPresetLabel(currentModel)
|
|
81
|
+
console.log(`[${packageName}] created ${resolveConfigPath} (preset: ${label})`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function detectOpenCodeModel(config) {
|
|
85
|
+
// Prefer top-level `model` as primary signal
|
|
86
|
+
if (typeof config.model === "string" && config.model.length > 0) {
|
|
87
|
+
return config.model
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check top-level `models` object values if present
|
|
91
|
+
if (isObject(config.models)) {
|
|
92
|
+
for (const value of Object.values(config.models)) {
|
|
93
|
+
if (typeof value === "string" && value.length > 0) {
|
|
94
|
+
return value
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check agent.*.model values if present
|
|
100
|
+
if (isObject(config.agent)) {
|
|
101
|
+
for (const agentConfig of Object.values(config.agent)) {
|
|
102
|
+
if (isObject(agentConfig) && typeof agentConfig.model === "string" && agentConfig.model.length > 0) {
|
|
103
|
+
return agentConfig.model
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function buildModelPreset(currentModel) {
|
|
112
|
+
if (!currentModel) return {}
|
|
113
|
+
|
|
114
|
+
const lower = currentModel.toLowerCase()
|
|
115
|
+
|
|
116
|
+
// GLM / ZAI mixed preset
|
|
117
|
+
if (lower.includes("glm") || lower.includes("zai")) {
|
|
118
|
+
return {
|
|
119
|
+
glm: "zai-coding-plan/glm-5.1",
|
|
120
|
+
gpt: "openai/gpt-5.5",
|
|
121
|
+
fast: "glm",
|
|
122
|
+
strong: "gpt",
|
|
123
|
+
coder: "glm",
|
|
124
|
+
resolver: "gpt",
|
|
125
|
+
reviewer: "gpt",
|
|
126
|
+
"deep-reviewer": "gpt",
|
|
127
|
+
explorer: "fast",
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// OpenAI / GPT single-provider preset
|
|
132
|
+
if (lower.includes("openai/") || lower.includes("gpt")) {
|
|
133
|
+
return {
|
|
134
|
+
gpt: currentModel,
|
|
135
|
+
fast: "gpt",
|
|
136
|
+
strong: "gpt",
|
|
137
|
+
mini: "gpt",
|
|
138
|
+
codex: "gpt",
|
|
139
|
+
coder: "gpt",
|
|
140
|
+
resolver: "gpt",
|
|
141
|
+
explorer: "gpt",
|
|
142
|
+
reviewer: "gpt",
|
|
143
|
+
"deep-reviewer": "gpt",
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Unknown provider — keep model-neutral
|
|
148
|
+
return {}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function getPresetLabel(currentModel) {
|
|
152
|
+
if (!currentModel) return "inherited"
|
|
153
|
+
const lower = currentModel.toLowerCase()
|
|
154
|
+
if (lower.includes("glm") || lower.includes("zai")) return "glm+gpt"
|
|
155
|
+
if (lower.includes("openai/") || lower.includes("gpt")) return "gpt-only"
|
|
156
|
+
return "inherited"
|
|
157
|
+
}
|
|
158
|
+
|
|
53
159
|
async function migrateResolveConfig() {
|
|
54
160
|
let raw
|
|
55
161
|
try {
|