opencode-resolve 0.1.18 → 0.1.20

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 CHANGED
@@ -16,8 +16,8 @@
16
16
  독립 실행형 앱, 모델 프로바이더, API 키 관리자, `opencode.json` 대체물이 아닙니다.
17
17
 
18
18
  ```sh
19
- npm install -g opencode-resolve
20
- opencode plugin opencode-resolve --global --force
19
+ npm install -g opencode-resolve@latest
20
+ opencode-resolve setup
21
21
  ```
22
22
 
23
23
  ## 목차
package/README.md CHANGED
@@ -16,8 +16,8 @@
16
16
  It is not a standalone app, model provider, API key manager, or replacement for `opencode.json`.
17
17
 
18
18
  ```sh
19
- npm install -g opencode-resolve
20
- opencode plugin opencode-resolve --global --force
19
+ npm install -g opencode-resolve@latest
20
+ opencode-resolve setup
21
21
  ```
22
22
 
23
23
  ## Contents
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-resolve",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
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/",
@@ -20,9 +20,24 @@ const ADDITIVE_DEFAULTS = {
20
20
  autoApprove: true,
21
21
  }
22
22
 
23
- const DEFAULT_ENABLED_AGENTS = ["coder", "resolver", "explorer", "reviewer", "deep-reviewer", "planner"]
24
- const GPT_ENABLED_AGENTS = ["coder", "resolver", "gpt", "explorer", "reviewer", "deep-reviewer", "planner"]
25
- const GLM_ENABLED_AGENTS = ["coder", "resolver", "glm", "explorer", "reviewer", "planner"]
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.
@@ -293,11 +308,16 @@ async function chooseExistingResolveConfigAction(scriptedAnswers) {
293
308
  const rl = createPromptInterface(scriptedAnswers)
294
309
  try {
295
310
  console.log("")
296
- console.log(`[${packageName}] Existing resolve config found: ${resolveConfigPath}`)
297
- console.log(" 1. update existing config — preserve your settings and add missing defaults")
298
- console.log(" 2. reconfigure models — preserve the rest of resolve.json")
299
- console.log(" 3. fresh reinstall — back up resolve.json and run setup again, preserving model pins")
300
- const answer = await askChoice(rl, "Existing config [1=update, 2=models, 3=fresh, default 1]: ", ["1", "2", "3"], "1")
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")
301
321
  if (answer === "2") return "models"
302
322
  return answer === "3" ? "fresh" : "update"
303
323
  } finally {
@@ -337,7 +357,7 @@ async function createAdaptiveResolveConfig(opencodeConfig, scriptedAnswers, opti
337
357
  (process.stdin.isTTY && process.stdout.isTTY) || forcePrompt,
338
358
  )
339
359
  const interactivePreset = canPrompt
340
- ? await buildInteractivePreset(currentModel, allModels, scriptedAnswers)
360
+ ? await buildInteractivePreset(currentModel, allModels, scriptedAnswers, opencodeConfig)
341
361
  : undefined
342
362
 
343
363
  if (interactivePreset) {
@@ -413,7 +433,7 @@ async function reconfigureExistingModels(opencodeConfig, scriptedAnswers) {
413
433
  const forcePrompt = readInstallerOption("force_prompt") === "1"
414
434
  const canPrompt = Boolean((process.stdin.isTTY && process.stdout.isTTY) || forcePrompt)
415
435
  const interactivePreset = canPrompt
416
- ? await buildInteractivePreset(currentModel, allModels, scriptedAnswers)
436
+ ? await buildInteractivePreset(currentModel, allModels, scriptedAnswers, opencodeConfig)
417
437
  : undefined
418
438
  const preset = interactivePreset ?? {
419
439
  label: getPresetLabel(currentModel),
@@ -582,7 +602,7 @@ function buildGPTOnlyPreset(model, gptModels) {
582
602
  }
583
603
  }
584
604
 
585
- async function buildInteractivePreset(currentModel, allModels, scriptedAnswers) {
605
+ async function buildInteractivePreset(currentModel, allModels, scriptedAnswers, opencodeConfig) {
586
606
  const choices = {
587
607
  gpt: collectModelChoices(allModels, isGPTModel, OPENAI_MODEL_HINTS),
588
608
  glm: collectModelChoices(allModels, isGLMModel, GLM_MODEL_HINTS),
@@ -592,6 +612,11 @@ async function buildInteractivePreset(currentModel, allModels, scriptedAnswers)
592
612
  if (isGLMModel(currentModel)) choices.glm = unique([currentModel, ...choices.glm])
593
613
  }
594
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
+
595
620
  const rl = createPromptInterface(scriptedAnswers)
596
621
  try {
597
622
  console.log("")
@@ -601,10 +626,22 @@ async function buildInteractivePreset(currentModel, allModels, scriptedAnswers)
601
626
  console.log("──────────────────────────────────────────────────────────────")
602
627
  console.log("")
603
628
  console.log(`[${packageName}] Step 1/2 — Choose resolve profile:`)
604
- console.log(" 1. mix — neutral resolver plus optional Codex and GLM primary agents (recommended)")
629
+ console.log(` 1. mix — neutral resolver plus optional Codex and GLM primary agents${offerAuto ? "" : " (recommended)"}`)
605
630
  console.log(" 2. gpt — GPT/Codex-only, three-tier")
606
631
  console.log(" 3. glm — GLM-only, three-tier")
607
- const profileAnswer = await askChoice(rl, "Profile [1=mix, 2=gpt, 3=glm, default 1]: ", ["1", "2", "3"], "1")
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
+
608
645
  const profile = profileAnswer === "2" ? "gpt" : profileAnswer === "3" ? "glm" : "mix"
609
646
 
610
647
  if (profile === "gpt") {
@@ -620,12 +657,9 @@ async function buildInteractivePreset(currentModel, allModels, scriptedAnswers)
620
657
  }
621
658
 
622
659
  if (profile === "glm") {
623
- const useCodingPlan = await askYesNo(rl, "Use coding-plan (zai-coding-plan) instead of standard (zai)? [y/N]: ", false)
624
- const glmChoices = useCodingPlan
625
- ? choices.glm.map((m) => m.replace(/^zai\//, "zai-coding-plan/"))
626
- : choices.glm.map((m) => m.replace(/^zai-coding-plan\//, "zai/"))
627
- const deduped = unique([...glmChoices.filter((m) => isGLMModel(m)), ...GLM_MODEL_HINTS])
628
- const tiers = await askThreeTier(rl, "GLM", deduped)
660
+ // zai and zai-coding-plan are distinct providers show whatever the user actually
661
+ // has configured. No rewriting between them.
662
+ const tiers = await askThreeTier(rl, "GLM", choices.glm)
629
663
  return {
630
664
  label: "glm-three-tier",
631
665
  profile: "glm",
@@ -675,14 +709,22 @@ function createPromptInterface(scriptedAnswers) {
675
709
 
676
710
  async function askThreeTier(rl, label, models) {
677
711
  const choices = models.length > 0 ? models : (label.toLowerCase().includes("glm") ? GLM_MODEL_HINTS : OPENAI_MODEL_HINTS)
678
- console.log("")
679
- console.log(`[${packageName}] ${label} model choices:`)
680
- choices.forEach((model, index) => console.log(` ${index + 1}. ${model}`))
681
712
  const defaults = chooseThreeTier(choices, label.toLowerCase().includes("glm") ? "glm" : "gpt")
682
- const bronze = await askModel(rl, choices, `Pick ${label} bronze/scout [default ${defaults.bronze}]: `, defaults.bronze)
683
- const silver = await askModel(rl, choices, `Pick ${label} silver/coder [default ${defaults.silver}]: `, defaults.silver)
684
- const gold = await askModel(rl, choices, `Pick ${label} gold/reasoner [default ${defaults.gold}]: `, defaults.gold)
685
- return { bronze, silver, gold }
713
+ while (true) {
714
+ console.log("")
715
+ console.log(`[${packageName}] ${label} model choices:`)
716
+ choices.forEach((model, index) => console.log(` ${index + 1}. ${model}`))
717
+ const bronze = await askModel(rl, choices, `Pick ${label} bronze/scout [default ${defaults.bronze}]: `, defaults.bronze)
718
+ const silver = await askModel(rl, choices, `Pick ${label} silver/coder [default ${defaults.silver}]: `, defaults.silver)
719
+ const gold = await askModel(rl, choices, `Pick ${label} gold/reasoner [default ${defaults.gold}]: `, defaults.gold)
720
+ console.log("")
721
+ console.log(` → bronze: ${bronze}`)
722
+ console.log(` → silver: ${silver}`)
723
+ console.log(` → gold: ${gold}`)
724
+ const ok = await askYesNo(rl, `Confirm ${label} picks? [Y/n] (n re-asks all three): `, true)
725
+ if (ok) return { bronze, silver, gold }
726
+ console.log(`[${packageName}] re-asking ${label} picks…`)
727
+ }
686
728
  }
687
729
 
688
730
  async function askModel(rl, choices, question, defaultValue) {
@@ -771,21 +813,30 @@ function buildGLMThreeTierModels(tiers) {
771
813
 
772
814
  function collectModelChoices(allModels, predicate, hints, includeFallbackHints = true) {
773
815
  const detected = allModels.filter(predicate)
774
- const providerIds = new Set(detected.map((model) => model.split("/")[0]).filter(Boolean))
775
- const matchingHints = includeFallbackHints
776
- ? hints.filter((model) => providerIds.size === 0 || providerIds.has(model.split("/")[0]) || detected.length < 3)
777
- : []
778
- const choices = unique([...detected, ...matchingHints])
816
+ if (detected.length > 0) {
817
+ // Only show the user's own models when they have any — never pollute the picker
818
+ // with hint IDs they don't actually have configured in opencode.json.
819
+ return predicate === isGLMModel ? sortGLMModelChoices(unique(detected)) : unique(detected)
820
+ }
821
+ // Fallback: user has zero models of this family — show the hint list so the
822
+ // picker still has something to display.
823
+ if (!includeFallbackHints) return []
824
+ const choices = unique(hints)
779
825
  return predicate === isGLMModel ? sortGLMModelChoices(choices) : choices
780
826
  }
781
827
 
782
- function chooseThreeTier(models, family, includeFallbackHints = true) {
783
- const fallback = family === "glm" ? GLM_MODEL_HINTS : OPENAI_MODEL_HINTS
784
- const choices = unique(includeFallbackHints ? [...models, ...fallback] : models)
828
+ function chooseThreeTier(models, family) {
829
+ const choices = unique(models)
830
+ if (choices.length === 0) {
831
+ const fallback = family === "glm" ? GLM_MODEL_HINTS : OPENAI_MODEL_HINTS
832
+ return chooseThreeTier(fallback, family)
833
+ }
834
+ // Sort by inferred strength (weak → strong) and pick bronze/silver/gold by position.
835
+ const sorted = sortModelsByStrength(choices)
785
836
  return {
786
- bronze: preferModel(choices, family === "glm" ? ["5.1", "4.5", "5"] : ["spark", "mini", "4o-mini"], choices[0]),
787
- silver: preferModel(choices, family === "glm" ? ["5.1", "4.5", "5"] : ["codex", "5.3", "5.2"], choices[1] ?? choices[0]),
788
- gold: preferModel(choices, family === "glm" ? ["5.1", "5", "4.5"] : ["5.5", "5.4", "gpt-5.3-codex"], choices[2] ?? choices[1] ?? choices[0]),
837
+ bronze: sorted[0],
838
+ silver: sorted[Math.min(1, sorted.length - 1)] ?? sorted[0],
839
+ gold: sorted[sorted.length - 1],
789
840
  }
790
841
  }
791
842
 
@@ -853,6 +904,201 @@ function getPresetLabel(currentModel) {
853
904
  return "inherited"
854
905
  }
855
906
 
907
+ function inferModelStrength(modelId) {
908
+ if (typeof modelId !== "string") return 0
909
+ let score = 0
910
+ for (const { re, score: s } of STRENGTH_BOOSTS) {
911
+ if (re.test(modelId)) score += s
912
+ }
913
+ return score
914
+ }
915
+
916
+ function sortModelsByStrength(models) {
917
+ return [...models].sort((a, b) => inferModelStrength(a) - inferModelStrength(b))
918
+ }
919
+
920
+ function detectProvidersFromConfig(config) {
921
+ const providers = new Map()
922
+ const note = (providerId, modelId) => {
923
+ if (!providerId) return
924
+ const list = providers.get(providerId) ?? []
925
+ if (modelId && !list.includes(modelId)) list.push(modelId)
926
+ providers.set(providerId, list)
927
+ }
928
+ if (isObject(config.provider)) {
929
+ for (const [providerId, providerConfig] of Object.entries(config.provider)) {
930
+ providers.set(providerId, providers.get(providerId) ?? [])
931
+ if (isObject(providerConfig) && isObject(providerConfig.models)) {
932
+ for (const [modelKey, modelEntry] of Object.entries(providerConfig.models)) {
933
+ if (typeof modelKey === "string" && modelKey.length > 0) {
934
+ note(providerId, qualifyModelId(providerId, modelKey))
935
+ }
936
+ if (typeof modelEntry === "string") note(providerId, qualifyModelId(providerId, modelEntry))
937
+ else if (isObject(modelEntry) && typeof modelEntry.id === "string") {
938
+ note(providerId, qualifyModelId(providerId, modelEntry.id))
939
+ }
940
+ }
941
+ }
942
+ }
943
+ }
944
+ // Add implicit providers from top-level model and agent overrides
945
+ const additional = []
946
+ if (typeof config.model === "string" && config.model.includes("/")) additional.push(config.model)
947
+ if (isObject(config.agent)) {
948
+ for (const agent of Object.values(config.agent)) {
949
+ if (isObject(agent) && typeof agent.model === "string" && agent.model.includes("/")) {
950
+ additional.push(agent.model)
951
+ }
952
+ }
953
+ }
954
+ for (const fullId of additional) {
955
+ const slash = fullId.indexOf("/")
956
+ if (slash < 0) continue
957
+ note(fullId.slice(0, slash), fullId)
958
+ }
959
+ return [...providers.entries()]
960
+ .map(([id, models]) => ({ id, models: unique(models) }))
961
+ .filter((p) => p.models.length > 0)
962
+ }
963
+
964
+ function pickTierShapeForModelCount(count) {
965
+ if (count >= 3) return "three"
966
+ if (count === 2) return "two"
967
+ return "single"
968
+ }
969
+
970
+ function buildGenericResolveModels(tiers) {
971
+ if (tiers.shape === "three") {
972
+ return {
973
+ bronze: tiers.bronze,
974
+ silver: tiers.silver,
975
+ gold: tiers.gold,
976
+ explorer: "bronze",
977
+ coder: "silver",
978
+ resolver: "gold",
979
+ reviewer: "gold",
980
+ "deep-reviewer": "gold",
981
+ planner: "gold",
982
+ architect: "gold",
983
+ debugger: "silver",
984
+ researcher: "bronze",
985
+ }
986
+ }
987
+ if (tiers.shape === "two") {
988
+ return {
989
+ silver: tiers.silver,
990
+ gold: tiers.gold,
991
+ explorer: "silver",
992
+ coder: "silver",
993
+ resolver: "gold",
994
+ reviewer: "gold",
995
+ "deep-reviewer": "gold",
996
+ planner: "gold",
997
+ architect: "gold",
998
+ debugger: "silver",
999
+ researcher: "silver",
1000
+ }
1001
+ }
1002
+ return {
1003
+ gold: tiers.gold,
1004
+ explorer: "gold",
1005
+ coder: "gold",
1006
+ resolver: "gold",
1007
+ reviewer: "gold",
1008
+ "deep-reviewer": "gold",
1009
+ planner: "gold",
1010
+ architect: "gold",
1011
+ debugger: "gold",
1012
+ researcher: "gold",
1013
+ }
1014
+ }
1015
+
1016
+ async function buildGenericInteractivePreset(currentModel, opencodeConfig, scriptedAnswers) {
1017
+ const providers = detectProvidersFromConfig(opencodeConfig)
1018
+ if (providers.length === 0) return undefined
1019
+
1020
+ const rl = createPromptInterface(scriptedAnswers)
1021
+ try {
1022
+ console.log("")
1023
+ console.log("──────────────────────────────────────────────────────────────")
1024
+ console.log(` opencode-resolve setup — provider-agnostic mode`)
1025
+ console.log(` ${providers.length} provider${providers.length === 1 ? "" : "s"} detected in opencode.json`)
1026
+ console.log("──────────────────────────────────────────────────────────────")
1027
+
1028
+ // 1. Pick provider
1029
+ let chosenProvider
1030
+ if (providers.length === 1) {
1031
+ chosenProvider = providers[0]
1032
+ console.log("")
1033
+ console.log(`[${packageName}] only one provider available — using "${chosenProvider.id}"`)
1034
+ } else {
1035
+ console.log("")
1036
+ console.log(`[${packageName}] Step 1/3 — Pick provider:`)
1037
+ providers.forEach((p, i) => {
1038
+ const marker = currentModel?.startsWith(`${p.id}/`) ? " ← top-level model" : ""
1039
+ console.log(` ${i + 1}. ${p.id} (${p.models.length} models)${marker}`)
1040
+ })
1041
+ const defaultIdx = Math.max(1, providers.findIndex((p) => currentModel?.startsWith(`${p.id}/`)) + 1)
1042
+ const valid = providers.map((_, i) => String(i + 1))
1043
+ const answer = await askChoice(rl, `Provider [1..${providers.length}, default ${defaultIdx}]: `, valid, String(defaultIdx))
1044
+ chosenProvider = providers[Number.parseInt(answer, 10) - 1]
1045
+ }
1046
+
1047
+ const sorted = sortModelsByStrength(chosenProvider.models)
1048
+ const defaultShape = pickTierShapeForModelCount(sorted.length)
1049
+
1050
+ // 2. Pick tier shape
1051
+ console.log("")
1052
+ console.log(`[${packageName}] Step 2/3 — Choose tier shape:`)
1053
+ console.log(` 1. single — one model for every role (good for cost simplicity)`)
1054
+ console.log(` 2. two — fast + strong split (explorer/coder + resolver/reviewer/…)`)
1055
+ console.log(` 3. three — bronze + silver + gold full split (recommended when available)`)
1056
+ const shapeDefault = defaultShape === "three" ? "3" : defaultShape === "two" ? "2" : "1"
1057
+ const shapeAns = await askChoice(rl, `Tier shape [1,2,3, default ${shapeDefault}]: `, ["1", "2", "3"], shapeDefault)
1058
+ const shape = shapeAns === "3" ? "three" : shapeAns === "2" ? "two" : "single"
1059
+
1060
+ // 3. Pick models
1061
+ const weakest = sorted[0]
1062
+ const strongest = sorted[sorted.length - 1]
1063
+ const middle = sorted[Math.floor((sorted.length - 1) / 2)]
1064
+ console.log("")
1065
+ console.log(`[${packageName}] Step 3/3 — Pick models (defaults inferred from name strength):`)
1066
+ sorted.forEach((m, i) => console.log(` ${i + 1}. ${m}`))
1067
+
1068
+ while (true) {
1069
+ const tiers = { shape }
1070
+ if (shape === "three") {
1071
+ tiers.bronze = await askModel(rl, sorted, `Bronze (scout for explorer/researcher) [default ${weakest}]: `, weakest)
1072
+ tiers.silver = await askModel(rl, sorted, `Silver (coder/debugger) [default ${middle}]: `, middle)
1073
+ tiers.gold = await askModel(rl, sorted, `Gold (resolver/reviewer/deep-reviewer/planner/architect) [default ${strongest}]: `, strongest)
1074
+ } else if (shape === "two") {
1075
+ tiers.silver = await askModel(rl, sorted, `Silver (fast: coder/explorer/debugger/researcher) [default ${weakest}]: `, weakest)
1076
+ tiers.gold = await askModel(rl, sorted, `Gold (strong: resolver/reviewer/…/architect) [default ${strongest}]: `, strongest)
1077
+ } else {
1078
+ tiers.gold = await askModel(rl, sorted, `Model for all roles [default ${strongest}]: `, strongest)
1079
+ }
1080
+ console.log("")
1081
+ for (const [k, v] of Object.entries(tiers)) {
1082
+ if (k === "shape") continue
1083
+ console.log(` → ${k.padEnd(7)} ${v}`)
1084
+ }
1085
+ const ok = await askYesNo(rl, `Confirm picks? [Y/n] (n re-asks all): `, true)
1086
+ if (ok) {
1087
+ return {
1088
+ label: `generic-${shape}-tier`,
1089
+ profile: "mix",
1090
+ enabled: DEFAULT_ENABLED_AGENTS,
1091
+ models: buildGenericResolveModels(tiers),
1092
+ agents: {},
1093
+ }
1094
+ }
1095
+ console.log(`[${packageName}] re-asking picks…`)
1096
+ }
1097
+ } finally {
1098
+ rl.close()
1099
+ }
1100
+ }
1101
+
856
1102
  function isGLMModel(currentModel) {
857
1103
  if (!currentModel) return false
858
1104
  const lower = currentModel.toLowerCase()