opencode-resolve 0.1.17 → 0.1.19
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 +9 -0
- package/README.md +9 -0
- package/package.json +1 -1
- package/scripts/postinstall.mjs +300 -23
package/README.ko.md
CHANGED
|
@@ -52,6 +52,15 @@ opencode plugin opencode-resolve --global --force
|
|
|
52
52
|
|
|
53
53
|
## 설치
|
|
54
54
|
|
|
55
|
+
### 한 줄 설치 (권장)
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
npm install -g opencode-resolve@latest
|
|
59
|
+
opencode-resolve setup
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`opencode-resolve setup`이 `opencode.json`의 provider/model을 자동 감지하고, 합리적인 기본값으로 짧은 Q&A를 거친 뒤(엔터로 모두 통과 가능) `resolve.json`을 작성하고 OpenCode 플러그인 캐시를 새로고침합니다. 모델 핀을 보존한 채 언제든 다시 실행해 재설정할 수 있습니다.
|
|
63
|
+
|
|
55
64
|
### 요구 사항
|
|
56
65
|
|
|
57
66
|
- OpenCode 실행 가능: `opencode --version`
|
package/README.md
CHANGED
|
@@ -52,6 +52,15 @@ Default enabled agents:
|
|
|
52
52
|
|
|
53
53
|
## Install
|
|
54
54
|
|
|
55
|
+
### One command (recommended)
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
npm install -g opencode-resolve@latest
|
|
59
|
+
opencode-resolve setup
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`opencode-resolve setup` auto-detects your providers and models from `opencode.json`, walks you through a short Q&A with sensible defaults (press enter to accept each), writes `resolve.json`, and refreshes the OpenCode plugin cache. Re-run any time to reconfigure without losing your model pins.
|
|
63
|
+
|
|
55
64
|
### Requirements
|
|
56
65
|
|
|
57
66
|
- OpenCode installed and runnable: `opencode --version`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-resolve",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "OpenCode plugin that adds a lightweight resolver/coder harness for continuous agentic coding.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://jshsakura.github.io/opencode-resolve/",
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -20,9 +20,24 @@ const ADDITIVE_DEFAULTS = {
|
|
|
20
20
|
autoApprove: true,
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
23
|
+
// Heuristic strength scoring for an arbitrary provider/model id.
|
|
24
|
+
// Higher = "stronger" / more expensive in expected behavior. Heuristic-only.
|
|
25
|
+
const STRENGTH_BOOSTS = [
|
|
26
|
+
{ re: /\b(mini|flash|nano|lite|haiku|air|small)\b/i, score: -3 },
|
|
27
|
+
{ re: /\b(claude-3|gpt-3|gpt-4o-mini|o1-mini|o3-mini|4\.5)\b/i, score: -1 },
|
|
28
|
+
{ re: /\b(gpt-4o|opus|sonnet|gemini-1\.5)\b/i, score: 1 },
|
|
29
|
+
{ re: /\b(o1|o3|o4|reason|reasoning|pro|max|ultra|gold|sota)\b/i, score: 2 },
|
|
30
|
+
{ re: /\b(5\.5|5\.4|5\.3|5\.2|5\.1|sonnet-4|opus-4|opus-5|max-5|next|2026)\b/i, score: 2 },
|
|
31
|
+
{ re: /\b(codex|coder|code|coding)\b/i, score: 1 },
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
// Provider-neutral agents — safe to enable for everyone (no profile-specific model needed).
|
|
35
|
+
const DEFAULT_ENABLED_AGENTS = [
|
|
36
|
+
"coder", "resolver", "explorer", "reviewer", "deep-reviewer", "planner",
|
|
37
|
+
"architect", "debugger", "researcher",
|
|
38
|
+
]
|
|
39
|
+
const GPT_ENABLED_AGENTS = [...DEFAULT_ENABLED_AGENTS, "gpt", "gpt-coder", "codex"]
|
|
40
|
+
const GLM_ENABLED_AGENTS = [...DEFAULT_ENABLED_AGENTS, "glm"]
|
|
26
41
|
|
|
27
42
|
// ZAI local MCP server bootstrap for GLM users.
|
|
28
43
|
// Do not copy provider secrets from OpenCode's auth store into opencode.json.
|
|
@@ -84,11 +99,41 @@ if (process.env.OPENCODE_RESOLVE_SKIP_POSTINSTALL === "1") {
|
|
|
84
99
|
const pluginVersion = await readOwnVersion()
|
|
85
100
|
console.log(`[${packageName}] installing v${pluginVersion}`)
|
|
86
101
|
|
|
102
|
+
async function printSummaryBanner(version) {
|
|
103
|
+
let resolveSummary = ""
|
|
104
|
+
try {
|
|
105
|
+
const raw = await readFile(resolveConfigPath, "utf8")
|
|
106
|
+
const cfg = JSON.parse(raw)
|
|
107
|
+
const parts = []
|
|
108
|
+
if (cfg.profile) parts.push(`profile=${cfg.profile}`)
|
|
109
|
+
if (cfg.tier) parts.push(`tier=${cfg.tier}`)
|
|
110
|
+
const enabled = Array.isArray(cfg.enabled) ? cfg.enabled.length : Object.keys(cfg.agents ?? {}).length
|
|
111
|
+
if (enabled) parts.push(`${enabled} agents`)
|
|
112
|
+
if (parts.length > 0) resolveSummary = parts.join(", ")
|
|
113
|
+
} catch { /* file may not exist on partial flows */ }
|
|
114
|
+
|
|
115
|
+
const lines = [
|
|
116
|
+
`✓ opencode-resolve v${version} installed`,
|
|
117
|
+
` Config: ${resolveConfigPath}${resolveSummary ? ` (${resolveSummary})` : ""}`,
|
|
118
|
+
` Next: restart OpenCode to load the plugin`,
|
|
119
|
+
` Verify: opencode run "list available agents" (must show resolver + coder)`,
|
|
120
|
+
` or inside any session: run resolve-version`,
|
|
121
|
+
]
|
|
122
|
+
const width = Math.max(...lines.map((l) => l.length)) + 2
|
|
123
|
+
const bar = "═".repeat(Math.min(width, 100))
|
|
124
|
+
console.log("")
|
|
125
|
+
console.log(bar)
|
|
126
|
+
for (const line of lines) console.log(line)
|
|
127
|
+
console.log(bar)
|
|
128
|
+
console.log("")
|
|
129
|
+
}
|
|
130
|
+
|
|
87
131
|
try {
|
|
88
132
|
await registerPlugin()
|
|
89
133
|
await refreshSelfPluginCache(pluginVersion)
|
|
90
134
|
await offerCompanionPlugins()
|
|
91
135
|
console.log(`[${packageName}] v${pluginVersion} install complete — restart OpenCode to load the plugin`)
|
|
136
|
+
await printSummaryBanner(pluginVersion)
|
|
92
137
|
} catch (error) {
|
|
93
138
|
console.warn(`[${packageName}] automatic OpenCode registration skipped: ${formatError(error)}`)
|
|
94
139
|
console.warn(`[${packageName}] add "${packageName}" to your OpenCode plugin list manually if needed.`)
|
|
@@ -263,11 +308,16 @@ async function chooseExistingResolveConfigAction(scriptedAnswers) {
|
|
|
263
308
|
const rl = createPromptInterface(scriptedAnswers)
|
|
264
309
|
try {
|
|
265
310
|
console.log("")
|
|
266
|
-
console.log(
|
|
267
|
-
console.log(
|
|
268
|
-
console.log(
|
|
269
|
-
console.log("
|
|
270
|
-
|
|
311
|
+
console.log("──────────────────────────────────────────────────────────────")
|
|
312
|
+
console.log(` opencode-resolve — existing config detected`)
|
|
313
|
+
console.log(` ${resolveConfigPath}`)
|
|
314
|
+
console.log("──────────────────────────────────────────────────────────────")
|
|
315
|
+
console.log("")
|
|
316
|
+
console.log("How do you want to handle it?")
|
|
317
|
+
console.log(" 1. keep — preserve existing settings, only add missing defaults (recommended)")
|
|
318
|
+
console.log(" 2. models — re-pick models only, keep the rest")
|
|
319
|
+
console.log(" 3. reset — back up the current file and run setup from scratch (model pins preserved)")
|
|
320
|
+
const answer = await askChoice(rl, "Choice [1=keep, 2=models, 3=reset, default 1]: ", ["1", "2", "3"], "1")
|
|
271
321
|
if (answer === "2") return "models"
|
|
272
322
|
return answer === "3" ? "fresh" : "update"
|
|
273
323
|
} finally {
|
|
@@ -307,7 +357,7 @@ async function createAdaptiveResolveConfig(opencodeConfig, scriptedAnswers, opti
|
|
|
307
357
|
(process.stdin.isTTY && process.stdout.isTTY) || forcePrompt,
|
|
308
358
|
)
|
|
309
359
|
const interactivePreset = canPrompt
|
|
310
|
-
? await buildInteractivePreset(currentModel, allModels, scriptedAnswers)
|
|
360
|
+
? await buildInteractivePreset(currentModel, allModels, scriptedAnswers, opencodeConfig)
|
|
311
361
|
: undefined
|
|
312
362
|
|
|
313
363
|
if (interactivePreset) {
|
|
@@ -359,8 +409,10 @@ async function createAdaptiveResolveConfig(opencodeConfig, scriptedAnswers, opti
|
|
|
359
409
|
} else if (preservedModels) {
|
|
360
410
|
resolveConfig.models = { ...preservedModels }
|
|
361
411
|
} else {
|
|
362
|
-
|
|
363
|
-
console.log(`[${packageName}]
|
|
412
|
+
const providerHint = currentModel ? ` (top-level model: ${currentModel})` : ""
|
|
413
|
+
console.log(`[${packageName}] no GPT/GLM models detected in opencode.json — agents inherit the top-level model${providerHint}`)
|
|
414
|
+
console.log(`[${packageName}] to pin role-specific models, edit ${resolveConfigPath} ("models" section)`)
|
|
415
|
+
console.log(`[${packageName}] or rerun setup in a TTY: ${packageName} setup --models`)
|
|
364
416
|
}
|
|
365
417
|
|
|
366
418
|
await writeFile(resolveConfigPath, `${JSON.stringify(resolveConfig, null, 2)}\n`)
|
|
@@ -381,7 +433,7 @@ async function reconfigureExistingModels(opencodeConfig, scriptedAnswers) {
|
|
|
381
433
|
const forcePrompt = readInstallerOption("force_prompt") === "1"
|
|
382
434
|
const canPrompt = Boolean((process.stdin.isTTY && process.stdout.isTTY) || forcePrompt)
|
|
383
435
|
const interactivePreset = canPrompt
|
|
384
|
-
? await buildInteractivePreset(currentModel, allModels, scriptedAnswers)
|
|
436
|
+
? await buildInteractivePreset(currentModel, allModels, scriptedAnswers, opencodeConfig)
|
|
385
437
|
: undefined
|
|
386
438
|
const preset = interactivePreset ?? {
|
|
387
439
|
label: getPresetLabel(currentModel),
|
|
@@ -550,7 +602,7 @@ function buildGPTOnlyPreset(model, gptModels) {
|
|
|
550
602
|
}
|
|
551
603
|
}
|
|
552
604
|
|
|
553
|
-
async function buildInteractivePreset(currentModel, allModels, scriptedAnswers) {
|
|
605
|
+
async function buildInteractivePreset(currentModel, allModels, scriptedAnswers, opencodeConfig) {
|
|
554
606
|
const choices = {
|
|
555
607
|
gpt: collectModelChoices(allModels, isGPTModel, OPENAI_MODEL_HINTS),
|
|
556
608
|
glm: collectModelChoices(allModels, isGLMModel, GLM_MODEL_HINTS),
|
|
@@ -560,14 +612,36 @@ async function buildInteractivePreset(currentModel, allModels, scriptedAnswers)
|
|
|
560
612
|
if (isGLMModel(currentModel)) choices.glm = unique([currentModel, ...choices.glm])
|
|
561
613
|
}
|
|
562
614
|
|
|
615
|
+
const hasGPTOrGLM = allModels.some((m) => isGPTModel(m) || isGLMModel(m))
|
|
616
|
+
const providerCount = opencodeConfig ? detectProvidersFromConfig(opencodeConfig).length : 0
|
|
617
|
+
const offerAuto = !hasGPTOrGLM && providerCount > 0
|
|
618
|
+
const defaultChoice = offerAuto ? "4" : "1"
|
|
619
|
+
|
|
563
620
|
const rl = createPromptInterface(scriptedAnswers)
|
|
564
621
|
try {
|
|
565
622
|
console.log("")
|
|
566
|
-
console.log(
|
|
567
|
-
console.log(
|
|
623
|
+
console.log("──────────────────────────────────────────────────────────────")
|
|
624
|
+
console.log(` opencode-resolve setup`)
|
|
625
|
+
console.log(` Press enter at any prompt to accept the default in [brackets].`)
|
|
626
|
+
console.log("──────────────────────────────────────────────────────────────")
|
|
627
|
+
console.log("")
|
|
628
|
+
console.log(`[${packageName}] Step 1/2 — Choose resolve profile:`)
|
|
629
|
+
console.log(` 1. mix — neutral resolver plus optional Codex and GLM primary agents${offerAuto ? "" : " (recommended)"}`)
|
|
568
630
|
console.log(" 2. gpt — GPT/Codex-only, three-tier")
|
|
569
631
|
console.log(" 3. glm — GLM-only, three-tier")
|
|
570
|
-
|
|
632
|
+
if (offerAuto) {
|
|
633
|
+
console.log(` 4. auto — provider-agnostic tier setup from detected providers (recommended)`)
|
|
634
|
+
}
|
|
635
|
+
const validChoices = offerAuto ? ["1", "2", "3", "4"] : ["1", "2", "3"]
|
|
636
|
+
const profileAnswer = await askChoice(rl, `Profile [${validChoices.join(",")}, default ${defaultChoice}]: `, validChoices, defaultChoice)
|
|
637
|
+
|
|
638
|
+
if (profileAnswer === "4" && offerAuto) {
|
|
639
|
+
rl.close()
|
|
640
|
+
const generic = await buildGenericInteractivePreset(currentModel, opencodeConfig, scriptedAnswers)
|
|
641
|
+
if (generic) return generic
|
|
642
|
+
// fall through to mix if generic returned nothing
|
|
643
|
+
}
|
|
644
|
+
|
|
571
645
|
const profile = profileAnswer === "2" ? "gpt" : profileAnswer === "3" ? "glm" : "mix"
|
|
572
646
|
|
|
573
647
|
if (profile === "gpt") {
|
|
@@ -638,14 +712,22 @@ function createPromptInterface(scriptedAnswers) {
|
|
|
638
712
|
|
|
639
713
|
async function askThreeTier(rl, label, models) {
|
|
640
714
|
const choices = models.length > 0 ? models : (label.toLowerCase().includes("glm") ? GLM_MODEL_HINTS : OPENAI_MODEL_HINTS)
|
|
641
|
-
console.log("")
|
|
642
|
-
console.log(`[${packageName}] ${label} model choices:`)
|
|
643
|
-
choices.forEach((model, index) => console.log(` ${index + 1}. ${model}`))
|
|
644
715
|
const defaults = chooseThreeTier(choices, label.toLowerCase().includes("glm") ? "glm" : "gpt")
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
716
|
+
while (true) {
|
|
717
|
+
console.log("")
|
|
718
|
+
console.log(`[${packageName}] ${label} model choices:`)
|
|
719
|
+
choices.forEach((model, index) => console.log(` ${index + 1}. ${model}`))
|
|
720
|
+
const bronze = await askModel(rl, choices, `Pick ${label} bronze/scout [default ${defaults.bronze}]: `, defaults.bronze)
|
|
721
|
+
const silver = await askModel(rl, choices, `Pick ${label} silver/coder [default ${defaults.silver}]: `, defaults.silver)
|
|
722
|
+
const gold = await askModel(rl, choices, `Pick ${label} gold/reasoner [default ${defaults.gold}]: `, defaults.gold)
|
|
723
|
+
console.log("")
|
|
724
|
+
console.log(` → bronze: ${bronze}`)
|
|
725
|
+
console.log(` → silver: ${silver}`)
|
|
726
|
+
console.log(` → gold: ${gold}`)
|
|
727
|
+
const ok = await askYesNo(rl, `Confirm ${label} picks? [Y/n] (n re-asks all three): `, true)
|
|
728
|
+
if (ok) return { bronze, silver, gold }
|
|
729
|
+
console.log(`[${packageName}] re-asking ${label} picks…`)
|
|
730
|
+
}
|
|
649
731
|
}
|
|
650
732
|
|
|
651
733
|
async function askModel(rl, choices, question, defaultValue) {
|
|
@@ -816,6 +898,201 @@ function getPresetLabel(currentModel) {
|
|
|
816
898
|
return "inherited"
|
|
817
899
|
}
|
|
818
900
|
|
|
901
|
+
function inferModelStrength(modelId) {
|
|
902
|
+
if (typeof modelId !== "string") return 0
|
|
903
|
+
let score = 0
|
|
904
|
+
for (const { re, score: s } of STRENGTH_BOOSTS) {
|
|
905
|
+
if (re.test(modelId)) score += s
|
|
906
|
+
}
|
|
907
|
+
return score
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function sortModelsByStrength(models) {
|
|
911
|
+
return [...models].sort((a, b) => inferModelStrength(a) - inferModelStrength(b))
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
function detectProvidersFromConfig(config) {
|
|
915
|
+
const providers = new Map()
|
|
916
|
+
const note = (providerId, modelId) => {
|
|
917
|
+
if (!providerId) return
|
|
918
|
+
const list = providers.get(providerId) ?? []
|
|
919
|
+
if (modelId && !list.includes(modelId)) list.push(modelId)
|
|
920
|
+
providers.set(providerId, list)
|
|
921
|
+
}
|
|
922
|
+
if (isObject(config.provider)) {
|
|
923
|
+
for (const [providerId, providerConfig] of Object.entries(config.provider)) {
|
|
924
|
+
providers.set(providerId, providers.get(providerId) ?? [])
|
|
925
|
+
if (isObject(providerConfig) && isObject(providerConfig.models)) {
|
|
926
|
+
for (const [modelKey, modelEntry] of Object.entries(providerConfig.models)) {
|
|
927
|
+
if (typeof modelKey === "string" && modelKey.length > 0) {
|
|
928
|
+
note(providerId, qualifyModelId(providerId, modelKey))
|
|
929
|
+
}
|
|
930
|
+
if (typeof modelEntry === "string") note(providerId, qualifyModelId(providerId, modelEntry))
|
|
931
|
+
else if (isObject(modelEntry) && typeof modelEntry.id === "string") {
|
|
932
|
+
note(providerId, qualifyModelId(providerId, modelEntry.id))
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
// Add implicit providers from top-level model and agent overrides
|
|
939
|
+
const additional = []
|
|
940
|
+
if (typeof config.model === "string" && config.model.includes("/")) additional.push(config.model)
|
|
941
|
+
if (isObject(config.agent)) {
|
|
942
|
+
for (const agent of Object.values(config.agent)) {
|
|
943
|
+
if (isObject(agent) && typeof agent.model === "string" && agent.model.includes("/")) {
|
|
944
|
+
additional.push(agent.model)
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
for (const fullId of additional) {
|
|
949
|
+
const slash = fullId.indexOf("/")
|
|
950
|
+
if (slash < 0) continue
|
|
951
|
+
note(fullId.slice(0, slash), fullId)
|
|
952
|
+
}
|
|
953
|
+
return [...providers.entries()]
|
|
954
|
+
.map(([id, models]) => ({ id, models: unique(models) }))
|
|
955
|
+
.filter((p) => p.models.length > 0)
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function pickTierShapeForModelCount(count) {
|
|
959
|
+
if (count >= 3) return "three"
|
|
960
|
+
if (count === 2) return "two"
|
|
961
|
+
return "single"
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function buildGenericResolveModels(tiers) {
|
|
965
|
+
if (tiers.shape === "three") {
|
|
966
|
+
return {
|
|
967
|
+
bronze: tiers.bronze,
|
|
968
|
+
silver: tiers.silver,
|
|
969
|
+
gold: tiers.gold,
|
|
970
|
+
explorer: "bronze",
|
|
971
|
+
coder: "silver",
|
|
972
|
+
resolver: "gold",
|
|
973
|
+
reviewer: "gold",
|
|
974
|
+
"deep-reviewer": "gold",
|
|
975
|
+
planner: "gold",
|
|
976
|
+
architect: "gold",
|
|
977
|
+
debugger: "silver",
|
|
978
|
+
researcher: "bronze",
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
if (tiers.shape === "two") {
|
|
982
|
+
return {
|
|
983
|
+
silver: tiers.silver,
|
|
984
|
+
gold: tiers.gold,
|
|
985
|
+
explorer: "silver",
|
|
986
|
+
coder: "silver",
|
|
987
|
+
resolver: "gold",
|
|
988
|
+
reviewer: "gold",
|
|
989
|
+
"deep-reviewer": "gold",
|
|
990
|
+
planner: "gold",
|
|
991
|
+
architect: "gold",
|
|
992
|
+
debugger: "silver",
|
|
993
|
+
researcher: "silver",
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return {
|
|
997
|
+
gold: tiers.gold,
|
|
998
|
+
explorer: "gold",
|
|
999
|
+
coder: "gold",
|
|
1000
|
+
resolver: "gold",
|
|
1001
|
+
reviewer: "gold",
|
|
1002
|
+
"deep-reviewer": "gold",
|
|
1003
|
+
planner: "gold",
|
|
1004
|
+
architect: "gold",
|
|
1005
|
+
debugger: "gold",
|
|
1006
|
+
researcher: "gold",
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
async function buildGenericInteractivePreset(currentModel, opencodeConfig, scriptedAnswers) {
|
|
1011
|
+
const providers = detectProvidersFromConfig(opencodeConfig)
|
|
1012
|
+
if (providers.length === 0) return undefined
|
|
1013
|
+
|
|
1014
|
+
const rl = createPromptInterface(scriptedAnswers)
|
|
1015
|
+
try {
|
|
1016
|
+
console.log("")
|
|
1017
|
+
console.log("──────────────────────────────────────────────────────────────")
|
|
1018
|
+
console.log(` opencode-resolve setup — provider-agnostic mode`)
|
|
1019
|
+
console.log(` ${providers.length} provider${providers.length === 1 ? "" : "s"} detected in opencode.json`)
|
|
1020
|
+
console.log("──────────────────────────────────────────────────────────────")
|
|
1021
|
+
|
|
1022
|
+
// 1. Pick provider
|
|
1023
|
+
let chosenProvider
|
|
1024
|
+
if (providers.length === 1) {
|
|
1025
|
+
chosenProvider = providers[0]
|
|
1026
|
+
console.log("")
|
|
1027
|
+
console.log(`[${packageName}] only one provider available — using "${chosenProvider.id}"`)
|
|
1028
|
+
} else {
|
|
1029
|
+
console.log("")
|
|
1030
|
+
console.log(`[${packageName}] Step 1/3 — Pick provider:`)
|
|
1031
|
+
providers.forEach((p, i) => {
|
|
1032
|
+
const marker = currentModel?.startsWith(`${p.id}/`) ? " ← top-level model" : ""
|
|
1033
|
+
console.log(` ${i + 1}. ${p.id} (${p.models.length} models)${marker}`)
|
|
1034
|
+
})
|
|
1035
|
+
const defaultIdx = Math.max(1, providers.findIndex((p) => currentModel?.startsWith(`${p.id}/`)) + 1)
|
|
1036
|
+
const valid = providers.map((_, i) => String(i + 1))
|
|
1037
|
+
const answer = await askChoice(rl, `Provider [1..${providers.length}, default ${defaultIdx}]: `, valid, String(defaultIdx))
|
|
1038
|
+
chosenProvider = providers[Number.parseInt(answer, 10) - 1]
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
const sorted = sortModelsByStrength(chosenProvider.models)
|
|
1042
|
+
const defaultShape = pickTierShapeForModelCount(sorted.length)
|
|
1043
|
+
|
|
1044
|
+
// 2. Pick tier shape
|
|
1045
|
+
console.log("")
|
|
1046
|
+
console.log(`[${packageName}] Step 2/3 — Choose tier shape:`)
|
|
1047
|
+
console.log(` 1. single — one model for every role (good for cost simplicity)`)
|
|
1048
|
+
console.log(` 2. two — fast + strong split (explorer/coder + resolver/reviewer/…)`)
|
|
1049
|
+
console.log(` 3. three — bronze + silver + gold full split (recommended when available)`)
|
|
1050
|
+
const shapeDefault = defaultShape === "three" ? "3" : defaultShape === "two" ? "2" : "1"
|
|
1051
|
+
const shapeAns = await askChoice(rl, `Tier shape [1,2,3, default ${shapeDefault}]: `, ["1", "2", "3"], shapeDefault)
|
|
1052
|
+
const shape = shapeAns === "3" ? "three" : shapeAns === "2" ? "two" : "single"
|
|
1053
|
+
|
|
1054
|
+
// 3. Pick models
|
|
1055
|
+
const weakest = sorted[0]
|
|
1056
|
+
const strongest = sorted[sorted.length - 1]
|
|
1057
|
+
const middle = sorted[Math.floor((sorted.length - 1) / 2)]
|
|
1058
|
+
console.log("")
|
|
1059
|
+
console.log(`[${packageName}] Step 3/3 — Pick models (defaults inferred from name strength):`)
|
|
1060
|
+
sorted.forEach((m, i) => console.log(` ${i + 1}. ${m}`))
|
|
1061
|
+
|
|
1062
|
+
while (true) {
|
|
1063
|
+
const tiers = { shape }
|
|
1064
|
+
if (shape === "three") {
|
|
1065
|
+
tiers.bronze = await askModel(rl, sorted, `Bronze (scout for explorer/researcher) [default ${weakest}]: `, weakest)
|
|
1066
|
+
tiers.silver = await askModel(rl, sorted, `Silver (coder/debugger) [default ${middle}]: `, middle)
|
|
1067
|
+
tiers.gold = await askModel(rl, sorted, `Gold (resolver/reviewer/deep-reviewer/planner/architect) [default ${strongest}]: `, strongest)
|
|
1068
|
+
} else if (shape === "two") {
|
|
1069
|
+
tiers.silver = await askModel(rl, sorted, `Silver (fast: coder/explorer/debugger/researcher) [default ${weakest}]: `, weakest)
|
|
1070
|
+
tiers.gold = await askModel(rl, sorted, `Gold (strong: resolver/reviewer/…/architect) [default ${strongest}]: `, strongest)
|
|
1071
|
+
} else {
|
|
1072
|
+
tiers.gold = await askModel(rl, sorted, `Model for all roles [default ${strongest}]: `, strongest)
|
|
1073
|
+
}
|
|
1074
|
+
console.log("")
|
|
1075
|
+
for (const [k, v] of Object.entries(tiers)) {
|
|
1076
|
+
if (k === "shape") continue
|
|
1077
|
+
console.log(` → ${k.padEnd(7)} ${v}`)
|
|
1078
|
+
}
|
|
1079
|
+
const ok = await askYesNo(rl, `Confirm picks? [Y/n] (n re-asks all): `, true)
|
|
1080
|
+
if (ok) {
|
|
1081
|
+
return {
|
|
1082
|
+
label: `generic-${shape}-tier`,
|
|
1083
|
+
profile: "mix",
|
|
1084
|
+
enabled: DEFAULT_ENABLED_AGENTS,
|
|
1085
|
+
models: buildGenericResolveModels(tiers),
|
|
1086
|
+
agents: {},
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
console.log(`[${packageName}] re-asking picks…`)
|
|
1090
|
+
}
|
|
1091
|
+
} finally {
|
|
1092
|
+
rl.close()
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
819
1096
|
function isGLMModel(currentModel) {
|
|
820
1097
|
if (!currentModel) return false
|
|
821
1098
|
const lower = currentModel.toLowerCase()
|