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.
@@ -1,5 +1,5 @@
1
1
  import { constants } from "node:fs"
2
- import { access, copyFile, mkdir, symlink, unlink } from "node:fs/promises"
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 resolveConfig = join(configDir, "resolve.json")
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(resolveConfig))) {
26
- await copyFile(exampleConfig, resolveConfig)
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: ${resolveConfig}`)
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
+ }
@@ -1,5 +1,5 @@
1
1
  import { constants } from "node:fs"
2
- import { access, copyFile, mkdir, readFile, writeFile } from "node:fs/promises"
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 assertReadable(exampleConfigPath)
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 {