opencode-resolve 0.1.7 → 0.1.9
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 +121 -60
- package/README.md +121 -60
- package/dist/agents.d.ts +27 -0
- package/dist/agents.js +392 -0
- package/dist/config.d.ts +29 -0
- package/dist/config.js +404 -0
- package/dist/hooks/index.d.ts +18 -0
- package/dist/hooks/index.js +493 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +11 -702
- package/dist/state.d.ts +33 -0
- package/dist/state.js +20 -0
- package/dist/tools/index.d.ts +252 -0
- package/dist/tools/index.js +1209 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +33 -0
- package/dist/utils.js +371 -0
- package/opencode-resolve.example.json +8 -2
- package/opencode-resolve.reference.jsonc +107 -27
- package/package.json +10 -2
- package/scripts/install-git-hooks.mjs +28 -0
- package/scripts/install-local.mjs +64 -4
- package/scripts/postinstall.mjs +552 -34
package/scripts/postinstall.mjs
CHANGED
|
@@ -17,6 +17,19 @@ const ADDITIVE_DEFAULTS = {
|
|
|
17
17
|
autoApprove: true,
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// ZAI local MCP server bootstrap for GLM users.
|
|
21
|
+
// Do not copy provider secrets from OpenCode's auth store into opencode.json.
|
|
22
|
+
// The MCP process should receive credentials from the user's runtime environment.
|
|
23
|
+
const ZAI_MCP_SERVERS = {
|
|
24
|
+
"zai-mcp-server": {
|
|
25
|
+
type: "local",
|
|
26
|
+
command: ["npx", "-y", "@z_ai/mcp-server"],
|
|
27
|
+
environment: {
|
|
28
|
+
Z_AI_MODE: "ZAI",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
|
|
20
33
|
const COMPANION_PLUGINS = [
|
|
21
34
|
{
|
|
22
35
|
pkg: "@tarquinen/opencode-dcp",
|
|
@@ -28,6 +41,26 @@ const COMPANION_PLUGINS = [
|
|
|
28
41
|
},
|
|
29
42
|
]
|
|
30
43
|
|
|
44
|
+
const OPENAI_MODEL_HINTS = [
|
|
45
|
+
"openai/gpt-5.5",
|
|
46
|
+
"openai/gpt-5.4",
|
|
47
|
+
"openai/gpt-5.3-codex",
|
|
48
|
+
"openai/gpt-5.3-codex-spark",
|
|
49
|
+
"openai/gpt-5.2",
|
|
50
|
+
"openai/gpt-5-mini",
|
|
51
|
+
"openai/gpt-4o-mini",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
const GLM_MODEL_HINTS = [
|
|
55
|
+
"zai-coding-plan/glm-5.1",
|
|
56
|
+
"zai/glm-5.1",
|
|
57
|
+
"zai/glm-5",
|
|
58
|
+
"zai/glm-4.7-flash",
|
|
59
|
+
"zai/glm-4.5-flash",
|
|
60
|
+
"zai/glm-4.5",
|
|
61
|
+
"zai/glm-4.5-air",
|
|
62
|
+
]
|
|
63
|
+
|
|
31
64
|
if (process.env.OPENCODE_RESOLVE_SKIP_POSTINSTALL === "1") {
|
|
32
65
|
process.exit(0)
|
|
33
66
|
}
|
|
@@ -56,34 +89,162 @@ async function readOwnVersion() {
|
|
|
56
89
|
|
|
57
90
|
async function registerPlugin() {
|
|
58
91
|
await mkdir(configDir, { recursive: true })
|
|
92
|
+
const scriptedAnswers = await readScriptedAnswersIfNeeded()
|
|
59
93
|
|
|
60
94
|
const config = await readOpenCodeConfig()
|
|
61
|
-
|
|
95
|
+
let configChanged = addPlugin(config)
|
|
96
|
+
|
|
97
|
+
// Inject ZAI MCP servers when GLM is detected
|
|
98
|
+
const allModels = detectAllModels(config)
|
|
99
|
+
const hasGLM = allModels.some((m) => isGLMModel(m))
|
|
100
|
+
if (hasGLM) {
|
|
101
|
+
const mcpChanged = await injectZAIMCPs(config)
|
|
102
|
+
configChanged = configChanged || mcpChanged
|
|
103
|
+
}
|
|
62
104
|
|
|
63
|
-
if (
|
|
105
|
+
if (configChanged) {
|
|
64
106
|
await writeFile(opencodeConfigPath, `${JSON.stringify(config, null, 2)}\n`)
|
|
65
|
-
console.log(`[${packageName}]
|
|
107
|
+
console.log(`[${packageName}] updated ${opencodeConfigPath}`)
|
|
66
108
|
} else {
|
|
67
109
|
console.log(`[${packageName}] already registered in ${opencodeConfigPath}`)
|
|
68
110
|
}
|
|
69
111
|
|
|
70
112
|
if (!(await exists(resolveConfigPath))) {
|
|
71
|
-
await createAdaptiveResolveConfig(config)
|
|
113
|
+
await createAdaptiveResolveConfig(config, scriptedAnswers)
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await handleExistingResolveConfig(config, scriptedAnswers)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function handleExistingResolveConfig(opencodeConfig, scriptedAnswers) {
|
|
121
|
+
const action = await chooseExistingResolveConfigAction(scriptedAnswers)
|
|
122
|
+
if (action === "fresh") {
|
|
123
|
+
await backupResolveConfig()
|
|
124
|
+
await createAdaptiveResolveConfig(opencodeConfig, scriptedAnswers)
|
|
72
125
|
return
|
|
73
126
|
}
|
|
74
127
|
|
|
75
128
|
await migrateResolveConfig()
|
|
76
129
|
}
|
|
77
130
|
|
|
78
|
-
async function
|
|
131
|
+
async function chooseExistingResolveConfigAction(scriptedAnswers) {
|
|
132
|
+
const requested = (process.env.OPENCODE_RESOLVE_REINSTALL ?? "").trim().toLowerCase()
|
|
133
|
+
if (["fresh", "reset", "recreate", "new"].includes(requested)) return "fresh"
|
|
134
|
+
if (["update", "keep", "migrate", "preserve"].includes(requested)) return "update"
|
|
135
|
+
if (requested) {
|
|
136
|
+
console.warn(`[${packageName}] ignoring unknown OPENCODE_RESOLVE_REINSTALL=${JSON.stringify(requested)}; use "fresh" or "update".`)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const forcePrompt = process.env.OPENCODE_RESOLVE_FORCE_PROMPT === "1"
|
|
140
|
+
const canPrompt = Boolean((process.stdin.isTTY && process.stdout.isTTY) || forcePrompt)
|
|
141
|
+
if (!canPrompt) {
|
|
142
|
+
console.log(`[${packageName}] existing ${resolveConfigPath} found; preserving it and applying additive updates.`)
|
|
143
|
+
console.log(`[${packageName}] for a fresh reinstall, rerun in a TTY or set OPENCODE_RESOLVE_REINSTALL=fresh.`)
|
|
144
|
+
return "update"
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const rl = createPromptInterface(scriptedAnswers)
|
|
148
|
+
try {
|
|
149
|
+
console.log("")
|
|
150
|
+
console.log(`[${packageName}] Existing resolve config found: ${resolveConfigPath}`)
|
|
151
|
+
console.log(" 1. update existing config — preserve your settings and add missing defaults")
|
|
152
|
+
console.log(" 2. fresh reinstall — back up resolve.json and run setup again")
|
|
153
|
+
const answer = await askChoice(rl, "Existing config [1=update, 2=fresh reinstall, default 1]: ", ["1", "2"], "1")
|
|
154
|
+
return answer === "2" ? "fresh" : "update"
|
|
155
|
+
} finally {
|
|
156
|
+
rl.close()
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function backupResolveConfig() {
|
|
161
|
+
const raw = await readFile(resolveConfigPath, "utf8")
|
|
162
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-")
|
|
163
|
+
const backupPath = `${resolveConfigPath}.bak.${stamp}`
|
|
164
|
+
await writeFile(backupPath, raw)
|
|
165
|
+
console.log(`[${packageName}] backed up existing resolve config to ${backupPath}`)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function createAdaptiveResolveConfig(opencodeConfig, scriptedAnswers) {
|
|
79
169
|
await assertReadable(exampleConfigPath)
|
|
80
170
|
const raw = await readFile(exampleConfigPath, "utf8")
|
|
81
171
|
const example = JSON.parse(raw)
|
|
82
172
|
|
|
83
173
|
const currentModel = detectOpenCodeModel(opencodeConfig)
|
|
84
|
-
const
|
|
85
|
-
|
|
174
|
+
const allModels = detectAllModels(opencodeConfig)
|
|
86
175
|
const resolveConfig = { ...example }
|
|
176
|
+
const forcePrompt = process.env.OPENCODE_RESOLVE_FORCE_PROMPT === "1"
|
|
177
|
+
const canPrompt = Boolean(
|
|
178
|
+
(process.stdin.isTTY && process.stdout.isTTY) || forcePrompt,
|
|
179
|
+
)
|
|
180
|
+
const interactivePreset = canPrompt
|
|
181
|
+
? await buildInteractivePreset(currentModel, allModels, scriptedAnswers)
|
|
182
|
+
: undefined
|
|
183
|
+
|
|
184
|
+
if (interactivePreset) {
|
|
185
|
+
resolveConfig.profile = interactivePreset.profile
|
|
186
|
+
if (interactivePreset.tier) resolveConfig.tier = interactivePreset.tier
|
|
187
|
+
else delete resolveConfig.tier
|
|
188
|
+
if (interactivePreset.enabled) resolveConfig.enabled = interactivePreset.enabled
|
|
189
|
+
resolveConfig.models = interactivePreset.models
|
|
190
|
+
resolveConfig.agents = {
|
|
191
|
+
...resolveConfig.agents,
|
|
192
|
+
...(interactivePreset.agents ?? {}),
|
|
193
|
+
}
|
|
194
|
+
await writeFile(resolveConfigPath, `${JSON.stringify(resolveConfig, null, 2)}\n`)
|
|
195
|
+
console.log(`[${packageName}] created ${resolveConfigPath} (preset: ${interactivePreset.label})`)
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (process.env.OPENCODE_RESOLVE_AUTO_PRESET !== "1") {
|
|
200
|
+
resolveConfig.profile = "mix"
|
|
201
|
+
delete resolveConfig.tier
|
|
202
|
+
resolveConfig.models = {}
|
|
203
|
+
resolveConfig.agents = {
|
|
204
|
+
...resolveConfig.agents,
|
|
205
|
+
codex: { ...(resolveConfig.agents?.codex ?? {}), enabled: true },
|
|
206
|
+
glm: { ...(resolveConfig.agents?.glm ?? {}), enabled: true },
|
|
207
|
+
}
|
|
208
|
+
await writeFile(resolveConfigPath, `${JSON.stringify(resolveConfig, null, 2)}\n`)
|
|
209
|
+
console.log(`[${packageName}] created ${resolveConfigPath} (preset: prompt-required)`)
|
|
210
|
+
console.log(`[${packageName}] model pinning was not guessed because no interactive prompt was available.`)
|
|
211
|
+
console.log(`[${packageName}] rerun setup in a TTY or edit ${resolveConfigPath} to choose GPT/GLM three-tier models.`)
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Profile selection based on detected providers
|
|
216
|
+
const hasGLM = allModels.some((m) => isGLMModel(m))
|
|
217
|
+
const hasGPT = allModels.some((m) => isGPTModel(m))
|
|
218
|
+
const preset = buildModelPreset(currentModel, allModels)
|
|
219
|
+
|
|
220
|
+
if (hasGLM && !hasGPT) {
|
|
221
|
+
// GLM only → GLM profile, silver tier (token-efficient, no deep-reviewer)
|
|
222
|
+
resolveConfig.profile = "glm"
|
|
223
|
+
resolveConfig.tier = "silver"
|
|
224
|
+
resolveConfig.agents = {
|
|
225
|
+
...resolveConfig.agents,
|
|
226
|
+
glm: { ...(resolveConfig.agents?.glm ?? {}), enabled: true },
|
|
227
|
+
}
|
|
228
|
+
} else if (hasGPT && !hasGLM) {
|
|
229
|
+
// GPT only → GPT profile, gold tier (full power)
|
|
230
|
+
resolveConfig.profile = "gpt"
|
|
231
|
+
resolveConfig.tier = "gold"
|
|
232
|
+
resolveConfig.agents = {
|
|
233
|
+
...resolveConfig.agents,
|
|
234
|
+
codex: { ...(resolveConfig.agents?.codex ?? {}), enabled: true },
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
// Mixed or unknown provider → explicit mix profile. No tier: use DEFAULT_ENABLED.
|
|
238
|
+
resolveConfig.profile = "mix"
|
|
239
|
+
if (hasGLM && hasGPT) {
|
|
240
|
+
resolveConfig.agents = {
|
|
241
|
+
...resolveConfig.agents,
|
|
242
|
+
codex: { ...(resolveConfig.agents?.codex ?? {}), enabled: true },
|
|
243
|
+
glm: { ...(resolveConfig.agents?.glm ?? {}), enabled: true },
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
87
248
|
if (preset && Object.keys(preset).length > 0) {
|
|
88
249
|
resolveConfig.models = preset
|
|
89
250
|
}
|
|
@@ -121,54 +282,389 @@ function detectOpenCodeModel(config) {
|
|
|
121
282
|
return null
|
|
122
283
|
}
|
|
123
284
|
|
|
124
|
-
function buildModelPreset(currentModel) {
|
|
125
|
-
|
|
285
|
+
function buildModelPreset(currentModel, allModels = []) {
|
|
286
|
+
const detectedGLMModels = allModels.filter(isGLMModel)
|
|
287
|
+
const detectedGPTModels = allModels.filter(isGPTModel)
|
|
288
|
+
const glmModels = detectedGLMModels.length > 0 ? collectModelChoices(detectedGLMModels, isGLMModel, GLM_MODEL_HINTS, false) : []
|
|
289
|
+
const gptModels = detectedGPTModels.length > 0 ? collectModelChoices(detectedGPTModels, isGPTModel, OPENAI_MODEL_HINTS, false) : []
|
|
290
|
+
const glmModel = glmModels[0]
|
|
291
|
+
const gptModel = gptModels[0]
|
|
292
|
+
|
|
293
|
+
if (glmModel && gptModel) {
|
|
294
|
+
const glmTiers = chooseThreeTier(glmModels, "glm", false)
|
|
295
|
+
const gptTiers = chooseThreeTier(gptModels, "gpt", false)
|
|
296
|
+
return {
|
|
297
|
+
mix: "gpt",
|
|
298
|
+
gpt: gptTiers.gold,
|
|
299
|
+
bronze: glmTiers.bronze,
|
|
300
|
+
silver: glmTiers.silver,
|
|
301
|
+
gold: gptTiers.gold,
|
|
302
|
+
"glm-bronze": glmTiers.bronze,
|
|
303
|
+
"glm-silver": glmTiers.silver,
|
|
304
|
+
"glm-gold": glmTiers.gold,
|
|
305
|
+
"gpt-bronze": gptTiers.bronze,
|
|
306
|
+
"gpt-silver": gptTiers.silver,
|
|
307
|
+
"gpt-gold": gptTiers.gold,
|
|
308
|
+
fast: "bronze",
|
|
309
|
+
strong: "gold",
|
|
310
|
+
mini: "bronze",
|
|
311
|
+
codex: "gpt-gold",
|
|
312
|
+
glm: glmTiers.gold,
|
|
313
|
+
explorer: "bronze",
|
|
314
|
+
coder: "silver",
|
|
315
|
+
resolver: "gold",
|
|
316
|
+
reviewer: "gold",
|
|
317
|
+
"deep-reviewer": "gold",
|
|
318
|
+
planner: "gold",
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (!currentModel) {
|
|
323
|
+
return {}
|
|
324
|
+
}
|
|
126
325
|
|
|
127
326
|
const lower = currentModel.toLowerCase()
|
|
128
327
|
|
|
129
|
-
// GLM / ZAI
|
|
328
|
+
// GLM / ZAI — GLM-only preset (no GPT dependency, avoids token-exhaustion errors)
|
|
130
329
|
if (lower.includes("glm") || lower.includes("zai")) {
|
|
131
|
-
return
|
|
132
|
-
glm: "zai-coding-plan/glm-5.1",
|
|
133
|
-
gpt: "openai/gpt-5.5",
|
|
134
|
-
fast: "glm",
|
|
135
|
-
strong: "gpt",
|
|
136
|
-
coder: "glm",
|
|
137
|
-
resolver: "gpt",
|
|
138
|
-
reviewer: "gpt",
|
|
139
|
-
"deep-reviewer": "gpt",
|
|
140
|
-
explorer: "fast",
|
|
141
|
-
}
|
|
330
|
+
return buildGLMOnlyPreset(currentModel)
|
|
142
331
|
}
|
|
143
332
|
|
|
144
333
|
// OpenAI / GPT single-provider preset
|
|
145
334
|
if (lower.includes("openai/") || lower.includes("gpt")) {
|
|
146
|
-
return
|
|
147
|
-
gpt: currentModel,
|
|
148
|
-
fast: "gpt",
|
|
149
|
-
strong: "gpt",
|
|
150
|
-
mini: "gpt",
|
|
151
|
-
codex: "gpt",
|
|
152
|
-
coder: "gpt",
|
|
153
|
-
resolver: "gpt",
|
|
154
|
-
explorer: "gpt",
|
|
155
|
-
reviewer: "gpt",
|
|
156
|
-
"deep-reviewer": "gpt",
|
|
157
|
-
}
|
|
335
|
+
return buildGPTOnlyPreset(currentModel)
|
|
158
336
|
}
|
|
159
337
|
|
|
160
338
|
// Unknown provider — keep model-neutral
|
|
161
339
|
return {}
|
|
162
340
|
}
|
|
163
341
|
|
|
342
|
+
function buildGLMOnlyPreset(model) {
|
|
343
|
+
const tiers = chooseThreeTier(collectModelChoices([model], isGLMModel, GLM_MODEL_HINTS, false), "glm", false)
|
|
344
|
+
return {
|
|
345
|
+
glm: tiers.gold,
|
|
346
|
+
bronze: tiers.bronze,
|
|
347
|
+
silver: tiers.silver,
|
|
348
|
+
gold: tiers.gold,
|
|
349
|
+
fast: "bronze",
|
|
350
|
+
strong: "gold",
|
|
351
|
+
mini: "bronze",
|
|
352
|
+
coder: "silver",
|
|
353
|
+
resolver: "gold",
|
|
354
|
+
reviewer: "gold",
|
|
355
|
+
"deep-reviewer": "gold",
|
|
356
|
+
explorer: "bronze",
|
|
357
|
+
planner: "gold",
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function buildGPTOnlyPreset(model) {
|
|
362
|
+
const tiers = chooseThreeTier(collectModelChoices([model], isGPTModel, OPENAI_MODEL_HINTS, false), "gpt", false)
|
|
363
|
+
return {
|
|
364
|
+
gpt: tiers.gold,
|
|
365
|
+
bronze: tiers.bronze,
|
|
366
|
+
silver: tiers.silver,
|
|
367
|
+
gold: tiers.gold,
|
|
368
|
+
fast: "bronze",
|
|
369
|
+
strong: "gold",
|
|
370
|
+
mini: "bronze",
|
|
371
|
+
codex: "gold",
|
|
372
|
+
coder: "silver",
|
|
373
|
+
resolver: "gold",
|
|
374
|
+
explorer: "bronze",
|
|
375
|
+
reviewer: "gold",
|
|
376
|
+
"deep-reviewer": "gold",
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function buildInteractivePreset(currentModel, allModels, scriptedAnswers) {
|
|
381
|
+
const choices = {
|
|
382
|
+
gpt: collectModelChoices(allModels, isGPTModel, OPENAI_MODEL_HINTS),
|
|
383
|
+
glm: collectModelChoices(allModels, isGLMModel, GLM_MODEL_HINTS),
|
|
384
|
+
}
|
|
385
|
+
if (currentModel) {
|
|
386
|
+
if (isGPTModel(currentModel)) choices.gpt = unique([currentModel, ...choices.gpt])
|
|
387
|
+
if (isGLMModel(currentModel)) choices.glm = unique([currentModel, ...choices.glm])
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const rl = createPromptInterface(scriptedAnswers)
|
|
391
|
+
try {
|
|
392
|
+
console.log("")
|
|
393
|
+
console.log(`[${packageName}] Choose resolve profile:`)
|
|
394
|
+
console.log(" 1. mix — neutral resolver plus optional Codex and GLM primary agents")
|
|
395
|
+
console.log(" 2. gpt — GPT/Codex-only, three-tier")
|
|
396
|
+
console.log(" 3. glm — GLM-only, three-tier")
|
|
397
|
+
const profileAnswer = await askChoice(rl, "Profile [1=mix, 2=gpt, 3=glm, default 1]: ", ["1", "2", "3"], "1")
|
|
398
|
+
const profile = profileAnswer === "2" ? "gpt" : profileAnswer === "3" ? "glm" : "mix"
|
|
399
|
+
|
|
400
|
+
if (profile === "gpt") {
|
|
401
|
+
const tiers = await askThreeTier(rl, "GPT/Codex", choices.gpt)
|
|
402
|
+
return {
|
|
403
|
+
label: "gpt-three-tier",
|
|
404
|
+
profile: "gpt",
|
|
405
|
+
tier: "gold",
|
|
406
|
+
models: buildGPTThreeTierModels(tiers),
|
|
407
|
+
agents: { codex: { enabled: true } },
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (profile === "glm") {
|
|
412
|
+
const tiers = await askThreeTier(rl, "GLM", choices.glm)
|
|
413
|
+
return {
|
|
414
|
+
label: "glm-three-tier",
|
|
415
|
+
profile: "glm",
|
|
416
|
+
tier: "gold",
|
|
417
|
+
models: buildGLMThreeTierModels(tiers),
|
|
418
|
+
agents: { glm: { enabled: true } },
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const useCodex = await askYesNo(rl, "Enable dedicated Codex primary agent too? [Y/n]: ", true)
|
|
423
|
+
const useGLM = await askYesNo(rl, "Enable dedicated GLM primary agent too? [Y/n]: ", true)
|
|
424
|
+
const gptTiers = await askThreeTier(rl, "Codex/GPT", choices.gpt)
|
|
425
|
+
const glmTiers = await askThreeTier(rl, "GLM", choices.glm)
|
|
426
|
+
return {
|
|
427
|
+
label: "mix-three-tier",
|
|
428
|
+
profile: "mix",
|
|
429
|
+
models: buildMixedThreeTierModels(gptTiers, glmTiers),
|
|
430
|
+
agents: {
|
|
431
|
+
codex: { enabled: useCodex },
|
|
432
|
+
glm: { enabled: useGLM },
|
|
433
|
+
},
|
|
434
|
+
}
|
|
435
|
+
} finally {
|
|
436
|
+
rl.close()
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function createPromptInterface(scriptedAnswers) {
|
|
441
|
+
return scriptedAnswers
|
|
442
|
+
? {
|
|
443
|
+
async question(prompt) {
|
|
444
|
+
const answer = scriptedAnswers.length > 0 ? scriptedAnswers.shift() ?? "" : ""
|
|
445
|
+
process.stdout.write(prompt)
|
|
446
|
+
process.stdout.write(`${answer}\n`)
|
|
447
|
+
return answer
|
|
448
|
+
},
|
|
449
|
+
close() {},
|
|
450
|
+
}
|
|
451
|
+
: createInterface({ input: process.stdin, output: process.stdout })
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function askThreeTier(rl, label, models) {
|
|
455
|
+
const choices = models.length > 0 ? models : (label.toLowerCase().includes("glm") ? GLM_MODEL_HINTS : OPENAI_MODEL_HINTS)
|
|
456
|
+
console.log("")
|
|
457
|
+
console.log(`[${packageName}] ${label} model choices:`)
|
|
458
|
+
choices.forEach((model, index) => console.log(` ${index + 1}. ${model}`))
|
|
459
|
+
const defaults = chooseThreeTier(choices, label.toLowerCase().includes("glm") ? "glm" : "gpt")
|
|
460
|
+
const bronze = await askModel(rl, choices, `Pick ${label} bronze/scout [default ${defaults.bronze}]: `, defaults.bronze)
|
|
461
|
+
const silver = await askModel(rl, choices, `Pick ${label} silver/coder [default ${defaults.silver}]: `, defaults.silver)
|
|
462
|
+
const gold = await askModel(rl, choices, `Pick ${label} gold/reasoner [default ${defaults.gold}]: `, defaults.gold)
|
|
463
|
+
return { bronze, silver, gold }
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async function askModel(rl, choices, question, defaultValue) {
|
|
467
|
+
const answer = (await rl.question(question)).trim()
|
|
468
|
+
if (!answer) return defaultValue
|
|
469
|
+
const index = Number.parseInt(answer, 10)
|
|
470
|
+
if (Number.isInteger(index) && index >= 1 && index <= choices.length) return choices[index - 1]
|
|
471
|
+
return answer
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async function askChoice(rl, question, valid, defaultValue) {
|
|
475
|
+
const answer = (await rl.question(question)).trim().toLowerCase()
|
|
476
|
+
if (!answer) return defaultValue
|
|
477
|
+
return valid.includes(answer) ? answer : defaultValue
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async function askYesNo(rl, question, defaultValue) {
|
|
481
|
+
const answer = (await rl.question(question)).trim().toLowerCase()
|
|
482
|
+
if (!answer) return defaultValue
|
|
483
|
+
return answer === "y" || answer === "yes"
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function buildMixedThreeTierModels(gptTiers, glmTiers) {
|
|
487
|
+
return {
|
|
488
|
+
mix: "gpt-gold",
|
|
489
|
+
gpt: "gpt-gold",
|
|
490
|
+
glm: "glm-gold",
|
|
491
|
+
bronze: "glm-bronze",
|
|
492
|
+
silver: "glm-silver",
|
|
493
|
+
gold: "gpt-gold",
|
|
494
|
+
"gpt-bronze": gptTiers.bronze,
|
|
495
|
+
"gpt-silver": gptTiers.silver,
|
|
496
|
+
"gpt-gold": gptTiers.gold,
|
|
497
|
+
"glm-bronze": glmTiers.bronze,
|
|
498
|
+
"glm-silver": glmTiers.silver,
|
|
499
|
+
"glm-gold": glmTiers.gold,
|
|
500
|
+
fast: "bronze",
|
|
501
|
+
strong: "gold",
|
|
502
|
+
mini: "bronze",
|
|
503
|
+
codex: "gpt-gold",
|
|
504
|
+
explorer: "bronze",
|
|
505
|
+
coder: "silver",
|
|
506
|
+
resolver: "gold",
|
|
507
|
+
reviewer: "gold",
|
|
508
|
+
"deep-reviewer": "gold",
|
|
509
|
+
planner: "gold",
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function buildGPTThreeTierModels(tiers) {
|
|
514
|
+
return {
|
|
515
|
+
gpt: "gold",
|
|
516
|
+
bronze: tiers.bronze,
|
|
517
|
+
silver: tiers.silver,
|
|
518
|
+
gold: tiers.gold,
|
|
519
|
+
fast: "bronze",
|
|
520
|
+
strong: "gold",
|
|
521
|
+
mini: "bronze",
|
|
522
|
+
codex: "gold",
|
|
523
|
+
explorer: "bronze",
|
|
524
|
+
coder: "silver",
|
|
525
|
+
resolver: "gold",
|
|
526
|
+
reviewer: "gold",
|
|
527
|
+
"deep-reviewer": "gold",
|
|
528
|
+
planner: "gold",
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function buildGLMThreeTierModels(tiers) {
|
|
533
|
+
return {
|
|
534
|
+
glm: "gold",
|
|
535
|
+
bronze: tiers.bronze,
|
|
536
|
+
silver: tiers.silver,
|
|
537
|
+
gold: tiers.gold,
|
|
538
|
+
fast: "bronze",
|
|
539
|
+
strong: "gold",
|
|
540
|
+
mini: "bronze",
|
|
541
|
+
explorer: "bronze",
|
|
542
|
+
coder: "silver",
|
|
543
|
+
resolver: "gold",
|
|
544
|
+
reviewer: "gold",
|
|
545
|
+
"deep-reviewer": "gold",
|
|
546
|
+
planner: "gold",
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function collectModelChoices(allModels, predicate, hints, includeFallbackHints = true) {
|
|
551
|
+
const detected = allModels.filter(predicate)
|
|
552
|
+
const providerIds = new Set(detected.map((model) => model.split("/")[0]).filter(Boolean))
|
|
553
|
+
const matchingHints = includeFallbackHints
|
|
554
|
+
? hints.filter((model) => providerIds.size === 0 || providerIds.has(model.split("/")[0]) || detected.length < 3)
|
|
555
|
+
: []
|
|
556
|
+
return unique([...detected, ...matchingHints])
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function chooseThreeTier(models, family, includeFallbackHints = true) {
|
|
560
|
+
const fallback = family === "glm" ? GLM_MODEL_HINTS : OPENAI_MODEL_HINTS
|
|
561
|
+
const choices = unique(includeFallbackHints ? [...models, ...fallback] : models)
|
|
562
|
+
return {
|
|
563
|
+
bronze: preferModel(choices, family === "glm" ? ["flash", "air", "mini"] : ["spark", "mini", "4o-mini"], choices[0]),
|
|
564
|
+
silver: preferModel(choices, family === "glm" ? ["5.1", "4.5"] : ["codex", "5.3", "5.2"], choices[1] ?? choices[0]),
|
|
565
|
+
gold: preferModel(choices, family === "glm" ? ["5.1", "5", "4.5"] : ["5.5", "5.4", "gpt-5.3-codex"], choices[2] ?? choices[1] ?? choices[0]),
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function preferModel(models, needles, fallback) {
|
|
570
|
+
return models.find((model) => {
|
|
571
|
+
const lower = model.toLowerCase()
|
|
572
|
+
return needles.some((needle) => lower.includes(needle))
|
|
573
|
+
}) ?? fallback
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function unique(values) {
|
|
577
|
+
return [...new Set(values.filter((value) => typeof value === "string" && value.length > 0))]
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
async function readAllStdin() {
|
|
581
|
+
let raw = ""
|
|
582
|
+
for await (const chunk of process.stdin) {
|
|
583
|
+
raw += chunk
|
|
584
|
+
}
|
|
585
|
+
return raw
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
async function readScriptedAnswersIfNeeded() {
|
|
589
|
+
if (process.env.OPENCODE_RESOLVE_FORCE_PROMPT === "1" && !process.stdin.isTTY) {
|
|
590
|
+
return (await readAllStdin()).split(/\r?\n/)
|
|
591
|
+
}
|
|
592
|
+
return undefined
|
|
593
|
+
}
|
|
594
|
+
|
|
164
595
|
function getPresetLabel(currentModel) {
|
|
165
596
|
if (!currentModel) return "inherited"
|
|
166
597
|
const lower = currentModel.toLowerCase()
|
|
167
|
-
if (lower.includes("glm") || lower.includes("zai")) return "glm
|
|
598
|
+
if (lower.includes("glm") || lower.includes("zai")) return "glm-only"
|
|
168
599
|
if (lower.includes("openai/") || lower.includes("gpt")) return "gpt-only"
|
|
169
600
|
return "inherited"
|
|
170
601
|
}
|
|
171
602
|
|
|
603
|
+
function isGLMModel(currentModel) {
|
|
604
|
+
if (!currentModel) return false
|
|
605
|
+
const lower = currentModel.toLowerCase()
|
|
606
|
+
return lower.includes("glm") || lower.includes("zai")
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function isGPTModel(currentModel) {
|
|
610
|
+
if (!currentModel) return false
|
|
611
|
+
const lower = currentModel.toLowerCase()
|
|
612
|
+
return lower.includes("openai/") || lower.includes("gpt")
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function detectAllModels(config) {
|
|
616
|
+
const models = new Set()
|
|
617
|
+
|
|
618
|
+
// Top-level model
|
|
619
|
+
if (typeof config.model === "string" && config.model.length > 0) {
|
|
620
|
+
models.add(config.model)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Top-level models object values
|
|
624
|
+
if (isObject(config.models)) {
|
|
625
|
+
for (const value of Object.values(config.models)) {
|
|
626
|
+
if (typeof value === "string" && value.length > 0) {
|
|
627
|
+
models.add(value)
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Provider model lists
|
|
633
|
+
if (isObject(config.provider)) {
|
|
634
|
+
for (const [providerId, providerConfig] of Object.entries(config.provider)) {
|
|
635
|
+
if (isObject(providerConfig) && isObject(providerConfig.models)) {
|
|
636
|
+
for (const [modelKey, modelEntry] of Object.entries(providerConfig.models)) {
|
|
637
|
+
if (typeof modelKey === "string" && modelKey.length > 0) {
|
|
638
|
+
models.add(qualifyModelId(providerId, modelKey))
|
|
639
|
+
}
|
|
640
|
+
if (typeof modelEntry === "string") {
|
|
641
|
+
models.add(qualifyModelId(providerId, modelEntry))
|
|
642
|
+
} else if (isObject(modelEntry) && typeof modelEntry.id === "string") {
|
|
643
|
+
models.add(qualifyModelId(providerId, modelEntry.id))
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Agent model values
|
|
651
|
+
if (isObject(config.agent)) {
|
|
652
|
+
for (const agentConfig of Object.values(config.agent)) {
|
|
653
|
+
if (isObject(agentConfig) && typeof agentConfig.model === "string" && agentConfig.model.length > 0) {
|
|
654
|
+
models.add(agentConfig.model)
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
return [...models]
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function qualifyModelId(providerId, modelId) {
|
|
663
|
+
if (typeof modelId !== "string" || modelId.length === 0) return modelId
|
|
664
|
+
if (modelId.includes("/")) return modelId
|
|
665
|
+
return `${providerId}/${modelId}`
|
|
666
|
+
}
|
|
667
|
+
|
|
172
668
|
async function migrateResolveConfig() {
|
|
173
669
|
let raw
|
|
174
670
|
try {
|
|
@@ -239,6 +735,28 @@ function addPlugin(config) {
|
|
|
239
735
|
return true
|
|
240
736
|
}
|
|
241
737
|
|
|
738
|
+
async function injectZAIMCPs(config) {
|
|
739
|
+
config.mcp ??= {}
|
|
740
|
+
if (!isObject(config.mcp)) return false
|
|
741
|
+
|
|
742
|
+
const added = []
|
|
743
|
+
for (const [name, mcpConfig] of Object.entries(ZAI_MCP_SERVERS)) {
|
|
744
|
+
if (config.mcp[name] === undefined) {
|
|
745
|
+
config.mcp[name] = mcpConfig
|
|
746
|
+
added.push(name)
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (added.length > 0) {
|
|
751
|
+
console.log(`[${packageName}] injected ZAI MCP server config: ${added.join(", ")}`)
|
|
752
|
+
console.log(`[${packageName}] note: API keys are not copied into opencode.json; export Z_AI_API_KEY if the MCP server requires it.`)
|
|
753
|
+
return true
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
console.log(`[${packageName}] ZAI MCP server already present — skipping`)
|
|
757
|
+
return false
|
|
758
|
+
}
|
|
759
|
+
|
|
242
760
|
function isRegisteredPluginEntry(entry) {
|
|
243
761
|
if (typeof entry === "string") return isResolvePluginName(entry)
|
|
244
762
|
if (Array.isArray(entry) && typeof entry[0] === "string") return isResolvePluginName(entry[0])
|