oh-my-opencode-slim 0.8.1 → 0.8.3

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/dist/cli/index.js CHANGED
@@ -11,73 +11,6 @@ var __export = (target, all) => {
11
11
  });
12
12
  };
13
13
 
14
- // src/cli/install.ts
15
- import * as readline from "readline/promises";
16
-
17
- // src/cli/model-selection.ts
18
- function defaultTieBreaker(left, right) {
19
- return left.model.localeCompare(right.model);
20
- }
21
- function rankModels(models, scoreFn, options = {}) {
22
- const excluded = new Set(options.excludeModels ?? []);
23
- const tieBreaker = options.tieBreaker ?? defaultTieBreaker;
24
- return models.filter((model) => !excluded.has(model.model)).map((candidate) => ({
25
- candidate,
26
- score: scoreFn(candidate)
27
- })).sort((left, right) => {
28
- if (left.score !== right.score)
29
- return right.score - left.score;
30
- return tieBreaker(left.candidate, right.candidate);
31
- });
32
- }
33
- function pickBestModel(models, scoreFn, options = {}) {
34
- return rankModels(models, scoreFn, options)[0]?.candidate ?? null;
35
- }
36
- function pickPrimaryAndSupport(models, scoring, preferredPrimaryModel) {
37
- if (models.length === 0)
38
- return { primary: null, support: null };
39
- const preferredPrimary = preferredPrimaryModel ? models.find((candidate) => candidate.model === preferredPrimaryModel) : undefined;
40
- const primary = preferredPrimary ?? pickBestModel(models, scoring.primary);
41
- if (!primary)
42
- return { primary: null, support: null };
43
- const support = pickBestModel(models, scoring.support, {
44
- excludeModels: [primary.model]
45
- }) ?? pickBestModel(models, scoring.support);
46
- return { primary, support };
47
- }
48
-
49
- // src/cli/chutes-selection.ts
50
- function speedBonus(modelName) {
51
- const lower = modelName.toLowerCase();
52
- let score = 0;
53
- if (lower.includes("nano"))
54
- score += 60;
55
- if (lower.includes("flash"))
56
- score += 45;
57
- if (lower.includes("mini"))
58
- score += 30;
59
- if (lower.includes("lite"))
60
- score += 20;
61
- if (lower.includes("small"))
62
- score += 15;
63
- return score;
64
- }
65
- var scoreChutesPrimaryForCoding = (model) => {
66
- return (model.reasoning ? 120 : 0) + (model.toolcall ? 80 : 0) + (model.attachment ? 20 : 0) + Math.min(model.contextLimit, 1e6) / 9000 + Math.min(model.outputLimit, 300000) / 1e4 + (model.status === "active" ? 10 : 0);
67
- };
68
- var scoreChutesSupportForCoding = (model) => {
69
- return (model.toolcall ? 90 : 0) + (model.reasoning ? 35 : 0) + speedBonus(model.model) + Math.min(model.contextLimit, 400000) / 20000 + (model.status === "active" ? 8 : 0);
70
- };
71
- function pickBestCodingChutesModel(models) {
72
- return pickBestModel(models, scoreChutesPrimaryForCoding);
73
- }
74
- function pickSupportChutesModel(models, primaryModel) {
75
- const { support } = pickPrimaryAndSupport(models, {
76
- primary: scoreChutesPrimaryForCoding,
77
- support: scoreChutesSupportForCoding
78
- }, primaryModel);
79
- return support;
80
- }
81
14
  // src/cli/config-io.ts
82
15
  import {
83
16
  copyFileSync as copyFileSync2,
@@ -13704,7 +13637,16 @@ var FallbackChainsSchema = exports_external.object({
13704
13637
  fixer: AgentModelChainSchema.optional()
13705
13638
  }).catchall(AgentModelChainSchema);
13706
13639
  var AgentOverrideConfigSchema = exports_external.object({
13707
- model: exports_external.string().optional(),
13640
+ model: exports_external.union([
13641
+ exports_external.string(),
13642
+ exports_external.array(exports_external.union([
13643
+ exports_external.string(),
13644
+ exports_external.object({
13645
+ id: exports_external.string(),
13646
+ variant: exports_external.string().optional()
13647
+ })
13648
+ ]))
13649
+ ]).optional(),
13708
13650
  temperature: exports_external.number().min(0).max(2).optional(),
13709
13651
  variant: exports_external.string().optional().catch(undefined),
13710
13652
  skills: exports_external.array(exports_external.string()).optional(),
@@ -13873,15 +13815,15 @@ function installSkill(skill) {
13873
13815
  }
13874
13816
 
13875
13817
  // src/cli/providers.ts
13876
- var AGENT_NAMES = [
13877
- "orchestrator",
13878
- "oracle",
13879
- "designer",
13880
- "explorer",
13881
- "librarian",
13882
- "fixer"
13883
- ];
13884
13818
  var MODEL_MAPPINGS = {
13819
+ openai: {
13820
+ orchestrator: { model: "openai/gpt-5.4" },
13821
+ oracle: { model: "openai/gpt-5.4", variant: "high" },
13822
+ librarian: { model: "openai/gpt-5-codex", variant: "low" },
13823
+ explorer: { model: "openai/gpt-5-codex", variant: "low" },
13824
+ designer: { model: "openai/gpt-5-codex", variant: "medium" },
13825
+ fixer: { model: "openai/gpt-5-codex", variant: "low" }
13826
+ },
13885
13827
  kimi: {
13886
13828
  orchestrator: { model: "kimi-for-coding/k2p5" },
13887
13829
  oracle: { model: "kimi-for-coding/k2p5", variant: "high" },
@@ -13890,212 +13832,28 @@ var MODEL_MAPPINGS = {
13890
13832
  designer: { model: "kimi-for-coding/k2p5", variant: "medium" },
13891
13833
  fixer: { model: "kimi-for-coding/k2p5", variant: "low" }
13892
13834
  },
13893
- openai: {
13894
- orchestrator: { model: "openai/gpt-5.3-codex" },
13895
- oracle: { model: "openai/gpt-5.3-codex", variant: "high" },
13896
- librarian: { model: "openai/gpt-5.1-codex-mini", variant: "low" },
13897
- explorer: { model: "openai/gpt-5.1-codex-mini", variant: "low" },
13898
- designer: { model: "openai/gpt-5.1-codex-mini", variant: "medium" },
13899
- fixer: { model: "openai/gpt-5.1-codex-mini", variant: "low" }
13900
- },
13901
- anthropic: {
13902
- orchestrator: { model: "anthropic/claude-opus-4-6" },
13903
- oracle: { model: "anthropic/claude-opus-4-6", variant: "high" },
13904
- librarian: { model: "anthropic/claude-sonnet-4-5", variant: "low" },
13905
- explorer: { model: "anthropic/claude-haiku-4-5", variant: "low" },
13906
- designer: { model: "anthropic/claude-sonnet-4-5", variant: "medium" },
13907
- fixer: { model: "anthropic/claude-sonnet-4-5", variant: "low" }
13908
- },
13909
13835
  copilot: {
13910
- orchestrator: { model: "github-copilot/grok-code-fast-1" },
13911
- oracle: { model: "github-copilot/grok-code-fast-1", variant: "high" },
13836
+ orchestrator: { model: "github-copilot/claude-opus-4.6" },
13837
+ oracle: { model: "github-copilot/claude-opus-4.6", variant: "high" },
13912
13838
  librarian: { model: "github-copilot/grok-code-fast-1", variant: "low" },
13913
13839
  explorer: { model: "github-copilot/grok-code-fast-1", variant: "low" },
13914
- designer: { model: "github-copilot/grok-code-fast-1", variant: "medium" },
13915
- fixer: { model: "github-copilot/grok-code-fast-1", variant: "low" }
13840
+ designer: { model: "github-copilot/gemini-3.1-pro-preview", variant: "medium" },
13841
+ fixer: { model: "github-copilot/claude-sonnet-4.6", variant: "low" }
13916
13842
  },
13917
13843
  "zai-plan": {
13918
- orchestrator: { model: "zai-coding-plan/glm-4.7" },
13919
- oracle: { model: "zai-coding-plan/glm-4.7", variant: "high" },
13920
- librarian: { model: "zai-coding-plan/glm-4.7", variant: "low" },
13921
- explorer: { model: "zai-coding-plan/glm-4.7", variant: "low" },
13922
- designer: { model: "zai-coding-plan/glm-4.7", variant: "medium" },
13923
- fixer: { model: "zai-coding-plan/glm-4.7", variant: "low" }
13924
- },
13925
- antigravity: {
13926
- orchestrator: { model: "google/antigravity-gemini-3-flash" },
13927
- oracle: { model: "google/antigravity-gemini-3.1-pro" },
13928
- librarian: {
13929
- model: "google/antigravity-gemini-3-flash",
13930
- variant: "low"
13931
- },
13932
- explorer: {
13933
- model: "google/antigravity-gemini-3-flash",
13934
- variant: "low"
13935
- },
13936
- designer: {
13937
- model: "google/antigravity-gemini-3-flash",
13938
- variant: "medium"
13939
- },
13940
- fixer: { model: "google/antigravity-gemini-3-flash", variant: "low" }
13941
- },
13942
- chutes: {
13943
- orchestrator: { model: "chutes/kimi-k2.5" },
13944
- oracle: { model: "chutes/kimi-k2.5", variant: "high" },
13945
- librarian: { model: "chutes/minimax-m2.1", variant: "low" },
13946
- explorer: { model: "chutes/minimax-m2.1", variant: "low" },
13947
- designer: { model: "chutes/kimi-k2.5", variant: "medium" },
13948
- fixer: { model: "chutes/minimax-m2.1", variant: "low" }
13949
- },
13950
- "zen-free": {
13951
- orchestrator: { model: "opencode/big-pickle" },
13952
- oracle: { model: "opencode/big-pickle", variant: "high" },
13953
- librarian: { model: "opencode/big-pickle", variant: "low" },
13954
- explorer: { model: "opencode/big-pickle", variant: "low" },
13955
- designer: { model: "opencode/big-pickle", variant: "medium" },
13956
- fixer: { model: "opencode/big-pickle", variant: "low" }
13844
+ orchestrator: { model: "zai-coding-plan/glm-5" },
13845
+ oracle: { model: "zai-coding-plan/glm-5", variant: "high" },
13846
+ librarian: { model: "zai-coding-plan/glm-5", variant: "low" },
13847
+ explorer: { model: "zai-coding-plan/glm-5", variant: "low" },
13848
+ designer: { model: "zai-coding-plan/glm-5", variant: "medium" },
13849
+ fixer: { model: "zai-coding-plan/glm-5", variant: "low" }
13957
13850
  }
13958
13851
  };
13959
- function generateAntigravityMixedPreset(config2, existingPreset) {
13960
- const result = existingPreset ? { ...existingPreset } : {};
13961
- const createAgentConfig = (agentName, modelInfo) => {
13962
- const isOrchestrator = agentName === "orchestrator";
13963
- const skills = isOrchestrator ? ["*"] : RECOMMENDED_SKILLS.filter((s) => s.allowedAgents.includes("*") || s.allowedAgents.includes(agentName)).map((s) => s.skillName);
13964
- if (agentName === "designer" && !skills.includes("agent-browser")) {
13965
- skills.push("agent-browser");
13966
- }
13967
- return {
13968
- model: modelInfo.model,
13969
- variant: modelInfo.variant,
13970
- skills,
13971
- mcps: DEFAULT_AGENT_MCPS[agentName] ?? []
13972
- };
13973
- };
13974
- const antigravityFlash = {
13975
- model: "google/antigravity-gemini-3-flash"
13976
- };
13977
- const chutesPrimary = config2.selectedChutesPrimaryModel ?? MODEL_MAPPINGS.chutes.orchestrator.model;
13978
- const chutesSupport = config2.selectedChutesSecondaryModel ?? MODEL_MAPPINGS.chutes.explorer.model;
13979
- if (config2.hasKimi) {
13980
- result.orchestrator = createAgentConfig("orchestrator", MODEL_MAPPINGS.kimi.orchestrator);
13981
- } else if (config2.hasChutes) {
13982
- result.orchestrator = createAgentConfig("orchestrator", {
13983
- model: chutesPrimary
13984
- });
13985
- } else if (!result.orchestrator) {
13986
- result.orchestrator = createAgentConfig("orchestrator", MODEL_MAPPINGS.antigravity.orchestrator);
13987
- }
13988
- if (config2.hasOpenAI) {
13989
- result.oracle = createAgentConfig("oracle", MODEL_MAPPINGS.openai.oracle);
13990
- } else if (!result.oracle) {
13991
- result.oracle = createAgentConfig("oracle", MODEL_MAPPINGS.antigravity.oracle);
13992
- }
13993
- result.explorer = createAgentConfig("explorer", {
13994
- ...antigravityFlash,
13995
- variant: "low"
13996
- });
13997
- if (config2.hasChutes) {
13998
- result.librarian = createAgentConfig("librarian", {
13999
- model: chutesSupport,
14000
- variant: "low"
14001
- });
14002
- result.designer = createAgentConfig("designer", {
14003
- model: chutesPrimary,
14004
- variant: "medium"
14005
- });
14006
- } else {
14007
- result.librarian = createAgentConfig("librarian", {
14008
- ...antigravityFlash,
14009
- variant: "low"
14010
- });
14011
- result.designer = createAgentConfig("designer", {
14012
- ...antigravityFlash,
14013
- variant: "medium"
14014
- });
14015
- }
14016
- if (config2.hasOpenAI) {
14017
- result.fixer = createAgentConfig("fixer", {
14018
- ...MODEL_MAPPINGS.openai.oracle,
14019
- variant: "low"
14020
- });
14021
- } else if (config2.hasChutes) {
14022
- result.fixer = createAgentConfig("fixer", {
14023
- model: chutesSupport,
14024
- variant: "low"
14025
- });
14026
- } else {
14027
- result.fixer = createAgentConfig("fixer", {
14028
- ...antigravityFlash,
14029
- variant: "low"
14030
- });
14031
- }
14032
- return result;
14033
- }
14034
13852
  function generateLiteConfig(installConfig) {
14035
13853
  const config2 = {
14036
- preset: "zen-free",
14037
- presets: {},
14038
- balanceProviderUsage: installConfig.balanceProviderUsage ?? false
14039
- };
14040
- if (installConfig.setupMode === "manual" && installConfig.manualAgentConfigs) {
14041
- config2.preset = "manual";
14042
- const manualPreset = {};
14043
- const chains = {};
14044
- for (const agentName of AGENT_NAMES) {
14045
- const manualConfig = installConfig.manualAgentConfigs[agentName];
14046
- if (manualConfig) {
14047
- manualPreset[agentName] = {
14048
- model: manualConfig.primary,
14049
- skills: agentName === "orchestrator" ? ["*"] : RECOMMENDED_SKILLS.filter((s) => s.allowedAgents.includes("*") || s.allowedAgents.includes(agentName)).map((s) => s.skillName),
14050
- mcps: DEFAULT_AGENT_MCPS[agentName] ?? []
14051
- };
14052
- const fallbackChain = [
14053
- manualConfig.primary,
14054
- manualConfig.fallback1,
14055
- manualConfig.fallback2,
14056
- manualConfig.fallback3
14057
- ].filter((m, i, arr) => m && arr.indexOf(m) === i);
14058
- chains[agentName] = fallbackChain;
14059
- }
14060
- }
14061
- config2.presets.manual = manualPreset;
14062
- config2.fallback = {
14063
- enabled: true,
14064
- timeoutMs: 15000,
14065
- chains
14066
- };
14067
- if (installConfig.hasTmux) {
14068
- config2.tmux = {
14069
- enabled: true,
14070
- layout: "main-vertical",
14071
- main_pane_size: 60
14072
- };
14073
- }
14074
- return config2;
14075
- }
14076
- let activePreset = "zen-free";
14077
- if (installConfig.hasAntigravity && installConfig.hasKimi && installConfig.hasOpenAI) {
14078
- activePreset = "antigravity-mixed-both";
14079
- } else if (installConfig.hasAntigravity && installConfig.hasKimi) {
14080
- activePreset = "antigravity-mixed-kimi";
14081
- } else if (installConfig.hasAntigravity && installConfig.hasOpenAI) {
14082
- activePreset = "antigravity-mixed-openai";
14083
- } else if (installConfig.hasAntigravity) {
14084
- activePreset = "antigravity";
14085
- } else if (installConfig.hasKimi) {
14086
- activePreset = "kimi";
14087
- } else if (installConfig.hasOpenAI) {
14088
- activePreset = "openai";
14089
- } else if (installConfig.hasAnthropic) {
14090
- activePreset = "anthropic";
14091
- } else if (installConfig.hasCopilot) {
14092
- activePreset = "copilot";
14093
- } else if (installConfig.hasZaiPlan) {
14094
- activePreset = "zai-plan";
14095
- } else if (installConfig.hasChutes) {
14096
- activePreset = "chutes";
14097
- }
14098
- config2.preset = activePreset;
13854
+ preset: "openai",
13855
+ presets: {}
13856
+ };
14099
13857
  const createAgentConfig = (agentName, modelInfo) => {
14100
13858
  const isOrchestrator = agentName === "orchestrator";
14101
13859
  const skills = isOrchestrator ? ["*"] : RECOMMENDED_SKILLS.filter((s) => s.allowedAgents.includes("*") || s.allowedAgents.includes(agentName)).map((s) => s.skillName);
@@ -14109,142 +13867,14 @@ function generateLiteConfig(installConfig) {
14109
13867
  mcps: DEFAULT_AGENT_MCPS[agentName] ?? []
14110
13868
  };
14111
13869
  };
14112
- if (installConfig.dynamicModelPlan) {
14113
- const dynamicPreset = Object.fromEntries(Object.entries(installConfig.dynamicModelPlan.agents).map(([agentName, assignment]) => [
13870
+ const buildPreset = (mappingName) => {
13871
+ const mapping = MODEL_MAPPINGS[mappingName];
13872
+ return Object.fromEntries(Object.entries(mapping).map(([agentName, modelInfo]) => [
14114
13873
  agentName,
14115
- createAgentConfig(agentName, assignment)
13874
+ createAgentConfig(agentName, modelInfo)
14116
13875
  ]));
14117
- config2.preset = "dynamic";
14118
- config2.presets.dynamic = dynamicPreset;
14119
- config2.fallback = {
14120
- enabled: true,
14121
- timeoutMs: 15000,
14122
- chains: installConfig.dynamicModelPlan.chains
14123
- };
14124
- if (installConfig.hasTmux) {
14125
- config2.tmux = {
14126
- enabled: true,
14127
- layout: "main-vertical",
14128
- main_pane_size: 60
14129
- };
14130
- }
14131
- return config2;
14132
- }
14133
- const applyOpenCodeFreeAssignments = (presetAgents, hasExternalProviders) => {
14134
- if (!installConfig.useOpenCodeFreeModels)
14135
- return;
14136
- const primaryModel = installConfig.selectedOpenCodePrimaryModel;
14137
- const secondaryModel = installConfig.selectedOpenCodeSecondaryModel ?? primaryModel;
14138
- if (!primaryModel || !secondaryModel)
14139
- return;
14140
- const setAgent = (agentName, model) => {
14141
- presetAgents[agentName] = createAgentConfig(agentName, { model });
14142
- };
14143
- if (!hasExternalProviders) {
14144
- setAgent("orchestrator", primaryModel);
14145
- setAgent("oracle", primaryModel);
14146
- setAgent("designer", primaryModel);
14147
- }
14148
- setAgent("librarian", secondaryModel);
14149
- setAgent("explorer", secondaryModel);
14150
- setAgent("fixer", secondaryModel);
14151
- };
14152
- const applyChutesAssignments = (presetAgents) => {
14153
- if (!installConfig.hasChutes)
14154
- return;
14155
- const hasExternalProviders = installConfig.hasKimi || installConfig.hasOpenAI || installConfig.hasAnthropic || installConfig.hasCopilot || installConfig.hasZaiPlan || installConfig.hasAntigravity;
14156
- if (hasExternalProviders && activePreset !== "chutes")
14157
- return;
14158
- const primaryModel = installConfig.selectedChutesPrimaryModel;
14159
- const secondaryModel = installConfig.selectedChutesSecondaryModel ?? primaryModel;
14160
- if (!primaryModel || !secondaryModel)
14161
- return;
14162
- const setAgent = (agentName, model) => {
14163
- presetAgents[agentName] = createAgentConfig(agentName, { model });
14164
- };
14165
- setAgent("orchestrator", primaryModel);
14166
- setAgent("oracle", primaryModel);
14167
- setAgent("designer", primaryModel);
14168
- setAgent("librarian", secondaryModel);
14169
- setAgent("explorer", secondaryModel);
14170
- setAgent("fixer", secondaryModel);
14171
- };
14172
- const dedupeModels = (models) => {
14173
- const seen = new Set;
14174
- const result = [];
14175
- for (const model of models) {
14176
- if (!model || seen.has(model))
14177
- continue;
14178
- seen.add(model);
14179
- result.push(model);
14180
- }
14181
- return result;
14182
- };
14183
- const getOpenCodeFallbackForAgent = (agentName) => {
14184
- if (!installConfig.useOpenCodeFreeModels)
14185
- return;
14186
- const isSupport = agentName === "explorer" || agentName === "librarian" || agentName === "fixer";
14187
- if (isSupport) {
14188
- return installConfig.selectedOpenCodeSecondaryModel ?? installConfig.selectedOpenCodePrimaryModel;
14189
- }
14190
- return installConfig.selectedOpenCodePrimaryModel;
14191
13876
  };
14192
- const getChutesFallbackForAgent = (agentName) => {
14193
- if (!installConfig.hasChutes)
14194
- return;
14195
- const isSupport = agentName === "explorer" || agentName === "librarian" || agentName === "fixer";
14196
- if (isSupport) {
14197
- return installConfig.selectedChutesSecondaryModel ?? installConfig.selectedChutesPrimaryModel ?? MODEL_MAPPINGS.chutes[agentName].model;
14198
- }
14199
- return installConfig.selectedChutesPrimaryModel ?? MODEL_MAPPINGS.chutes[agentName].model;
14200
- };
14201
- const attachFallbackConfig = (presetAgents) => {
14202
- const chains = {};
14203
- for (const agentName of AGENT_NAMES) {
14204
- const currentModel = presetAgents[agentName]?.model;
14205
- const chain = dedupeModels([
14206
- currentModel,
14207
- installConfig.hasOpenAI ? MODEL_MAPPINGS.openai[agentName].model : undefined,
14208
- installConfig.hasAnthropic ? MODEL_MAPPINGS.anthropic[agentName].model : undefined,
14209
- installConfig.hasCopilot ? MODEL_MAPPINGS.copilot[agentName].model : undefined,
14210
- installConfig.hasZaiPlan ? MODEL_MAPPINGS["zai-plan"][agentName].model : undefined,
14211
- installConfig.hasKimi ? MODEL_MAPPINGS.kimi[agentName].model : undefined,
14212
- installConfig.hasAntigravity ? MODEL_MAPPINGS.antigravity[agentName].model : undefined,
14213
- getChutesFallbackForAgent(agentName),
14214
- getOpenCodeFallbackForAgent(agentName),
14215
- MODEL_MAPPINGS["zen-free"][agentName].model
14216
- ]);
14217
- if (chain.length > 0) {
14218
- chains[agentName] = chain;
14219
- }
14220
- }
14221
- config2.fallback = {
14222
- enabled: true,
14223
- timeoutMs: 15000,
14224
- chains
14225
- };
14226
- };
14227
- const buildPreset = (mappingName) => {
14228
- const mapping = MODEL_MAPPINGS[mappingName];
14229
- return Object.fromEntries(Object.entries(mapping).map(([agentName, modelInfo]) => {
14230
- let activeModelInfo = { ...modelInfo };
14231
- if (activePreset === "kimi" && installConfig.hasOpenAI && agentName === "oracle") {
14232
- activeModelInfo = { ...MODEL_MAPPINGS.openai.oracle };
14233
- }
14234
- return [agentName, createAgentConfig(agentName, activeModelInfo)];
14235
- }));
14236
- };
14237
- if (activePreset === "antigravity-mixed-both" || activePreset === "antigravity-mixed-kimi" || activePreset === "antigravity-mixed-openai") {
14238
- config2.presets[activePreset] = generateAntigravityMixedPreset(installConfig);
14239
- applyOpenCodeFreeAssignments(config2.presets[activePreset], installConfig.hasKimi || installConfig.hasOpenAI || installConfig.hasAnthropic || installConfig.hasCopilot || installConfig.hasZaiPlan || installConfig.hasAntigravity || installConfig.hasChutes === true);
14240
- applyChutesAssignments(config2.presets[activePreset]);
14241
- attachFallbackConfig(config2.presets[activePreset]);
14242
- } else {
14243
- config2.presets[activePreset] = buildPreset(activePreset);
14244
- applyOpenCodeFreeAssignments(config2.presets[activePreset], installConfig.hasKimi || installConfig.hasOpenAI || installConfig.hasAnthropic || installConfig.hasCopilot || installConfig.hasZaiPlan || installConfig.hasAntigravity || installConfig.hasChutes === true);
14245
- applyChutesAssignments(config2.presets[activePreset]);
14246
- attachFallbackConfig(config2.presets[activePreset]);
14247
- }
13877
+ config2.presets.openai = buildPreset("openai");
14248
13878
  if (installConfig.hasTmux) {
14249
13879
  config2.tmux = {
14250
13880
  enabled: true,
@@ -14386,145 +14016,6 @@ function disableDefaultAgents() {
14386
14016
  };
14387
14017
  }
14388
14018
  }
14389
- function addAntigravityPlugin() {
14390
- const configPath = getExistingConfigPath();
14391
- try {
14392
- const { config: parsedConfig, error: error48 } = parseConfig(configPath);
14393
- if (error48) {
14394
- return {
14395
- success: false,
14396
- configPath,
14397
- error: `Failed to parse config: ${error48}`
14398
- };
14399
- }
14400
- const config2 = parsedConfig ?? {};
14401
- const plugins = config2.plugin ?? [];
14402
- const pluginName = "opencode-antigravity-auth@latest";
14403
- if (!plugins.includes(pluginName)) {
14404
- plugins.push(pluginName);
14405
- }
14406
- config2.plugin = plugins;
14407
- writeConfig(configPath, config2);
14408
- return { success: true, configPath };
14409
- } catch (err) {
14410
- return {
14411
- success: false,
14412
- configPath,
14413
- error: `Failed to add antigravity plugin: ${err}`
14414
- };
14415
- }
14416
- }
14417
- function addGoogleProvider() {
14418
- const configPath = getExistingConfigPath();
14419
- try {
14420
- const { config: parsedConfig, error: error48 } = parseConfig(configPath);
14421
- if (error48) {
14422
- return {
14423
- success: false,
14424
- configPath,
14425
- error: `Failed to parse config: ${error48}`
14426
- };
14427
- }
14428
- const config2 = parsedConfig ?? {};
14429
- const providers = config2.provider ?? {};
14430
- providers.google = {
14431
- models: {
14432
- "antigravity-gemini-3.1-pro": {
14433
- name: "Gemini 3.1 Pro (Antigravity)",
14434
- limit: { context: 1048576, output: 65535 },
14435
- modalities: { input: ["text", "image", "pdf"], output: ["text"] },
14436
- variants: {
14437
- low: { thinkingLevel: "low" },
14438
- high: { thinkingLevel: "high" }
14439
- }
14440
- },
14441
- "antigravity-gemini-3-flash": {
14442
- name: "Gemini 3 Flash (Antigravity)",
14443
- limit: { context: 1048576, output: 65536 },
14444
- modalities: { input: ["text", "image", "pdf"], output: ["text"] },
14445
- variants: {
14446
- minimal: { thinkingLevel: "minimal" },
14447
- low: { thinkingLevel: "low" },
14448
- medium: { thinkingLevel: "medium" },
14449
- high: { thinkingLevel: "high" }
14450
- }
14451
- },
14452
- "antigravity-claude-sonnet-4-5": {
14453
- name: "Claude Sonnet 4.5 (Antigravity)",
14454
- limit: { context: 200000, output: 64000 },
14455
- modalities: { input: ["text", "image", "pdf"], output: ["text"] }
14456
- },
14457
- "antigravity-claude-sonnet-4-5-thinking": {
14458
- name: "Claude Sonnet 4.5 Thinking (Antigravity)",
14459
- limit: { context: 200000, output: 64000 },
14460
- modalities: { input: ["text", "image", "pdf"], output: ["text"] },
14461
- variants: {
14462
- low: { thinkingConfig: { thinkingBudget: 8192 } },
14463
- max: { thinkingConfig: { thinkingBudget: 32768 } }
14464
- }
14465
- },
14466
- "antigravity-claude-opus-4-5-thinking": {
14467
- name: "Claude Opus 4.5 Thinking (Antigravity)",
14468
- limit: { context: 200000, output: 64000 },
14469
- modalities: { input: ["text", "image", "pdf"], output: ["text"] },
14470
- variants: {
14471
- low: { thinkingConfig: { thinkingBudget: 8192 } },
14472
- max: { thinkingConfig: { thinkingBudget: 32768 } }
14473
- }
14474
- },
14475
- "gemini-2.5-flash": {
14476
- name: "Gemini 2.5 Flash (Gemini CLI)",
14477
- limit: { context: 1048576, output: 65536 },
14478
- modalities: { input: ["text", "image", "pdf"], output: ["text"] }
14479
- },
14480
- "gemini-2.5-pro": {
14481
- name: "Gemini 2.5 Pro (Gemini CLI)",
14482
- limit: { context: 1048576, output: 65536 },
14483
- modalities: { input: ["text", "image", "pdf"], output: ["text"] }
14484
- },
14485
- "gemini-3-flash-preview": {
14486
- name: "Gemini 3 Flash Preview (Gemini CLI)",
14487
- limit: { context: 1048576, output: 65536 },
14488
- modalities: { input: ["text", "image", "pdf"], output: ["text"] }
14489
- },
14490
- "gemini-3.1-pro-preview": {
14491
- name: "Gemini 3.1 Pro Preview (Gemini CLI)",
14492
- limit: { context: 1048576, output: 65535 },
14493
- modalities: { input: ["text", "image", "pdf"], output: ["text"] }
14494
- }
14495
- }
14496
- };
14497
- config2.provider = providers;
14498
- writeConfig(configPath, config2);
14499
- return { success: true, configPath };
14500
- } catch (err) {
14501
- return {
14502
- success: false,
14503
- configPath,
14504
- error: `Failed to add google provider: ${err}`
14505
- };
14506
- }
14507
- }
14508
- function addChutesProvider() {
14509
- const configPath = getExistingConfigPath();
14510
- try {
14511
- const { error: error48 } = parseConfig(configPath);
14512
- if (error48) {
14513
- return {
14514
- success: false,
14515
- configPath,
14516
- error: `Failed to parse config: ${error48}`
14517
- };
14518
- }
14519
- return { success: true, configPath };
14520
- } catch (err) {
14521
- return {
14522
- success: false,
14523
- configPath,
14524
- error: `Failed to validate chutes provider config: ${err}`
14525
- };
14526
- }
14527
- }
14528
14019
  function detectCurrentConfig() {
14529
14020
  const result = {
14530
14021
  isInstalled: false,
@@ -14579,1289 +14070,57 @@ function detectCurrentConfig() {
14579
14070
  }
14580
14071
  return result;
14581
14072
  }
14582
- // src/cli/model-key-normalization.ts
14583
- function cleanupAlias(input, preserveSlash) {
14584
- let value = input.toLowerCase().trim();
14585
- value = value.replace(/\bfp[a-z0-9.-]*\b/g, " ");
14586
- value = value.replace(/\btee\b/g, " ");
14587
- if (preserveSlash) {
14588
- value = value.replace(/[_\s]+/g, "-");
14589
- value = value.replace(/-+/g, "-");
14590
- value = value.replace(/\/+/g, "/");
14591
- value = value.replace(/\/-+/g, "/");
14592
- value = value.replace(/-+\//g, "/");
14593
- value = value.replace(/^\/+|\/+$/g, "");
14594
- value = value.replace(/^-+|-+$/g, "");
14595
- return value;
14596
- }
14597
- value = value.replace(/[/_\s]+/g, "-");
14598
- value = value.replace(/-+/g, "-");
14599
- value = value.replace(/^-+|-+$/g, "");
14600
- return value;
14601
- }
14602
- function addDerivedAliases(seed, aliases) {
14603
- const slashAlias = cleanupAlias(seed, true);
14604
- const flatAlias = cleanupAlias(seed, false);
14605
- if (slashAlias)
14606
- aliases.add(slashAlias);
14607
- if (flatAlias)
14608
- aliases.add(flatAlias);
14609
- if (slashAlias) {
14610
- aliases.add(slashAlias.replace(/-(free|flash)$/i, ""));
14611
- }
14612
- if (flatAlias) {
14613
- aliases.add(flatAlias.replace(/-(free|flash)$/i, ""));
14614
- }
14615
- if (slashAlias.includes("/")) {
14616
- aliases.add(cleanupAlias(slashAlias.replace(/\//g, " "), false));
14617
- aliases.add(cleanupAlias(slashAlias.replace(/\//g, "-"), false));
14618
- const lastPart = slashAlias.split("/").at(-1);
14619
- if (lastPart) {
14620
- addDerivedAliases(lastPart, aliases);
14621
- }
14622
- }
14623
- }
14624
- function buildModelKeyAliases(input) {
14625
- const normalized = input.trim().toLowerCase();
14626
- if (!normalized)
14627
- return [];
14628
- const aliases = new Set;
14629
- const slashIndex = normalized.indexOf("/");
14630
- const afterProvider = slashIndex >= 0 ? normalized.slice(slashIndex + 1) : normalized;
14631
- addDerivedAliases(normalized, aliases);
14632
- addDerivedAliases(afterProvider, aliases);
14633
- return [...aliases].filter((alias) => alias.length > 0);
14634
- }
14635
-
14636
- // src/cli/precedence-resolver.ts
14637
- function dedupe(models) {
14638
- const seen = new Set;
14639
- const result = [];
14640
- for (const model of models) {
14641
- if (!model || seen.has(model))
14642
- continue;
14643
- seen.add(model);
14644
- result.push(model);
14645
- }
14646
- return result;
14647
- }
14648
- function buildLayerOrder(input) {
14073
+ // src/cli/system.ts
14074
+ import { statSync as statSync3 } from "fs";
14075
+ var cachedOpenCodePath = null;
14076
+ function getOpenCodePaths() {
14077
+ const home = process.env.HOME || process.env.USERPROFILE || "";
14649
14078
  return [
14650
- {
14651
- layer: "opencode-direct-override",
14652
- models: input.openCodeDirectOverride ? [input.openCodeDirectOverride] : []
14653
- },
14654
- {
14655
- layer: "manual-user-plan",
14656
- models: input.manualUserPlan ?? []
14657
- },
14658
- {
14659
- layer: "pinned-model",
14660
- models: input.pinnedModel ? [input.pinnedModel] : []
14661
- },
14662
- {
14663
- layer: "dynamic-recommendation",
14664
- models: input.dynamicRecommendation ?? []
14665
- },
14666
- {
14667
- layer: "provider-fallback-policy",
14668
- models: input.providerFallbackPolicy ?? []
14669
- },
14670
- {
14671
- layer: "system-default",
14672
- models: input.systemDefault
14673
- }
14079
+ "opencode",
14080
+ `${home}/.local/bin/opencode`,
14081
+ `${home}/.opencode/bin/opencode`,
14082
+ `${home}/bin/opencode`,
14083
+ "/usr/local/bin/opencode",
14084
+ "/opt/opencode/bin/opencode",
14085
+ "/usr/bin/opencode",
14086
+ "/bin/opencode",
14087
+ "/Applications/OpenCode.app/Contents/MacOS/opencode",
14088
+ `${home}/Applications/OpenCode.app/Contents/MacOS/opencode`,
14089
+ "/opt/homebrew/bin/opencode",
14090
+ "/home/linuxbrew/.linuxbrew/bin/opencode",
14091
+ `${home}/homebrew/bin/opencode`,
14092
+ `${home}/Library/Application Support/opencode/bin/opencode`,
14093
+ "/snap/bin/opencode",
14094
+ "/var/snap/opencode/current/bin/opencode",
14095
+ "/var/lib/flatpak/exports/bin/ai.opencode.OpenCode",
14096
+ `${home}/.local/share/flatpak/exports/bin/ai.opencode.OpenCode`,
14097
+ "/nix/store/opencode/bin/opencode",
14098
+ `${home}/.nix-profile/bin/opencode`,
14099
+ "/run/current-system/sw/bin/opencode",
14100
+ `${home}/.cargo/bin/opencode`,
14101
+ `${home}/.npm-global/bin/opencode`,
14102
+ "/usr/local/lib/node_modules/opencode/bin/opencode",
14103
+ `${home}/.yarn/bin/opencode`,
14104
+ `${home}/.pnpm-global/bin/opencode`
14674
14105
  ];
14675
14106
  }
14676
- function resolveAgentWithPrecedence(input) {
14677
- const ordered = buildLayerOrder(input);
14678
- const firstWinningIndex = ordered.findIndex((layer) => layer.models.length > 0);
14679
- const winnerIndex = firstWinningIndex >= 0 ? firstWinningIndex : ordered.length - 1;
14680
- const winnerLayer = ordered[winnerIndex];
14681
- const chain = dedupe(ordered.slice(winnerIndex).flatMap((layer) => layer.models).concat(input.systemDefault));
14682
- const model = chain[0] ?? input.systemDefault[0] ?? "opencode/big-pickle";
14683
- return {
14684
- model,
14685
- chain,
14686
- provenance: {
14687
- winnerLayer: winnerLayer?.layer ?? "system-default",
14688
- winnerModel: model
14689
- }
14690
- };
14691
- }
14692
-
14693
- // src/cli/scoring-v2/features.ts
14694
- function modelLookupKeys(model) {
14695
- return buildModelKeyAliases(model.model);
14696
- }
14697
- function findSignal(model, externalSignals) {
14698
- if (!externalSignals)
14699
- return;
14700
- return modelLookupKeys(model).map((key) => externalSignals[key]).find((item) => item !== undefined);
14701
- }
14702
- function statusValue(status) {
14703
- if (status === "active")
14704
- return 1;
14705
- if (status === "beta")
14706
- return 0.4;
14707
- if (status === "alpha")
14708
- return -0.25;
14709
- return -1;
14710
- }
14711
- function capability(value) {
14712
- return value ? 1 : 0;
14713
- }
14714
- function blendedPrice(signal) {
14715
- if (!signal)
14716
- return 0;
14717
- if (signal.inputPricePer1M !== undefined && signal.outputPricePer1M !== undefined) {
14718
- return signal.inputPricePer1M * 0.75 + signal.outputPricePer1M * 0.25;
14719
- }
14720
- return signal.inputPricePer1M ?? signal.outputPricePer1M ?? 0;
14721
- }
14722
- function kimiVersionBonus(agent, model) {
14723
- const lowered = `${model.model} ${model.name}`.toLowerCase();
14724
- const isChutes = model.providerID === "chutes";
14725
- const isQwen3 = isChutes && /qwen3/.test(lowered);
14726
- const isKimiK25 = /kimi-k2\.5|k2\.5/.test(lowered);
14727
- const isMinimaxM21 = isChutes && /minimax[-_ ]?m2\.1/.test(lowered);
14728
- const qwenPenalty = {
14729
- orchestrator: -6,
14730
- oracle: -6,
14731
- designer: -8,
14732
- explorer: -6,
14733
- librarian: -12,
14734
- fixer: -12
14735
- };
14736
- const kimiBonus = {
14737
- orchestrator: 1,
14738
- oracle: 1,
14739
- designer: 3,
14740
- explorer: 2,
14741
- librarian: 2,
14742
- fixer: 3
14743
- };
14744
- const minimaxBonus = {
14745
- orchestrator: 1,
14746
- oracle: 1,
14747
- designer: 2,
14748
- explorer: 4,
14749
- librarian: 4,
14750
- fixer: 4
14751
- };
14752
- if (isQwen3)
14753
- return qwenPenalty[agent];
14754
- if (isKimiK25)
14755
- return kimiBonus[agent];
14756
- if (isMinimaxM21)
14757
- return minimaxBonus[agent];
14758
- return 0;
14759
- }
14760
- function extractFeatureVector(model, agent, externalSignals) {
14761
- const signal = findSignal(model, externalSignals);
14762
- const latency = signal?.latencySeconds ?? 0;
14763
- const normalizedContext = Math.min(model.contextLimit, 1e6) / 1e5;
14764
- const normalizedOutput = Math.min(model.outputLimit, 300000) / 30000;
14765
- const designerOutputScore = model.outputLimit < 64000 ? -1 : 0;
14766
- const versionBonus = kimiVersionBonus(agent, model);
14767
- const quality = (signal?.qualityScore ?? 0) / 100;
14768
- const coding = (signal?.codingScore ?? 0) / 100;
14769
- const pricePenalty = Math.min(blendedPrice(signal), 50) / 10;
14770
- const explorerLatencyMultiplier = agent === "explorer" ? 1.4 : 1;
14771
- return {
14772
- status: statusValue(model.status),
14773
- context: normalizedContext,
14774
- output: agent === "designer" ? designerOutputScore : normalizedOutput,
14775
- versionBonus,
14776
- reasoning: capability(model.reasoning),
14777
- toolcall: capability(model.toolcall),
14778
- attachment: capability(model.attachment),
14779
- quality,
14780
- coding,
14781
- latencyPenalty: Math.min(latency, 20) * explorerLatencyMultiplier,
14782
- pricePenalty
14783
- };
14784
- }
14785
-
14786
- // src/cli/scoring-v2/weights.ts
14787
- var BASE_WEIGHTS = {
14788
- status: 22,
14789
- context: 6,
14790
- output: 6,
14791
- versionBonus: 8,
14792
- reasoning: 10,
14793
- toolcall: 16,
14794
- attachment: 2,
14795
- quality: 14,
14796
- coding: 18,
14797
- latencyPenalty: -3,
14798
- pricePenalty: -2
14799
- };
14800
- var AGENT_WEIGHT_OVERRIDES = {
14801
- orchestrator: {
14802
- reasoning: 22,
14803
- toolcall: 22,
14804
- quality: 16,
14805
- coding: 16,
14806
- latencyPenalty: -2
14807
- },
14808
- oracle: {
14809
- reasoning: 26,
14810
- quality: 20,
14811
- coding: 18,
14812
- latencyPenalty: -2,
14813
- output: 7
14814
- },
14815
- designer: {
14816
- attachment: 12,
14817
- output: 10,
14818
- quality: 16,
14819
- coding: 10
14820
- },
14821
- explorer: {
14822
- latencyPenalty: -8,
14823
- toolcall: 24,
14824
- reasoning: 2,
14825
- context: 4,
14826
- output: 4
14827
- },
14828
- librarian: {
14829
- context: 14,
14830
- output: 10,
14831
- quality: 18,
14832
- coding: 14
14833
- },
14834
- fixer: {
14835
- coding: 28,
14836
- toolcall: 22,
14837
- reasoning: 12,
14838
- output: 10
14107
+ function resolveOpenCodePath() {
14108
+ if (cachedOpenCodePath) {
14109
+ return cachedOpenCodePath;
14839
14110
  }
14840
- };
14841
- function getFeatureWeights(agent) {
14842
- return {
14843
- ...BASE_WEIGHTS,
14844
- ...AGENT_WEIGHT_OVERRIDES[agent]
14845
- };
14846
- }
14847
-
14848
- // src/cli/scoring-v2/engine.ts
14849
- function weightedFeatures(features, weights) {
14850
- return {
14851
- status: features.status * weights.status,
14852
- context: features.context * weights.context,
14853
- output: features.output * weights.output,
14854
- versionBonus: features.versionBonus * weights.versionBonus,
14855
- reasoning: features.reasoning * weights.reasoning,
14856
- toolcall: features.toolcall * weights.toolcall,
14857
- attachment: features.attachment * weights.attachment,
14858
- quality: features.quality * weights.quality,
14859
- coding: features.coding * weights.coding,
14860
- latencyPenalty: features.latencyPenalty * weights.latencyPenalty,
14861
- pricePenalty: features.pricePenalty * weights.pricePenalty
14862
- };
14863
- }
14864
- function sumFeatures(features) {
14865
- return features.status + features.context + features.output + features.versionBonus + features.reasoning + features.toolcall + features.attachment + features.quality + features.coding + features.latencyPenalty + features.pricePenalty;
14866
- }
14867
- function withStableTieBreak(left, right) {
14868
- if (left.totalScore !== right.totalScore) {
14869
- return right.totalScore - left.totalScore;
14870
- }
14871
- const providerDelta = left.model.providerID.localeCompare(right.model.providerID);
14872
- if (providerDelta !== 0) {
14873
- return providerDelta;
14874
- }
14875
- return left.model.model.localeCompare(right.model.model);
14876
- }
14877
- function scoreCandidateV2(model, agent, externalSignals) {
14878
- const features = extractFeatureVector(model, agent, externalSignals);
14879
- const weights = getFeatureWeights(agent);
14880
- const weighted = weightedFeatures(features, weights);
14881
- return {
14882
- model,
14883
- totalScore: Math.round(sumFeatures(weighted) * 1000) / 1000,
14884
- scoreBreakdown: {
14885
- features,
14886
- weighted
14887
- }
14888
- };
14889
- }
14890
- function rankModelsV2(models, agent, externalSignals) {
14891
- return models.map((model) => scoreCandidateV2(model, agent, externalSignals)).sort(withStableTieBreak);
14892
- }
14893
- // src/cli/dynamic-model-selection.ts
14894
- var AGENTS = [
14895
- "orchestrator",
14896
- "oracle",
14897
- "designer",
14898
- "explorer",
14899
- "librarian",
14900
- "fixer"
14901
- ];
14902
- var FREE_BIASED_PROVIDERS = new Set(["opencode"]);
14903
- var PRIMARY_ASSIGNMENT_ORDER = [
14904
- "oracle",
14905
- "orchestrator",
14906
- "fixer",
14907
- "designer",
14908
- "librarian",
14909
- "explorer"
14910
- ];
14911
- var ROLE_VARIANT = {
14912
- orchestrator: undefined,
14913
- oracle: "high",
14914
- designer: "medium",
14915
- explorer: "low",
14916
- librarian: "low",
14917
- fixer: "low"
14918
- };
14919
- function getEnabledProviders(config2) {
14920
- const providers = [];
14921
- if (config2.hasOpenAI)
14922
- providers.push("openai");
14923
- if (config2.hasAnthropic)
14924
- providers.push("anthropic");
14925
- if (config2.hasCopilot)
14926
- providers.push("github-copilot");
14927
- if (config2.hasZaiPlan)
14928
- providers.push("zai-coding-plan");
14929
- if (config2.hasKimi)
14930
- providers.push("kimi-for-coding");
14931
- if (config2.hasAntigravity)
14932
- providers.push("google");
14933
- if (config2.hasChutes)
14934
- providers.push("chutes");
14935
- if (config2.useOpenCodeFreeModels)
14936
- providers.push("opencode");
14937
- return providers;
14938
- }
14939
- function tokenScore(name, re, points) {
14940
- return re.test(name) ? points : 0;
14941
- }
14942
- function statusScore(status) {
14943
- if (status === "active")
14944
- return 20;
14945
- if (status === "beta")
14946
- return 8;
14947
- if (status === "alpha")
14948
- return -5;
14949
- return -40;
14950
- }
14951
- function toVersionTuple(major, minor, patch) {
14952
- return [
14953
- Number.parseInt(major, 10) || 0,
14954
- Number.parseInt(minor ?? "0", 10) || 0,
14955
- Number.parseInt(patch ?? "0", 10) || 0
14956
- ];
14957
- }
14958
- function compareVersionTuple(a, b) {
14959
- if (a[0] !== b[0])
14960
- return a[0] - b[0];
14961
- if (a[1] !== b[1])
14962
- return a[1] - b[1];
14963
- return a[2] - b[2];
14964
- }
14965
- function extractVersionFamily(model) {
14966
- const text = `${model.model} ${model.name}`.toLowerCase();
14967
- const gpt = text.match(/\bgpt[-_ ]?(\d+)(?:[.-](\d+))?(?:[.-](\d+))?\b/);
14968
- if (gpt) {
14969
- return {
14970
- family: "gpt",
14971
- version: toVersionTuple(gpt[1] ?? "0", gpt[2], gpt[3]),
14972
- confidence: 1,
14973
- prereleasePenalty: /preview|experimental|exp|\brc\b/.test(text) ? -2 : 0
14974
- };
14975
- }
14976
- const gemini = text.match(/\bgemini[-_ ]?(\d+)(?:[.-](\d+))?(?:[.-](\d+))?\b/);
14977
- if (gemini) {
14978
- return {
14979
- family: "gemini",
14980
- version: toVersionTuple(gemini[1] ?? "0", gemini[2], gemini[3]),
14981
- confidence: 1,
14982
- prereleasePenalty: /preview|experimental|exp|\brc\b/.test(text) ? -2 : 0
14983
- };
14984
- }
14985
- const kimi = text.match(/\bkimi[-_ ]?k(\d+)(?:[.-]?(\d+))?(?:[.-](\d+))?\b/);
14986
- if (kimi) {
14987
- return {
14988
- family: "kimi-k",
14989
- version: toVersionTuple(kimi[1] ?? "0", kimi[2], kimi[3]),
14990
- confidence: 1,
14991
- prereleasePenalty: /preview|experimental|exp|\brc\b/.test(text) ? -2 : 0
14992
- };
14993
- }
14994
- const generic = text.match(/\b([a-z][a-z0-9-]{1,20})[-_ ](\d+)(?:[.-](\d+))?(?:[.-](\d+))?\b/);
14995
- if (generic) {
14996
- return {
14997
- family: generic[1] ?? "generic",
14998
- version: toVersionTuple(generic[2] ?? "0", generic[3], generic[4]),
14999
- confidence: 0.7,
15000
- prereleasePenalty: /preview|experimental|exp|\brc\b/.test(text) ? -2 : 0
15001
- };
15002
- }
15003
- return null;
15004
- }
15005
- function getVersionRecencyMap(models) {
15006
- const familyVersions = new Map;
15007
- const modelInfo = new Map;
15008
- for (const model of models) {
15009
- const info = extractVersionFamily(model);
15010
- if (!info)
15011
- continue;
15012
- modelInfo.set(model.model, info);
15013
- const current = familyVersions.get(info.family) ?? [];
15014
- current.push(info.version);
15015
- familyVersions.set(info.family, current);
15016
- }
15017
- const recencyMap = {};
15018
- for (const model of models) {
15019
- const info = modelInfo.get(model.model);
15020
- if (!info) {
15021
- recencyMap[model.model] = 0;
15022
- continue;
15023
- }
15024
- const versions2 = familyVersions.get(info.family) ?? [];
15025
- const unique = versions2.map((tuple2) => `${tuple2[0]}.${tuple2[1]}.${tuple2[2]}`).filter((value, index2, arr) => arr.indexOf(value) === index2).map((value) => {
15026
- const [major, minor, patch] = value.split(".").map((v) => Number.parseInt(v, 10) || 0);
15027
- return [major, minor, patch];
15028
- }).sort(compareVersionTuple);
15029
- if (unique.length === 0) {
15030
- recencyMap[model.model] = 0;
15031
- continue;
15032
- }
15033
- const index = unique.findIndex((tuple2) => compareVersionTuple(tuple2, info.version) === 0);
15034
- const percentile = unique.length === 1 ? 0.5 : index / (unique.length - 1);
15035
- const raw = -3 + percentile * (12 - -3);
15036
- const final = Math.max(-3, Math.min(12, raw * info.confidence + info.prereleasePenalty));
15037
- recencyMap[model.model] = final;
15038
- }
15039
- return recencyMap;
15040
- }
15041
- function baseScore(model, versionRecencyBoost = 0) {
15042
- const lowered = `${model.model} ${model.name}`.toLowerCase();
15043
- const context = Math.min(model.contextLimit, 1e6) / 50000;
15044
- const output = Math.min(model.outputLimit, 300000) / 30000;
15045
- const deep = tokenScore(lowered, /(opus|pro|thinking|reason|r1|gpt-5|k2\.5)/i, 12);
15046
- const fast = tokenScore(lowered, /(nano|flash|mini|lite|fast|turbo|haiku|small)/i, 4);
15047
- const code = tokenScore(lowered, /(codex|coder|code|dev|program)/i, 12);
15048
- return statusScore(model.status) + context + output + deep + fast + code + versionRecencyBoost + (model.toolcall ? 25 : 0);
15049
- }
15050
- function hasFlashToken(model) {
15051
- return /flash/i.test(`${model.model} ${model.name}`);
15052
- }
15053
- function isZai47Model(model) {
15054
- return model.providerID === "zai-coding-plan" && /glm-4\.7/i.test(`${model.model} ${model.name}`);
15055
- }
15056
- function isKimiK25Model(model) {
15057
- return /kimi-k2\.?5|k2\.?5/i.test(`${model.model} ${model.name}`);
15058
- }
15059
- function geminiPreferenceAdjustment(_agent, model) {
15060
- const lowered = `${model.model} ${model.name}`.toLowerCase();
15061
- const isGemini25Pro = /gemini-2\.5-pro/.test(lowered);
15062
- return isGemini25Pro ? -14 : 0;
15063
- }
15064
- function chutesPreferenceAdjustment(agent, model) {
15065
- if (model.providerID !== "chutes")
15066
- return 0;
15067
- const lowered = `${model.model} ${model.name}`.toLowerCase();
15068
- const isQwen3 = /qwen3/.test(lowered);
15069
- const isKimiK25 = /kimi-k2\.5|k2\.5/.test(lowered);
15070
- const isMinimaxM21 = /minimax[-_ ]?m2\.1/.test(lowered);
15071
- const qwenPenalty = {
15072
- oracle: -12,
15073
- orchestrator: -10,
15074
- fixer: -22,
15075
- designer: -14,
15076
- librarian: -18,
15077
- explorer: -10
15078
- };
15079
- const kimiBonus = {
15080
- oracle: 0,
15081
- orchestrator: 0,
15082
- fixer: 8,
15083
- designer: 6,
15084
- librarian: 5,
15085
- explorer: 4
15086
- };
15087
- const minimaxBonus = {
15088
- oracle: 0,
15089
- orchestrator: 0,
15090
- fixer: 10,
15091
- designer: 3,
15092
- librarian: 9,
15093
- explorer: 12
15094
- };
15095
- return (isQwen3 ? qwenPenalty[agent] : 0) + (isKimiK25 ? kimiBonus[agent] : 0) + (isMinimaxM21 ? minimaxBonus[agent] : 0);
15096
- }
15097
- function modelLookupKeys2(model) {
15098
- return buildModelKeyAliases(model.model);
15099
- }
15100
- function roleScore(agent, model, versionRecencyBoost = 0) {
15101
- const lowered = `${model.model} ${model.name}`.toLowerCase();
15102
- const reasoning = model.reasoning ? 1 : 0;
15103
- const toolcall = model.toolcall ? 1 : 0;
15104
- const attachment = model.attachment ? 1 : 0;
15105
- const context = Math.min(model.contextLimit, 1e6) / 60000;
15106
- const output = Math.min(model.outputLimit, 300000) / 40000;
15107
- const deep = tokenScore(lowered, /(opus|pro|thinking|reason|r1|gpt-5|k2\.5)/i, 1);
15108
- const fast = tokenScore(lowered, /(nano|flash|mini|lite|fast|turbo|haiku|small)/i, 1);
15109
- const code = tokenScore(lowered, /(codex|coder|code|dev|program)/i, 1);
15110
- if ((agent === "orchestrator" || agent === "explorer" || agent === "librarian" || agent === "fixer") && !model.toolcall) {
15111
- return -1e4;
15112
- }
15113
- if (model.status === "deprecated") {
15114
- return -5000;
15115
- }
15116
- const score = baseScore(model, versionRecencyBoost);
15117
- const flash = hasFlashToken(model);
15118
- const isZai47 = isZai47Model(model);
15119
- const zai47Flash = isZai47 && flash;
15120
- const zai47NonFlash = isZai47 && !flash;
15121
- const providerBias = model.providerID === "openai" ? 3 : model.providerID === "anthropic" ? 3 : model.providerID === "kimi-for-coding" ? 2 : model.providerID === "google" ? 2 : model.providerID === "github-copilot" ? 1 : model.providerID === "zai-coding-plan" ? 0 : model.providerID === "chutes" ? 2 : model.providerID === "opencode" ? -2 : 0;
15122
- const geminiAdjustment = geminiPreferenceAdjustment(agent, model);
15123
- const chutesAdjustment = chutesPreferenceAdjustment(agent, model);
15124
- if (agent === "orchestrator") {
15125
- const flashAdjustment2 = flash ? -22 : 0;
15126
- const zaiAdjustment2 = zai47NonFlash ? 16 : zai47Flash ? -18 : 0;
15127
- const nonReasoningFlashPenalty2 = flash && !model.reasoning ? -16 : 0;
15128
- return score + reasoning * 40 + toolcall * 25 + deep * 10 + code * 8 + context + flashAdjustment2 + zaiAdjustment2 + nonReasoningFlashPenalty2 + geminiAdjustment + chutesAdjustment + providerBias;
15129
- }
15130
- if (agent === "oracle") {
15131
- const flashAdjustment2 = flash ? -34 : 0;
15132
- const zaiAdjustment2 = zai47NonFlash ? 16 : zai47Flash ? -18 : 0;
15133
- const nonReasoningFlashPenalty2 = flash && !model.reasoning ? -16 : 0;
15134
- return score + reasoning * 55 + deep * 18 + context * 1.2 + toolcall * 10 + flashAdjustment2 + zaiAdjustment2 + nonReasoningFlashPenalty2 + geminiAdjustment + chutesAdjustment + providerBias;
15135
- }
15136
- if (agent === "designer") {
15137
- const flashAdjustment2 = flash ? -8 : 0;
15138
- const zaiAdjustment2 = zai47NonFlash ? 10 : zai47Flash ? -8 : 0;
15139
- return score + attachment * 25 + reasoning * 18 + toolcall * 15 + context * 0.8 + output + flashAdjustment2 + zaiAdjustment2 + geminiAdjustment + chutesAdjustment + providerBias;
15140
- }
15141
- if (agent === "explorer") {
15142
- const flashAdjustment2 = flash ? 26 : -10;
15143
- const zaiAdjustment2 = zai47NonFlash ? 2 : zai47Flash ? 6 : 0;
15144
- const deepPenalty = deep * -18;
15145
- return score + fast * 68 + toolcall * 28 + reasoning * 2 + context * 0.2 + flashAdjustment2 + zaiAdjustment2 + deepPenalty + geminiAdjustment + chutesAdjustment + providerBias;
15146
- }
15147
- if (agent === "librarian") {
15148
- const flashAdjustment2 = flash ? -12 : 0;
15149
- const zaiAdjustment2 = zai47NonFlash ? 16 : zai47Flash ? -18 : 0;
15150
- return score + context * 30 + toolcall * 22 + reasoning * 15 + output * 10 + flashAdjustment2 + zaiAdjustment2 + geminiAdjustment + chutesAdjustment + providerBias;
15151
- }
15152
- const flashAdjustment = flash ? -18 : 0;
15153
- const zaiAdjustment = zai47NonFlash ? 16 : zai47Flash ? -18 : 0;
15154
- const nonReasoningFlashPenalty = flash && !model.reasoning ? -16 : 0;
15155
- return score + code * 28 + toolcall * 24 + fast * 18 + reasoning * 14 + output * 8 + flashAdjustment + zaiAdjustment + nonReasoningFlashPenalty + geminiAdjustment + chutesAdjustment + providerBias;
15156
- }
15157
- function getExternalSignalBoost(agent, model, externalSignals) {
15158
- if (!externalSignals)
15159
- return 0;
15160
- const signal = modelLookupKeys2(model).map((key) => externalSignals[key]).find((item) => item !== undefined);
15161
- if (!signal)
15162
- return 0;
15163
- const qualityScore = signal.qualityScore ?? 0;
15164
- const codingScore = signal.codingScore ?? 0;
15165
- const latencySeconds = signal.latencySeconds;
15166
- const blendedPrice2 = signal.inputPricePer1M !== undefined && signal.outputPricePer1M !== undefined ? signal.inputPricePer1M * 0.75 + signal.outputPricePer1M * 0.25 : signal.inputPricePer1M ?? signal.outputPricePer1M ?? 0;
15167
- if (agent === "explorer") {
15168
- const qualityBoost2 = qualityScore * 0.05;
15169
- const codingBoost2 = codingScore * 0.08;
15170
- const latencyPenalty2 = typeof latencySeconds === "number" && Number.isFinite(latencySeconds) ? Math.min(latencySeconds, 12) * 3.2 + (latencySeconds > 7 ? 16 : latencySeconds > 4 ? 10 : 0) : 0;
15171
- const pricePenalty2 = Math.min(blendedPrice2, 30) * 0.03;
15172
- const qualityFloorPenalty = qualityScore > 0 && qualityScore < 35 ? (35 - qualityScore) * 0.8 : 0;
15173
- const boost2 = qualityBoost2 + codingBoost2 - latencyPenalty2 - pricePenalty2 - qualityFloorPenalty;
15174
- return Math.max(-90, Math.min(25, boost2));
15175
- }
15176
- const qualityBoost = qualityScore * 0.16;
15177
- const codingBoost = codingScore * 0.24;
15178
- const latencyPenalty = typeof latencySeconds === "number" && Number.isFinite(latencySeconds) ? Math.min(latencySeconds, 25) * 0.22 : 0;
15179
- const pricePenalty = Math.min(blendedPrice2, 30) * 0.08;
15180
- const boost = qualityBoost + codingBoost - latencyPenalty - pricePenalty;
15181
- return Math.max(-30, Math.min(45, boost));
15182
- }
15183
- function rankModels2(models, agent, externalSignals) {
15184
- const versionRecencyMap = getVersionRecencyMap(models);
15185
- return [...models].sort((a, b) => {
15186
- const scoreA = roleScore(agent, a, versionRecencyMap[a.model] ?? 0) + getExternalSignalBoost(agent, a, externalSignals);
15187
- const scoreB = roleScore(agent, b, versionRecencyMap[b.model] ?? 0) + getExternalSignalBoost(agent, b, externalSignals);
15188
- const scoreDelta = scoreB - scoreA;
15189
- if (scoreDelta !== 0)
15190
- return scoreDelta;
15191
- const providerTieBreak = a.providerID.localeCompare(b.providerID);
15192
- if (providerTieBreak !== 0)
15193
- return providerTieBreak;
15194
- return a.model.localeCompare(b.model);
15195
- });
15196
- }
15197
- function combinedScore(agent, model, externalSignals, versionRecencyMap) {
15198
- return roleScore(agent, model, versionRecencyMap?.[model.model] ?? 0) + getExternalSignalBoost(agent, model, externalSignals);
15199
- }
15200
- function effectiveEngine(engineVersion) {
15201
- return engineVersion === "v2" ? "v2" : "v1";
15202
- }
15203
- function scoreForEngine(engineVersion, agent, model, externalSignals, versionRecencyMap) {
15204
- if (effectiveEngine(engineVersion) === "v2") {
15205
- return scoreCandidateV2(model, agent, externalSignals).totalScore;
15206
- }
15207
- return combinedScore(agent, model, externalSignals, versionRecencyMap);
15208
- }
15209
- function selectTopModelsPerProvider(models, engineVersion, externalSignals, versionRecencyMap) {
15210
- const byProvider = new Map;
15211
- for (const model of models) {
15212
- const current = byProvider.get(model.providerID) ?? [];
15213
- current.push(model);
15214
- byProvider.set(model.providerID, current);
15215
- }
15216
- const selected = [];
15217
- for (const providerModels of byProvider.values()) {
15218
- if (providerModels.length <= 2) {
15219
- selected.push(...providerModels);
15220
- continue;
15221
- }
15222
- const ranked = [...providerModels].map((model) => {
15223
- const total = AGENTS.reduce((sum, agent) => {
15224
- return sum + scoreForEngine(engineVersion, agent, model, externalSignals, versionRecencyMap);
15225
- }, 0);
15226
- return {
15227
- model,
15228
- score: total / AGENTS.length
15229
- };
15230
- }).sort((a, b) => {
15231
- if (a.score !== b.score)
15232
- return b.score - a.score;
15233
- return a.model.model.localeCompare(b.model.model);
15234
- }).slice(0, 2).map((entry) => entry.model);
15235
- selected.push(...ranked);
15236
- }
15237
- return selected;
15238
- }
15239
- function countProviderUsage(agents) {
15240
- const counts = new Map;
15241
- for (const assignment of Object.values(agents)) {
15242
- const provider = assignment.model.split("/")[0];
15243
- if (!provider)
15244
- continue;
15245
- counts.set(provider, (counts.get(provider) ?? 0) + 1);
15246
- }
15247
- return counts;
15248
- }
15249
- function rebalanceForSubscriptionMode(agents, chains, provenance, paidProviders, getRankedModels, getPinnedModelForProvider, targetByProvider, externalSignals, versionRecencyMap, engineVersion) {
15250
- if (paidProviders.length <= 1)
15251
- return;
15252
- const MAX_ALLOWED_SCORE_LOSS = 20;
15253
- while (true) {
15254
- const providerUsage = countProviderUsage(agents);
15255
- const underProviders = paidProviders.filter((providerID) => (providerUsage.get(providerID) ?? 0) < (targetByProvider[providerID] ?? 0));
15256
- const overProviders = paidProviders.filter((providerID) => (providerUsage.get(providerID) ?? 0) > (targetByProvider[providerID] ?? 0));
15257
- if (underProviders.length === 0 || overProviders.length === 0)
15258
- break;
15259
- let bestSwap;
15260
- for (const agent of PRIMARY_ASSIGNMENT_ORDER) {
15261
- const currentModelID = agents[agent]?.model;
15262
- if (!currentModelID)
15263
- continue;
15264
- const currentProvider = currentModelID.split("/")[0];
15265
- if (!currentProvider || !overProviders.includes(currentProvider))
15266
- continue;
15267
- const ranked = getRankedModels(agent);
15268
- const currentModel = ranked.find((model) => model.model === currentModelID) ?? ranked.find((model) => model.providerID === currentProvider);
15269
- if (!currentModel)
15270
- continue;
15271
- const currentScore = scoreForEngine(engineVersion, agent, currentModel, externalSignals, versionRecencyMap);
15272
- for (const underProvider of underProviders) {
15273
- const pinned = getPinnedModelForProvider(agent, underProvider);
15274
- const candidate = ranked.find((model) => model.model === pinned) ?? ranked.find((model) => model.providerID === underProvider);
15275
- if (!candidate)
15276
- continue;
15277
- const candidateScore = scoreForEngine(engineVersion, agent, candidate, externalSignals, versionRecencyMap);
15278
- const loss = currentScore - candidateScore;
15279
- if (loss > MAX_ALLOWED_SCORE_LOSS)
15280
- continue;
15281
- if (!bestSwap || loss < bestSwap.loss) {
15282
- bestSwap = { agent, candidate, loss };
15283
- }
15284
- }
15285
- }
15286
- if (!bestSwap)
15287
- break;
15288
- agents[bestSwap.agent].model = bestSwap.candidate.model;
15289
- chains[bestSwap.agent] = dedupe2([
15290
- bestSwap.candidate.model,
15291
- ...chains[bestSwap.agent] ?? []
15292
- ]).slice(0, 10);
15293
- provenance[bestSwap.agent] = {
15294
- winnerLayer: "provider-fallback-policy",
15295
- winnerModel: bestSwap.candidate.model
15296
- };
15297
- }
15298
- }
15299
- function chooseProviderRepresentative(providerModels, agent, externalSignals, versionRecencyMap) {
15300
- if (providerModels.length === 0)
15301
- return null;
15302
- const flashBest = providerModels.find((model) => hasFlashToken(model));
15303
- const nonFlashBest = providerModels.find((model) => !hasFlashToken(model));
15304
- if (!nonFlashBest)
15305
- return providerModels[0] ?? null;
15306
- if (!flashBest)
15307
- return nonFlashBest;
15308
- const flashScore = combinedScore(agent, flashBest, externalSignals, versionRecencyMap);
15309
- const nonFlashScore = combinedScore(agent, nonFlashBest, externalSignals, versionRecencyMap);
15310
- const threshold = agent === "explorer" ? -6 : 12;
15311
- return flashScore >= nonFlashScore + threshold ? flashBest : nonFlashBest;
15312
- }
15313
- function getQualityWindow(agent) {
15314
- if (agent === "oracle" || agent === "orchestrator")
15315
- return 12;
15316
- if (agent === "fixer")
15317
- return 15;
15318
- if (agent === "designer")
15319
- return 16;
15320
- if (agent === "librarian")
15321
- return 18;
15322
- return 22;
15323
- }
15324
- function getProviderBundle(providerModels, agent, externalSignals, versionRecencyMap) {
15325
- if (providerModels.length === 0)
15326
- return [];
15327
- const representative = chooseProviderRepresentative(providerModels, agent, externalSignals, versionRecencyMap);
15328
- if (!representative)
15329
- return [];
15330
- const second = providerModels.find((m) => m.model !== representative.model);
15331
- if (!second)
15332
- return [representative.model];
15333
- const score1 = combinedScore(agent, representative, externalSignals, versionRecencyMap);
15334
- const score2 = combinedScore(agent, second, externalSignals, versionRecencyMap);
15335
- const gap = Math.abs(score1 - score2);
15336
- const includeSecond = representative.providerID === "chutes" || gap <= (agent === "oracle" || agent === "orchestrator" ? 8 : agent === "designer" || agent === "librarian" ? 12 : agent === "fixer" ? 15 : 18);
15337
- return includeSecond ? [representative.model, second.model] : [representative.model];
15338
- }
15339
- function selectPrimaryWithDiversity(candidates, agent, providerUsage, targetByProvider, remainingSlots, externalSignals, versionRecencyMap) {
15340
- if (candidates.length === 0)
15341
- return null;
15342
- const candidateScores = candidates.map((model) => {
15343
- const usage = providerUsage.get(model.providerID) ?? 0;
15344
- const target = targetByProvider[model.providerID] ?? 1;
15345
- const softCap = target;
15346
- const hardCap = Math.min(target + 1, 4);
15347
- const deficit = Math.max(0, target - usage);
15348
- const softOverflow = Math.max(0, usage + 1 - softCap);
15349
- const hardOverflow = Math.max(0, usage + 1 - hardCap);
15350
- const rawScore = combinedScore(agent, model, externalSignals, versionRecencyMap);
15351
- const adjustedScore = rawScore + deficit * 14 - softOverflow * 18 - hardOverflow * 100;
15352
- return {
15353
- model,
15354
- usage,
15355
- target,
15356
- rawScore,
15357
- adjustedScore: Math.round(adjustedScore * 1000) / 1000
15358
- };
15359
- });
15360
- const bestRaw = Math.max(...candidateScores.map((item) => item.rawScore));
15361
- const window = getQualityWindow(agent);
15362
- let eligible = candidateScores.filter((item) => item.rawScore >= bestRaw - window);
15363
- const mustFillProviders = Object.entries(targetByProvider).filter(([providerID, target]) => {
15364
- const usage = providerUsage.get(providerID) ?? 0;
15365
- return Math.max(0, target - usage) >= remainingSlots;
15366
- }).map(([providerID]) => providerID);
15367
- if (mustFillProviders.length > 0) {
15368
- const forced = eligible.filter((item) => mustFillProviders.includes(item.model.providerID));
15369
- if (forced.length > 0)
15370
- eligible = forced;
15371
- }
15372
- eligible.sort((a, b) => {
15373
- const delta = b.adjustedScore - a.adjustedScore;
15374
- if (delta !== 0)
15375
- return delta;
15376
- const ratioA = a.target > 0 ? a.usage / a.target : a.usage;
15377
- const ratioB = b.target > 0 ? b.usage / b.target : b.usage;
15378
- if (ratioA !== ratioB)
15379
- return ratioA - ratioB;
15380
- if (a.rawScore !== b.rawScore)
15381
- return b.rawScore - a.rawScore;
15382
- const providerTie = a.model.providerID.localeCompare(b.model.providerID);
15383
- if (providerTie !== 0)
15384
- return providerTie;
15385
- return a.model.model.localeCompare(b.model.model);
15386
- });
15387
- let chosen = eligible[0] ?? candidateScores[0];
15388
- if (!chosen)
15389
- return null;
15390
- if (chosen.usage >= 2) {
15391
- const bestUnused = candidateScores.find((item) => item.usage === 0);
15392
- if (bestUnused && bestUnused.adjustedScore >= chosen.adjustedScore - 9) {
15393
- chosen = bestUnused;
15394
- }
15395
- }
15396
- if (agent !== "explorer" && isZai47Model(chosen.model) && hasFlashToken(chosen.model)) {
15397
- const kimiCandidate = candidateScores.find((item) => isKimiK25Model(item.model));
15398
- if (kimiCandidate && kimiCandidate.rawScore >= chosen.rawScore - 2) {
15399
- chosen = kimiCandidate;
15400
- }
15401
- }
15402
- return chosen.model;
15403
- }
15404
- function dedupe2(models) {
15405
- const seen = new Set;
15406
- const result = [];
15407
- for (const model of models) {
15408
- if (!model || seen.has(model))
15409
- continue;
15410
- seen.add(model);
15411
- result.push(model);
15412
- }
15413
- return result;
15414
- }
15415
- function finalizeChainWithTail(prefix, preferredTail) {
15416
- if (!preferredTail) {
15417
- return dedupe2([...prefix, "opencode/big-pickle"]).slice(0, 10);
15418
- }
15419
- const withoutTail = prefix.filter((model) => model !== preferredTail).slice(0, 9);
15420
- return [...withoutTail, preferredTail];
15421
- }
15422
- function ensureSyntheticModel(models, fullModelID) {
15423
- if (!fullModelID)
15424
- return models;
15425
- if (models.some((model) => model.model === fullModelID))
15426
- return models;
15427
- const [providerID, modelID] = fullModelID.split("/");
15428
- if (!providerID || !modelID)
15429
- return models;
15430
- return [
15431
- ...models,
15432
- {
15433
- providerID,
15434
- model: fullModelID,
15435
- name: modelID,
15436
- status: "active",
15437
- contextLimit: 200000,
15438
- outputLimit: 32000,
15439
- reasoning: true,
15440
- toolcall: true,
15441
- attachment: false
15442
- }
15443
- ];
15444
- }
15445
- function buildDynamicModelPlan(catalog, config2, externalSignals, options) {
15446
- const catalogWithSelectedModels = [
15447
- config2.selectedChutesPrimaryModel,
15448
- config2.selectedChutesSecondaryModel,
15449
- config2.selectedOpenCodePrimaryModel,
15450
- config2.selectedOpenCodeSecondaryModel
15451
- ].reduce((acc, modelID) => ensureSyntheticModel(acc, modelID), catalog);
15452
- const enabledProviders = new Set(getEnabledProviders(config2));
15453
- const providerUniverse = catalogWithSelectedModels.filter((m) => {
15454
- if (!enabledProviders.has(m.providerID))
15455
- return false;
15456
- if (m.providerID === "chutes" && /qwen/i.test(m.model)) {
15457
- return false;
15458
- }
15459
- return true;
15460
- });
15461
- const engineVersion = options?.scoringEngineVersion ?? config2.scoringEngineVersion ?? "v1";
15462
- const versionRecencyMap = getVersionRecencyMap(providerUniverse);
15463
- const providerCandidates = selectTopModelsPerProvider(providerUniverse, engineVersion, externalSignals, versionRecencyMap);
15464
- if (providerCandidates.length === 0) {
15465
- return null;
15466
- }
15467
- const hasPaidProviderEnabled = config2.hasOpenAI || config2.hasAnthropic || config2.hasCopilot || config2.hasZaiPlan || config2.hasKimi || config2.hasAntigravity;
15468
- const paidProviders = dedupe2(providerCandidates.map((model) => model.providerID).filter((providerID) => providerID !== "opencode")).sort((a, b) => a.localeCompare(b));
15469
- const targetByProvider = {};
15470
- if (paidProviders.length > 0) {
15471
- const baseTarget = Math.floor(AGENTS.length / paidProviders.length);
15472
- const extra = AGENTS.length % paidProviders.length;
15473
- for (const [index, providerID] of paidProviders.entries()) {
15474
- targetByProvider[providerID] = baseTarget + (index < extra ? 1 : 0);
15475
- }
15476
- }
15477
- const providerUsage = new Map;
15478
- const rankCache = new Map;
15479
- const shadowDiffs = {};
15480
- const agents = {};
15481
- const chains = {};
15482
- const provenance = {};
15483
- const getSelectedChutesForAgent = (agent) => {
15484
- if (!config2.hasChutes)
15485
- return;
15486
- return agent === "explorer" || agent === "librarian" || agent === "fixer" ? config2.selectedChutesSecondaryModel ?? config2.selectedChutesPrimaryModel : config2.selectedChutesPrimaryModel;
15487
- };
15488
- const getSelectedOpenCodeForAgent = (agent) => {
15489
- if (!config2.useOpenCodeFreeModels)
15490
- return;
15491
- return agent === "explorer" || agent === "librarian" || agent === "fixer" ? config2.selectedOpenCodeSecondaryModel ?? config2.selectedOpenCodePrimaryModel : config2.selectedOpenCodePrimaryModel;
15492
- };
15493
- const getPinnedModelForProvider = (agent, providerID) => {
15494
- if (providerID === "chutes")
15495
- return getSelectedChutesForAgent(agent);
15496
- if (providerID === "opencode")
15497
- return getSelectedOpenCodeForAgent(agent);
15498
- return;
15499
- };
15500
- const getRankedModels = (agent) => {
15501
- const cached2 = rankCache.get(agent);
15502
- if (cached2)
15503
- return cached2;
15504
- const rankedV1 = rankModels2(providerCandidates, agent, externalSignals);
15505
- if (engineVersion === "v1") {
15506
- rankCache.set(agent, rankedV1);
15507
- return rankedV1;
15508
- }
15509
- const rankedV2 = rankModelsV2(providerCandidates, agent, externalSignals).map((candidate) => candidate.model);
15510
- if (engineVersion === "v2-shadow") {
15511
- shadowDiffs[agent] = {
15512
- v1TopModel: rankedV1[0]?.model,
15513
- v2TopModel: rankedV2[0]?.model
15514
- };
15515
- rankCache.set(agent, rankedV1);
15516
- return rankedV1;
15517
- }
15518
- rankCache.set(agent, rankedV2);
15519
- return rankedV2;
15520
- };
15521
- for (const [agentIndex, agent] of PRIMARY_ASSIGNMENT_ORDER.entries()) {
15522
- const ranked = getRankedModels(agent);
15523
- const primaryPool = hasPaidProviderEnabled ? ranked.filter((model) => !FREE_BIASED_PROVIDERS.has(model.providerID)) : ranked;
15524
- const remainingSlots = PRIMARY_ASSIGNMENT_ORDER.length - agentIndex;
15525
- const primary = selectPrimaryWithDiversity(primaryPool.length > 0 ? primaryPool : ranked, agent, providerUsage, targetByProvider, remainingSlots, externalSignals, versionRecencyMap) ?? ranked[0];
15526
- if (!primary)
15527
- continue;
15528
- providerUsage.set(primary.providerID, (providerUsage.get(primary.providerID) ?? 0) + 1);
15529
- const providerOrder = dedupe2(ranked.map((m) => m.providerID));
15530
- const perProviderBest = providerOrder.flatMap((providerID) => {
15531
- const providerModels = ranked.filter((m) => m.providerID === providerID);
15532
- const pinned = getPinnedModelForProvider(agent, providerID);
15533
- if (pinned && providerModels.some((m) => m.model === pinned)) {
15534
- return [pinned];
15535
- }
15536
- return getProviderBundle(providerModels, agent, externalSignals, versionRecencyMap);
15537
- });
15538
- const nonFreePerProviderBest = perProviderBest.filter((model) => !model.startsWith("opencode/"));
15539
- const freePerProviderBest = perProviderBest.filter((model) => model.startsWith("opencode/"));
15540
- const selectedOpencode = getSelectedOpenCodeForAgent(agent);
15541
- const selectedChutes = getSelectedChutesForAgent(agent);
15542
- const chain = dedupe2([
15543
- primary.model,
15544
- ...nonFreePerProviderBest,
15545
- selectedChutes,
15546
- selectedOpencode,
15547
- ...freePerProviderBest
15548
- ]);
15549
- const deterministicFreeTail = selectedOpencode ?? freePerProviderBest[0] ?? ranked.find((model) => model.model.startsWith("opencode/"))?.model;
15550
- const finalizedChain = finalizeChainWithTail(chain, deterministicFreeTail);
15551
- const providerPolicyChain = dedupe2([selectedChutes, selectedOpencode]);
15552
- const systemDefaultModel = selectedOpencode ?? "opencode/big-pickle";
15553
- const resolved = resolveAgentWithPrecedence({
15554
- agentName: agent,
15555
- dynamicRecommendation: finalizedChain,
15556
- providerFallbackPolicy: providerPolicyChain,
15557
- systemDefault: [systemDefaultModel]
15558
- });
15559
- let finalModel = resolved.model;
15560
- let finalChain = resolved.chain;
15561
- const selectedChutesForAgent = getSelectedChutesForAgent(agent);
15562
- const selectedOpenCodeForAgent = getSelectedOpenCodeForAgent(agent);
15563
- const forceChutes = finalModel.startsWith("chutes/") && Boolean(selectedChutesForAgent);
15564
- const forceOpenCode = finalModel.startsWith("opencode/") && Boolean(selectedOpenCodeForAgent);
15565
- if (forceOpenCode && selectedOpenCodeForAgent) {
15566
- finalModel = selectedOpenCodeForAgent;
15567
- finalChain = dedupe2([selectedOpenCodeForAgent, ...finalChain]);
15568
- }
15569
- if (forceChutes && selectedChutesForAgent) {
15570
- finalModel = selectedChutesForAgent;
15571
- finalChain = dedupe2([selectedChutesForAgent, ...finalChain]);
15572
- }
15573
- const wasForced = forceChutes || forceOpenCode;
15574
- agents[agent] = {
15575
- model: finalModel,
15576
- variant: ROLE_VARIANT[agent]
15577
- };
15578
- chains[agent] = finalChain;
15579
- provenance[agent] = {
15580
- winnerLayer: wasForced ? "manual-user-plan" : resolved.provenance.winnerLayer,
15581
- winnerModel: finalModel
15582
- };
15583
- }
15584
- if (hasPaidProviderEnabled) {
15585
- for (const providerID of paidProviders) {
15586
- if ((providerUsage.get(providerID) ?? 0) > 0)
15587
- continue;
15588
- let bestSwap;
15589
- for (const agent of PRIMARY_ASSIGNMENT_ORDER) {
15590
- const currentModel = agents[agent]?.model;
15591
- if (!currentModel)
15592
- continue;
15593
- const ranked = getRankedModels(agent);
15594
- const pinned = getPinnedModelForProvider(agent, providerID);
15595
- const candidate = ranked.find((model) => model.model === pinned) ?? ranked.find((model) => model.providerID === providerID);
15596
- const current = ranked.find((model) => model.model === currentModel);
15597
- if (!candidate || !current)
15598
- continue;
15599
- const currentScore = combinedScore(agent, current, externalSignals, versionRecencyMap);
15600
- const candidateScore = combinedScore(agent, candidate, externalSignals, versionRecencyMap);
15601
- const loss = currentScore - candidateScore;
15602
- if (!bestSwap || loss < bestSwap.loss) {
15603
- bestSwap = {
15604
- agent,
15605
- candidateModel: candidate.model,
15606
- loss
15607
- };
15608
- }
15609
- }
15610
- if (!bestSwap)
15611
- continue;
15612
- const existingProvider = agents[bestSwap.agent]?.model.split("/")[0] ?? providerID;
15613
- agents[bestSwap.agent].model = bestSwap.candidateModel;
15614
- chains[bestSwap.agent] = dedupe2([
15615
- bestSwap.candidateModel,
15616
- ...chains[bestSwap.agent] ?? []
15617
- ]).slice(0, 10);
15618
- provenance[bestSwap.agent] = {
15619
- winnerLayer: "provider-fallback-policy",
15620
- winnerModel: bestSwap.candidateModel
15621
- };
15622
- providerUsage.set(providerID, (providerUsage.get(providerID) ?? 0) + 1);
15623
- providerUsage.set(existingProvider, Math.max(0, (providerUsage.get(existingProvider) ?? 1) - 1));
15624
- }
15625
- }
15626
- if (config2.balanceProviderUsage && hasPaidProviderEnabled) {
15627
- rebalanceForSubscriptionMode(agents, chains, provenance, paidProviders, getRankedModels, getPinnedModelForProvider, targetByProvider, externalSignals, versionRecencyMap, engineVersion);
15628
- }
15629
- if (Object.keys(agents).length === 0) {
15630
- return null;
15631
- }
15632
- return {
15633
- agents,
15634
- chains,
15635
- provenance,
15636
- scoring: {
15637
- engineVersionApplied: engineVersion === "v2" ? "v2" : "v1",
15638
- shadowCompared: engineVersion === "v2-shadow",
15639
- diffs: engineVersion === "v2-shadow" ? shadowDiffs : undefined
15640
- }
15641
- };
15642
- }
15643
- // src/utils/logger.ts
15644
- import * as os from "os";
15645
- import * as path from "path";
15646
- var logFile = path.join(os.tmpdir(), "oh-my-opencode-slim.log");
15647
- // src/utils/env.ts
15648
- function getEnv(name) {
15649
- const bunValue = globalThis.Bun?.env?.[name];
15650
- if (typeof bunValue === "string" && bunValue.length > 0)
15651
- return bunValue;
15652
- const processValue = globalThis.process?.env?.[name];
15653
- return typeof processValue === "string" && processValue.length > 0 ? processValue : undefined;
15654
- }
15655
- // src/cli/external-rankings.ts
15656
- function normalizeKey(input) {
15657
- return input.trim().toLowerCase();
15658
- }
15659
- function baseAliases(key) {
15660
- return buildModelKeyAliases(normalizeKey(key));
15661
- }
15662
- function providerScopedAlias(alias, providerPrefix) {
15663
- if (!providerPrefix || alias.includes("/"))
15664
- return alias;
15665
- return `${providerPrefix}/${alias}`;
15666
- }
15667
- function mergeSignal(existing, incoming) {
15668
- if (!existing)
15669
- return incoming;
15670
- return {
15671
- qualityScore: incoming.qualityScore ?? existing.qualityScore,
15672
- codingScore: incoming.codingScore ?? existing.codingScore,
15673
- latencySeconds: incoming.latencySeconds ?? existing.latencySeconds,
15674
- inputPricePer1M: incoming.inputPricePer1M ?? existing.inputPricePer1M,
15675
- outputPricePer1M: incoming.outputPricePer1M ?? existing.outputPricePer1M,
15676
- source: "merged"
15677
- };
15678
- }
15679
- function providerPrefixFromCreator(creatorSlug) {
15680
- if (!creatorSlug)
15681
- return;
15682
- const slug = creatorSlug.toLowerCase();
15683
- if (slug.includes("openai"))
15684
- return "openai";
15685
- if (slug.includes("anthropic"))
15686
- return "anthropic";
15687
- if (slug.includes("google"))
15688
- return "google";
15689
- if (slug.includes("chutes"))
15690
- return "chutes";
15691
- if (slug.includes("copilot") || slug.includes("github"))
15692
- return "github-copilot";
15693
- if (slug.includes("zai") || slug.includes("z-ai"))
15694
- return "zai-coding-plan";
15695
- if (slug.includes("kimi"))
15696
- return "kimi-for-coding";
15697
- if (slug.includes("opencode"))
15698
- return "opencode";
15699
- return;
15700
- }
15701
- function parseOpenRouterPrice(value) {
15702
- if (!value)
15703
- return;
15704
- const parsed = Number.parseFloat(value);
15705
- if (!Number.isFinite(parsed))
15706
- return;
15707
- return parsed * 1e6;
15708
- }
15709
- async function fetchJsonWithTimeout(url2, init, timeoutMs) {
15710
- const controller = new AbortController;
15711
- const timer = setTimeout(() => controller.abort(), timeoutMs);
15712
- try {
15713
- return await fetch(url2, {
15714
- ...init,
15715
- signal: controller.signal
15716
- });
15717
- } finally {
15718
- clearTimeout(timer);
15719
- }
15720
- }
15721
- async function fetchArtificialAnalysisSignals(apiKey) {
15722
- const response = await fetchJsonWithTimeout("https://artificialanalysis.ai/api/v2/data/llms/models", {
15723
- headers: {
15724
- "x-api-key": apiKey
15725
- }
15726
- }, 8000);
15727
- if (!response.ok) {
15728
- throw new Error(`Artificial Analysis request failed (${response.status} ${response.statusText})`);
15729
- }
15730
- const parsed = await response.json();
15731
- const map2 = {};
15732
- for (const model of parsed.data ?? []) {
15733
- const baseSignal = {
15734
- qualityScore: model.evaluations?.artificial_analysis_intelligence_index,
15735
- codingScore: model.evaluations?.artificial_analysis_coding_index ?? model.evaluations?.livecodebench,
15736
- latencySeconds: model.median_time_to_first_token_seconds,
15737
- inputPricePer1M: model.pricing?.price_1m_input_tokens ?? model.pricing?.price_1m_blended_3_to_1,
15738
- outputPricePer1M: model.pricing?.price_1m_output_tokens ?? model.pricing?.price_1m_blended_3_to_1,
15739
- source: "artificial-analysis"
15740
- };
15741
- const id = model.id ? normalizeKey(model.id) : undefined;
15742
- const slug = model.slug ? normalizeKey(model.slug) : undefined;
15743
- const name = model.name ? normalizeKey(model.name) : undefined;
15744
- const providerPrefix = providerPrefixFromCreator(model.model_creator?.slug);
15745
- for (const key of [id, slug, name]) {
15746
- if (!key)
15747
- continue;
15748
- for (const alias of baseAliases(key)) {
15749
- if (!providerPrefix || alias.includes("/")) {
15750
- map2[alias] = mergeSignal(map2[alias], baseSignal);
15751
- }
15752
- const scopedAlias = providerScopedAlias(alias, providerPrefix);
15753
- map2[scopedAlias] = mergeSignal(map2[scopedAlias], baseSignal);
15754
- }
15755
- }
15756
- }
15757
- return map2;
15758
- }
15759
- async function fetchOpenRouterSignals(apiKey) {
15760
- const response = await fetchJsonWithTimeout("https://openrouter.ai/api/v1/models", {
15761
- headers: {
15762
- Authorization: `Bearer ${apiKey}`
15763
- }
15764
- }, 8000);
15765
- if (!response.ok) {
15766
- throw new Error(`OpenRouter request failed (${response.status} ${response.statusText})`);
15767
- }
15768
- const parsed = await response.json();
15769
- const map2 = {};
15770
- for (const model of parsed.data ?? []) {
15771
- if (!model.id)
15772
- continue;
15773
- const key = normalizeKey(model.id);
15774
- const providerPrefix = key.split("/")[0];
15775
- const signal = {
15776
- inputPricePer1M: parseOpenRouterPrice(model.pricing?.prompt),
15777
- outputPricePer1M: parseOpenRouterPrice(model.pricing?.completion),
15778
- source: "openrouter"
15779
- };
15780
- for (const alias of baseAliases(key)) {
15781
- if (alias.includes("/")) {
15782
- map2[alias] = mergeSignal(map2[alias], signal);
15783
- }
15784
- const scopedAlias = providerScopedAlias(alias, providerPrefix);
15785
- map2[scopedAlias] = mergeSignal(map2[scopedAlias], signal);
15786
- }
15787
- }
15788
- return map2;
15789
- }
15790
- async function fetchExternalModelSignals(options) {
15791
- const warnings = [];
15792
- const aggregate = {};
15793
- const aaKey = options?.artificialAnalysisApiKey ?? getEnv("ARTIFICIAL_ANALYSIS_API_KEY");
15794
- const orKey = options?.openRouterApiKey ?? getEnv("OPENROUTER_API_KEY");
15795
- const aaPromise = aaKey ? fetchArtificialAnalysisSignals(aaKey) : Promise.resolve({});
15796
- const orPromise = orKey ? fetchOpenRouterSignals(orKey) : Promise.resolve({});
15797
- const [aaResult, orResult] = await Promise.allSettled([aaPromise, orPromise]);
15798
- if (aaResult.status === "fulfilled") {
15799
- for (const [key, signal] of Object.entries(aaResult.value)) {
15800
- aggregate[key] = mergeSignal(aggregate[key], signal);
15801
- }
15802
- } else if (aaKey) {
15803
- warnings.push(`Artificial Analysis unavailable: ${aaResult.reason instanceof Error ? aaResult.reason.message : String(aaResult.reason)}`);
15804
- }
15805
- if (orResult.status === "fulfilled") {
15806
- for (const [key, signal] of Object.entries(orResult.value)) {
15807
- aggregate[key] = mergeSignal(aggregate[key], signal);
15808
- }
15809
- } else if (orKey) {
15810
- warnings.push(`OpenRouter unavailable: ${orResult.reason instanceof Error ? orResult.reason.message : String(orResult.reason)}`);
15811
- }
15812
- return { signals: aggregate, warnings };
15813
- }
15814
- // src/cli/system.ts
15815
- import { statSync as statSync3 } from "fs";
15816
- var cachedOpenCodePath = null;
15817
- function getOpenCodePaths() {
15818
- const home = process.env.HOME || process.env.USERPROFILE || "";
15819
- return [
15820
- "opencode",
15821
- `${home}/.local/bin/opencode`,
15822
- `${home}/.opencode/bin/opencode`,
15823
- `${home}/bin/opencode`,
15824
- "/usr/local/bin/opencode",
15825
- "/opt/opencode/bin/opencode",
15826
- "/usr/bin/opencode",
15827
- "/bin/opencode",
15828
- "/Applications/OpenCode.app/Contents/MacOS/opencode",
15829
- `${home}/Applications/OpenCode.app/Contents/MacOS/opencode`,
15830
- "/opt/homebrew/bin/opencode",
15831
- "/home/linuxbrew/.linuxbrew/bin/opencode",
15832
- `${home}/homebrew/bin/opencode`,
15833
- `${home}/Library/Application Support/opencode/bin/opencode`,
15834
- "/snap/bin/opencode",
15835
- "/var/snap/opencode/current/bin/opencode",
15836
- "/var/lib/flatpak/exports/bin/ai.opencode.OpenCode",
15837
- `${home}/.local/share/flatpak/exports/bin/ai.opencode.OpenCode`,
15838
- "/nix/store/opencode/bin/opencode",
15839
- `${home}/.nix-profile/bin/opencode`,
15840
- "/run/current-system/sw/bin/opencode",
15841
- `${home}/.cargo/bin/opencode`,
15842
- `${home}/.npm-global/bin/opencode`,
15843
- "/usr/local/lib/node_modules/opencode/bin/opencode",
15844
- `${home}/.yarn/bin/opencode`,
15845
- `${home}/.pnpm-global/bin/opencode`
15846
- ];
15847
- }
15848
- function resolveOpenCodePath() {
15849
- if (cachedOpenCodePath) {
15850
- return cachedOpenCodePath;
15851
- }
15852
- const paths = getOpenCodePaths();
15853
- for (const opencodePath of paths) {
15854
- if (opencodePath === "opencode")
15855
- continue;
15856
- try {
15857
- const stat = statSync3(opencodePath);
15858
- if (stat.isFile()) {
15859
- cachedOpenCodePath = opencodePath;
15860
- return opencodePath;
15861
- }
15862
- } catch {}
15863
- }
15864
- return "opencode";
14111
+ const paths = getOpenCodePaths();
14112
+ for (const opencodePath of paths) {
14113
+ if (opencodePath === "opencode")
14114
+ continue;
14115
+ try {
14116
+ const stat = statSync3(opencodePath);
14117
+ if (stat.isFile()) {
14118
+ cachedOpenCodePath = opencodePath;
14119
+ return opencodePath;
14120
+ }
14121
+ } catch {}
14122
+ }
14123
+ return "opencode";
15865
14124
  }
15866
14125
  async function isOpenCodeInstalled() {
15867
14126
  const paths = getOpenCodePaths();
@@ -15896,193 +14155,8 @@ async function getOpenCodeVersion() {
15896
14155
  return null;
15897
14156
  }
15898
14157
  function getOpenCodePath() {
15899
- const path2 = resolveOpenCodePath();
15900
- return path2 === "opencode" ? null : path2;
15901
- }
15902
-
15903
- // src/cli/opencode-models.ts
15904
- function isFreeModel(record2) {
15905
- const inputCost = record2.cost?.input ?? 0;
15906
- const outputCost = record2.cost?.output ?? 0;
15907
- const cacheReadCost = record2.cost?.cache?.read ?? 0;
15908
- const cacheWriteCost = record2.cost?.cache?.write ?? 0;
15909
- return inputCost === 0 && outputCost === 0 && cacheReadCost === 0 && cacheWriteCost === 0;
15910
- }
15911
- function parseDailyRequestLimit(record2) {
15912
- const explicitLimit = record2.quota?.requestsPerDay ?? record2.meta?.requestsPerDay ?? record2.meta?.dailyLimit;
15913
- if (typeof explicitLimit === "number" && Number.isFinite(explicitLimit)) {
15914
- return explicitLimit;
15915
- }
15916
- const source = `${record2.id} ${record2.name ?? ""}`.toLowerCase();
15917
- const match = source.match(/\b(300|2000|5000)\b(?:\s*(?:req|requests|rpd|\/day))?/);
15918
- if (!match)
15919
- return;
15920
- const parsed = Number.parseInt(match[1], 10);
15921
- return Number.isFinite(parsed) ? parsed : undefined;
15922
- }
15923
- function normalizeDiscoveredModel(record2, providerFilter) {
15924
- if (providerFilter && record2.providerID !== providerFilter)
15925
- return null;
15926
- const fullModel = `${record2.providerID}/${record2.id}`;
15927
- return {
15928
- providerID: record2.providerID,
15929
- model: fullModel,
15930
- name: record2.name ?? record2.id,
15931
- status: record2.status ?? "active",
15932
- contextLimit: record2.limit?.context ?? 0,
15933
- outputLimit: record2.limit?.output ?? 0,
15934
- reasoning: record2.capabilities?.reasoning === true,
15935
- toolcall: record2.capabilities?.toolcall === true,
15936
- attachment: record2.capabilities?.attachment === true,
15937
- dailyRequestLimit: parseDailyRequestLimit(record2),
15938
- costInput: record2.cost?.input,
15939
- costOutput: record2.cost?.output
15940
- };
15941
- }
15942
- function parseOpenCodeModelsVerboseOutput(output, providerFilter, freeOnly = true) {
15943
- const lines = output.split(/\r?\n/);
15944
- const models = [];
15945
- const modelHeaderPattern = /^[a-z0-9-]+\/.+$/i;
15946
- for (let index = 0;index < lines.length; index++) {
15947
- const line = lines[index]?.trim();
15948
- if (!line || !line.includes("/"))
15949
- continue;
15950
- if (!modelHeaderPattern.test(line))
15951
- continue;
15952
- let jsonStart = -1;
15953
- for (let search = index + 1;search < lines.length; search++) {
15954
- if (lines[search]?.trim().startsWith("{")) {
15955
- jsonStart = search;
15956
- break;
15957
- }
15958
- if (modelHeaderPattern.test(lines[search]?.trim() ?? "")) {
15959
- break;
15960
- }
15961
- }
15962
- if (jsonStart === -1)
15963
- continue;
15964
- let braceDepth = 0;
15965
- const jsonLines = [];
15966
- let jsonEnd = -1;
15967
- for (let cursor = jsonStart;cursor < lines.length; cursor++) {
15968
- const current = lines[cursor] ?? "";
15969
- jsonLines.push(current);
15970
- for (const char of current) {
15971
- if (char === "{")
15972
- braceDepth++;
15973
- if (char === "}")
15974
- braceDepth--;
15975
- }
15976
- if (braceDepth === 0 && jsonLines.length > 0) {
15977
- jsonEnd = cursor;
15978
- break;
15979
- }
15980
- }
15981
- if (jsonEnd === -1)
15982
- continue;
15983
- try {
15984
- const parsed = JSON.parse(jsonLines.join(`
15985
- `));
15986
- const normalized = normalizeDiscoveredModel(parsed, providerFilter);
15987
- if (!normalized)
15988
- continue;
15989
- if (freeOnly && !isFreeModel(parsed))
15990
- continue;
15991
- if (normalized)
15992
- models.push(normalized);
15993
- } catch {}
15994
- index = jsonEnd;
15995
- }
15996
- return models;
15997
- }
15998
- async function discoverModelsByProvider(providerID, freeOnly = true) {
15999
- try {
16000
- const opencodePath = resolveOpenCodePath();
16001
- const proc = Bun.spawn([opencodePath, "models", "--refresh", "--verbose"], {
16002
- stdout: "pipe",
16003
- stderr: "pipe"
16004
- });
16005
- const stdout = await new Response(proc.stdout).text();
16006
- const stderr = await new Response(proc.stderr).text();
16007
- await proc.exited;
16008
- if (proc.exitCode !== 0) {
16009
- return {
16010
- models: [],
16011
- error: stderr.trim() || "Failed to fetch OpenCode models."
16012
- };
16013
- }
16014
- return {
16015
- models: parseOpenCodeModelsVerboseOutput(stdout, providerID, freeOnly)
16016
- };
16017
- } catch {
16018
- return {
16019
- models: [],
16020
- error: "Unable to run `opencode models --refresh --verbose`."
16021
- };
16022
- }
16023
- }
16024
- async function discoverModelCatalog() {
16025
- try {
16026
- const opencodePath = resolveOpenCodePath();
16027
- const proc = Bun.spawn([opencodePath, "models", "--refresh", "--verbose"], {
16028
- stdout: "pipe",
16029
- stderr: "pipe"
16030
- });
16031
- const stdout = await new Response(proc.stdout).text();
16032
- const stderr = await new Response(proc.stderr).text();
16033
- await proc.exited;
16034
- if (proc.exitCode !== 0) {
16035
- return {
16036
- models: [],
16037
- error: stderr.trim() || "Failed to fetch OpenCode models."
16038
- };
16039
- }
16040
- return {
16041
- models: parseOpenCodeModelsVerboseOutput(stdout, undefined, false)
16042
- };
16043
- } catch {
16044
- return {
16045
- models: [],
16046
- error: "Unable to run `opencode models --refresh --verbose`."
16047
- };
16048
- }
16049
- }
16050
- async function discoverOpenCodeFreeModels() {
16051
- const result = await discoverModelsByProvider("opencode", true);
16052
- return { models: result.models, error: result.error };
16053
- }
16054
- async function discoverProviderModels(providerID) {
16055
- return discoverModelsByProvider(providerID, false);
16056
- }
16057
- // src/cli/opencode-selection.ts
16058
- var scoreOpenCodePrimaryForCoding = (model) => {
16059
- return (model.reasoning ? 100 : 0) + (model.toolcall ? 80 : 0) + (model.attachment ? 20 : 0) + Math.min(model.contextLimit, 1e6) / 1e4 + Math.min(model.outputLimit, 300000) / 1e4 + (model.status === "active" ? 10 : 0);
16060
- };
16061
- function speedBonus2(modelName) {
16062
- const lower = modelName.toLowerCase();
16063
- let score = 0;
16064
- if (lower.includes("nano"))
16065
- score += 60;
16066
- if (lower.includes("flash"))
16067
- score += 45;
16068
- if (lower.includes("mini"))
16069
- score += 25;
16070
- if (lower.includes("preview"))
16071
- score += 10;
16072
- return score;
16073
- }
16074
- var scoreOpenCodeSupportForCoding = (model) => {
16075
- return (model.toolcall ? 90 : 0) + (model.reasoning ? 50 : 0) + speedBonus2(model.model) + Math.min(model.contextLimit, 400000) / 20000 + (model.status === "active" ? 5 : 0);
16076
- };
16077
- function pickBestCodingOpenCodeModel(models) {
16078
- return pickBestModel(models, scoreOpenCodePrimaryForCoding);
16079
- }
16080
- function pickSupportOpenCodeModel(models, primaryModel) {
16081
- const { support } = pickPrimaryAndSupport(models, {
16082
- primary: scoreOpenCodePrimaryForCoding,
16083
- support: scoreOpenCodeSupportForCoding
16084
- }, primaryModel);
16085
- return support;
14158
+ const path = resolveOpenCodePath();
14159
+ return path === "opencode" ? null : path;
16086
14160
  }
16087
14161
  // src/cli/install.ts
16088
14162
  var GREEN = "\x1B[32m";
@@ -16119,9 +14193,6 @@ function printError(message) {
16119
14193
  function printInfo(message) {
16120
14194
  console.log(`${SYMBOLS.info} ${message}`);
16121
14195
  }
16122
- function printWarning(message) {
16123
- console.log(`${SYMBOLS.warn} ${YELLOW}${message}${RESET}`);
16124
- }
16125
14196
  async function checkOpenCodeInstalled() {
16126
14197
  const installed = await isOpenCodeInstalled();
16127
14198
  if (!installed) {
@@ -16135,9 +14206,9 @@ async function checkOpenCodeInstalled() {
16135
14206
  return { ok: false };
16136
14207
  }
16137
14208
  const version2 = await getOpenCodeVersion();
16138
- const path2 = getOpenCodePath();
16139
- printSuccess(`OpenCode ${version2 ?? ""} detected${path2 ? ` (${DIM}${path2}${RESET})` : ""}`);
16140
- return { ok: true, version: version2 ?? undefined, path: path2 ?? undefined };
14209
+ const path = getOpenCodePath();
14210
+ printSuccess(`OpenCode ${version2 ?? ""} detected${path ? ` (${DIM}${path}${RESET})` : ""}`);
14211
+ return { ok: true, version: version2 ?? undefined, path: path ?? undefined };
16141
14212
  }
16142
14213
  function handleStepResult(result, successMsg) {
16143
14214
  if (!result.success) {
@@ -16147,605 +14218,39 @@ function handleStepResult(result, successMsg) {
16147
14218
  printSuccess(`${successMsg} ${SYMBOLS.arrow} ${DIM}${result.configPath}${RESET}`);
16148
14219
  return true;
16149
14220
  }
16150
- function formatConfigSummary(config2) {
16151
- const liteConfig = generateLiteConfig(config2);
16152
- const preset = liteConfig.preset || "unknown";
14221
+ function formatConfigSummary() {
16153
14222
  const lines = [];
16154
14223
  lines.push(`${BOLD}Configuration Summary${RESET}`);
16155
14224
  lines.push("");
16156
- lines.push(` ${BOLD}Preset:${RESET} ${BLUE}${preset}${RESET}`);
16157
- lines.push(` ${config2.hasKimi ? SYMBOLS.check : `${DIM}\u25CB${RESET}`} Kimi`);
16158
- lines.push(` ${config2.hasOpenAI ? SYMBOLS.check : `${DIM}\u25CB${RESET}`} OpenAI`);
16159
- lines.push(` ${config2.hasAnthropic ? SYMBOLS.check : `${DIM}\u25CB${RESET}`} Anthropic`);
16160
- lines.push(` ${config2.hasCopilot ? SYMBOLS.check : `${DIM}\u25CB${RESET}`} GitHub Copilot`);
16161
- lines.push(` ${config2.hasZaiPlan ? SYMBOLS.check : `${DIM}\u25CB${RESET}`} ZAI Coding Plan`);
16162
- lines.push(` ${config2.hasAntigravity ? SYMBOLS.check : `${DIM}\u25CB${RESET}`} Antigravity (Google)`);
16163
- lines.push(` ${config2.hasChutes ? SYMBOLS.check : `${DIM}\u25CB${RESET}`} Chutes`);
16164
- lines.push(` ${SYMBOLS.check} Opencode Zen`);
16165
- lines.push(` ${config2.balanceProviderUsage ? SYMBOLS.check : `${DIM}\u25CB${RESET}`} Balanced provider spend`);
16166
- if (config2.useOpenCodeFreeModels && config2.selectedOpenCodePrimaryModel) {
16167
- lines.push(` ${SYMBOLS.check} OpenCode Free Primary: ${BLUE}${config2.selectedOpenCodePrimaryModel}${RESET}`);
16168
- }
16169
- if (config2.useOpenCodeFreeModels && config2.selectedOpenCodeSecondaryModel) {
16170
- lines.push(` ${SYMBOLS.check} OpenCode Free Support: ${BLUE}${config2.selectedOpenCodeSecondaryModel}${RESET}`);
16171
- }
16172
- if (config2.hasChutes && config2.selectedChutesPrimaryModel) {
16173
- lines.push(` ${SYMBOLS.check} Chutes Primary: ${BLUE}${config2.selectedChutesPrimaryModel}${RESET}`);
16174
- }
16175
- if (config2.hasChutes && config2.selectedChutesSecondaryModel) {
16176
- lines.push(` ${SYMBOLS.check} Chutes Support: ${BLUE}${config2.selectedChutesSecondaryModel}${RESET}`);
16177
- }
16178
- lines.push(` ${config2.hasTmux ? SYMBOLS.check : `${DIM}\u25CB${RESET}`} Tmux Integration`);
14225
+ lines.push(` ${BOLD}Preset:${RESET} ${BLUE}openai${RESET}`);
14226
+ lines.push(` ${SYMBOLS.check} OpenAI (default)`);
14227
+ lines.push(` ${DIM}\u25CB Kimi \u2014 see docs/provider-configurations.md${RESET}`);
14228
+ lines.push(` ${DIM}\u25CB GitHub Copilot \u2014 see docs/provider-configurations.md${RESET}`);
14229
+ lines.push(` ${DIM}\u25CB ZAI Coding Plan \u2014 see docs/provider-configurations.md${RESET}`);
16179
14230
  return lines.join(`
16180
14231
  `);
16181
14232
  }
16182
- function printAgentModels(config2) {
16183
- const liteConfig = generateLiteConfig(config2);
16184
- const presetName = liteConfig.preset || "unknown";
16185
- const presets = liteConfig.presets;
16186
- const agents = presets?.[presetName];
16187
- if (!agents || Object.keys(agents).length === 0)
16188
- return;
16189
- console.log(`${BOLD}Agent Configuration (Preset: ${BLUE}${presetName}${RESET}):${RESET}`);
16190
- console.log();
16191
- const maxAgentLen = Math.max(...Object.keys(agents).map((a) => a.length));
16192
- for (const [agent, info] of Object.entries(agents)) {
16193
- const padding = " ".repeat(maxAgentLen - agent.length);
16194
- const skillsStr = info.skills.length > 0 ? ` ${DIM}[${info.skills.join(", ")}]${RESET}` : "";
16195
- console.log(` ${DIM}${agent}${RESET}${padding} ${SYMBOLS.arrow} ${BLUE}${info.model}${RESET}${skillsStr}`);
16196
- }
16197
- console.log();
16198
- }
16199
- function argsToConfig(args) {
16200
- return {
16201
- hasKimi: args.kimi === "yes",
16202
- hasOpenAI: args.openai === "yes",
16203
- hasAnthropic: args.anthropic === "yes",
16204
- hasCopilot: args.copilot === "yes",
16205
- hasZaiPlan: args.zaiPlan === "yes",
16206
- hasAntigravity: args.antigravity === "yes",
16207
- hasChutes: args.chutes === "yes",
16208
- hasOpencodeZen: true,
16209
- useOpenCodeFreeModels: args.opencodeFree === "yes",
16210
- preferredOpenCodeModel: args.opencodeFreeModel && args.opencodeFreeModel !== "auto" ? args.opencodeFreeModel : undefined,
16211
- artificialAnalysisApiKey: args.aaKey,
16212
- openRouterApiKey: args.openrouterKey,
16213
- balanceProviderUsage: args.balancedSpend === "yes",
16214
- hasTmux: args.tmux === "yes",
16215
- installSkills: args.skills === "yes",
16216
- installCustomSkills: args.skills === "yes",
16217
- setupMode: "quick",
16218
- dryRun: args.dryRun,
16219
- modelsOnly: args.modelsOnly
16220
- };
16221
- }
16222
- async function askModelSelection(rl, models, defaultModel, prompt) {
16223
- const defaultIndex = Math.max(0, models.findIndex((model) => model.model === defaultModel));
16224
- for (const [index, model] of models.entries()) {
16225
- const marker = model.model === defaultModel ? `${BOLD}(recommended)${RESET}` : "";
16226
- console.log(` ${DIM}${index + 1}.${RESET} ${BLUE}${model.model}${RESET} ${DIM}${model.name}${RESET} ${marker}`);
16227
- }
16228
- const answer = (await rl.question(`${BLUE}${prompt}${RESET} ${DIM}[default: ${defaultIndex + 1}]${RESET}: `)).trim().toLowerCase();
16229
- if (!answer)
16230
- return defaultModel;
16231
- const asNumber = Number.parseInt(answer, 10);
16232
- if (Number.isFinite(asNumber)) {
16233
- const chosen = models[asNumber - 1];
16234
- if (chosen)
16235
- return chosen.model;
16236
- }
16237
- const byId = models.find((model) => model.model.toLowerCase() === answer);
16238
- return byId?.model ?? defaultModel;
16239
- }
16240
- async function askYesNo(rl, prompt, defaultValue = "no") {
16241
- const hint = defaultValue === "yes" ? "[Y/n]" : "[y/N]";
16242
- const answer = (await rl.question(`${BLUE}${prompt}${RESET} ${hint}: `)).trim().toLowerCase();
16243
- if (answer === "")
16244
- return defaultValue;
16245
- if (answer === "y" || answer === "yes")
16246
- return "yes";
16247
- if (answer === "n" || answer === "no")
16248
- return "no";
16249
- return defaultValue;
16250
- }
16251
- async function askOptionalApiKey(rl, prompt, fromEnv) {
16252
- const hint = fromEnv ? "[optional, Enter keeps env value]" : "[optional]";
16253
- const answer = (await rl.question(`${BLUE}${prompt}${RESET} ${DIM}${hint}${RESET}: `)).trim();
16254
- if (!answer)
16255
- return fromEnv;
16256
- return answer;
16257
- }
16258
- async function askSetupMode(rl) {
16259
- console.log(`${BOLD}Choose setup mode:${RESET}`);
16260
- console.log(` ${DIM}1.${RESET} Quick setup - you choose providers, we auto-pick models`);
16261
- console.log(` ${DIM}2.${RESET} Manual setup - you choose providers and models per agent`);
16262
- console.log();
16263
- const answer = (await rl.question(`${BLUE}Selection${RESET} ${DIM}[default: 1]${RESET}: `)).trim().toLowerCase();
16264
- if (answer === "2" || answer === "manual")
16265
- return "manual";
16266
- return "quick";
16267
- }
16268
- async function askModelByNumber(rl, models, prompt, allowEmpty = false) {
16269
- let showAll = false;
16270
- while (true) {
16271
- console.log(`${BOLD}${prompt}${RESET}`);
16272
- console.log(`${DIM}Available models:${RESET}`);
16273
- const modelsToShow = showAll ? models : models.slice(0, 5);
16274
- const remainingCount = models.length - modelsToShow.length;
16275
- for (const [index, model] of modelsToShow.entries()) {
16276
- const displayIndex = showAll ? index + 1 : index + 1;
16277
- const name = model.name ? ` ${DIM}(${model.name})${RESET}` : "";
16278
- console.log(` ${DIM}${displayIndex}.${RESET} ${BLUE}${model.model}${RESET}${name}`);
16279
- }
16280
- if (!showAll && remainingCount > 0) {
16281
- console.log(`${DIM} ... and ${remainingCount} more${RESET}`);
16282
- console.log(`${DIM} (type "all" to show the full list)${RESET}`);
16283
- }
16284
- console.log(`${DIM} (or type any model ID directly)${RESET}`);
16285
- console.log();
16286
- const answer = (await rl.question(`${BLUE}Selection${RESET}: `)).trim().toLowerCase();
16287
- if (!answer) {
16288
- if (allowEmpty)
16289
- return;
16290
- return models[0]?.model;
16291
- }
16292
- if (answer === "all") {
16293
- showAll = true;
16294
- console.log();
16295
- continue;
16296
- }
16297
- const asNumber = Number.parseInt(answer, 10);
16298
- if (Number.isFinite(asNumber) && asNumber >= 1 && asNumber <= models.length) {
16299
- return models[asNumber - 1]?.model;
16300
- }
16301
- const byId = models.find((m) => m.model.toLowerCase() === answer);
16302
- if (byId)
16303
- return byId.model;
16304
- if (answer.includes("/"))
16305
- return answer;
16306
- printWarning(`Invalid selection: "${answer}". Using first available model.`);
16307
- return models[0]?.model;
16308
- }
16309
- }
16310
- async function configureAgentManually(rl, agentName, allModels) {
16311
- console.log();
16312
- console.log(`${BOLD}Configure ${agentName}:${RESET}`);
16313
- console.log();
16314
- const selectedModels = new Set;
16315
- const primary = await askModelByNumber(rl, allModels, "Primary model") ?? allModels[0]?.model ?? "opencode/big-pickle";
16316
- selectedModels.add(primary);
16317
- const availableForFallback1 = allModels.filter((m) => !selectedModels.has(m.model));
16318
- const fallback1 = availableForFallback1.length > 0 ? await askModelByNumber(rl, availableForFallback1, "Fallback 1 (optional, press Enter to skip)", true) ?? primary : primary;
16319
- if (fallback1 !== primary)
16320
- selectedModels.add(fallback1);
16321
- const availableForFallback2 = allModels.filter((m) => !selectedModels.has(m.model));
16322
- const fallback2 = availableForFallback2.length > 0 ? await askModelByNumber(rl, availableForFallback2, "Fallback 2 (optional, press Enter to skip)", true) ?? fallback1 : fallback1;
16323
- if (fallback2 !== fallback1)
16324
- selectedModels.add(fallback2);
16325
- const availableForFallback3 = allModels.filter((m) => !selectedModels.has(m.model));
16326
- const fallback3 = availableForFallback3.length > 0 ? await askModelByNumber(rl, availableForFallback3, "Fallback 3 (optional, press Enter to skip)", true) ?? fallback2 : fallback2;
16327
- return {
16328
- primary,
16329
- fallback1,
16330
- fallback2,
16331
- fallback3
16332
- };
16333
- }
16334
- async function runManualSetupMode(rl, detected, modelsOnly = false) {
16335
- console.log();
16336
- console.log(`${BOLD}Manual Setup Mode${RESET}`);
16337
- console.log("=".repeat(20));
16338
- console.log();
16339
- const existingAaKey = getEnv("ARTIFICIAL_ANALYSIS_API_KEY");
16340
- const existingOpenRouterKey = getEnv("OPENROUTER_API_KEY");
16341
- const artificialAnalysisApiKey = await askOptionalApiKey(rl, "Artificial Analysis API key for better ranking signals", existingAaKey);
16342
- if (existingAaKey && !artificialAnalysisApiKey) {
16343
- printInfo("Using existing ARTIFICIAL_ANALYSIS_API_KEY from environment.");
16344
- }
16345
- console.log();
16346
- const openRouterApiKey = await askOptionalApiKey(rl, "OpenRouter API key for pricing/metadata signals", existingOpenRouterKey);
16347
- if (existingOpenRouterKey && !openRouterApiKey) {
16348
- printInfo("Using existing OPENROUTER_API_KEY from environment.");
16349
- }
16350
- console.log();
16351
- const useOpenCodeFree = await askYesNo(rl, "Use Opencode Free models (opencode/*)?", "yes");
16352
- console.log();
16353
- let availableOpenCodeFreeModels;
16354
- let availableChutesModels;
16355
- if (useOpenCodeFree === "yes") {
16356
- printInfo("Refreshing models with: opencode models --refresh --verbose");
16357
- const discovery = await discoverOpenCodeFreeModels();
16358
- if (discovery.models.length === 0) {
16359
- printWarning(discovery.error ?? "No OpenCode free models found. Continuing without OpenCode free-model assignment.");
16360
- } else {
16361
- availableOpenCodeFreeModels = discovery.models;
16362
- printSuccess(`Found ${discovery.models.length} OpenCode free models`);
16363
- }
16364
- console.log();
16365
- }
16366
- const kimi = await askYesNo(rl, "Enable Kimi provider?", detected.hasKimi ? "yes" : "no");
16367
- console.log();
16368
- const openai = await askYesNo(rl, "Enable OpenAI provider?", detected.hasOpenAI ? "yes" : "no");
16369
- console.log();
16370
- const anthropic = await askYesNo(rl, "Enable Anthropic provider?", detected.hasAnthropic ? "yes" : "no");
16371
- console.log();
16372
- const copilot = await askYesNo(rl, "Enable GitHub Copilot provider?", detected.hasCopilot ? "yes" : "no");
16373
- console.log();
16374
- const zaiPlan = await askYesNo(rl, "Enable ZAI Coding Plan provider?", detected.hasZaiPlan ? "yes" : "no");
16375
- console.log();
16376
- const antigravity = await askYesNo(rl, "Enable Antigravity (Google) provider?", detected.hasAntigravity ? "yes" : "no");
16377
- console.log();
16378
- const chutes = await askYesNo(rl, "Enable Chutes provider?", detected.hasChutes ? "yes" : "no");
16379
- console.log();
16380
- if (chutes === "yes") {
16381
- printInfo("Refreshing Chutes model list with: opencode models --refresh --verbose");
16382
- const discovery = await discoverProviderModels("chutes");
16383
- if (discovery.models.length === 0) {
16384
- printWarning(discovery.error ?? "No Chutes models found. Continuing without Chutes dynamic assignment.");
16385
- } else {
16386
- availableChutesModels = discovery.models;
16387
- printSuccess(`Found ${discovery.models.length} Chutes models`);
16388
- }
16389
- console.log();
16390
- }
16391
- const availableModels = [];
16392
- if (useOpenCodeFree === "yes" && availableOpenCodeFreeModels) {
16393
- for (const model of availableOpenCodeFreeModels) {
16394
- availableModels.push({ model: model.model, name: model.name });
16395
- }
16396
- }
16397
- if (kimi === "yes") {
16398
- availableModels.push({ model: "kimi-for-coding/k2p5", name: "Kimi K2.5" });
16399
- }
16400
- if (openai === "yes") {
16401
- availableModels.push({
16402
- model: "openai/gpt-5.3-codex",
16403
- name: "GPT-5.3 Codex"
16404
- });
16405
- availableModels.push({
16406
- model: "openai/gpt-5.1-codex-mini",
16407
- name: "GPT-5.1 Codex Mini"
16408
- });
16409
- }
16410
- if (anthropic === "yes") {
16411
- availableModels.push({
16412
- model: "anthropic/claude-opus-4-6",
16413
- name: "Claude Opus 4.6"
16414
- });
16415
- availableModels.push({
16416
- model: "anthropic/claude-sonnet-4-5",
16417
- name: "Claude Sonnet 4.5"
16418
- });
16419
- availableModels.push({
16420
- model: "anthropic/claude-haiku-4-5",
16421
- name: "Claude Haiku 4.5"
16422
- });
16423
- }
16424
- if (copilot === "yes") {
16425
- availableModels.push({
16426
- model: "github-copilot/grok-code-fast-1",
16427
- name: "Grok Code Fast"
16428
- });
16429
- }
16430
- if (zaiPlan === "yes") {
16431
- availableModels.push({ model: "zai-coding-plan/glm-4.7", name: "GLM 4.7" });
16432
- }
16433
- if (antigravity === "yes") {
16434
- availableModels.push({
16435
- model: "google/antigravity-gemini-3.1-pro",
16436
- name: "Gemini 3.1 Pro"
16437
- });
16438
- availableModels.push({
16439
- model: "google/antigravity-gemini-3-flash",
16440
- name: "Gemini 3 Flash"
16441
- });
16442
- }
16443
- if (chutes === "yes" && availableChutesModels) {
16444
- for (const model of availableChutesModels) {
16445
- availableModels.push({ model: model.model, name: model.name });
16446
- }
16447
- }
16448
- availableModels.push({
16449
- model: "opencode/big-pickle",
16450
- name: "OpenCode Big Pickle"
16451
- });
16452
- if (availableModels.length === 0) {
16453
- printWarning("No models available. Please enable at least one provider.");
16454
- availableModels.push({
16455
- model: "opencode/big-pickle",
16456
- name: "OpenCode Big Pickle"
16457
- });
16458
- }
16459
- const manualAgentConfigs = {};
16460
- const agentNames = [
16461
- "orchestrator",
16462
- "oracle",
16463
- "designer",
16464
- "explorer",
16465
- "librarian",
16466
- "fixer"
16467
- ];
16468
- for (const agentName of agentNames) {
16469
- manualAgentConfigs[agentName] = await configureAgentManually(rl, agentName, availableModels);
16470
- }
16471
- const balancedSpend = await askYesNo(rl, "Do you have subscriptions or pay per API? If yes, we will distribute assignments evenly across selected providers so your subscriptions last longer.", "no");
16472
- console.log();
16473
- let skills = "no";
16474
- let customSkills = "no";
16475
- if (!modelsOnly) {
16476
- console.log(`${BOLD}Recommended Skills:${RESET}`);
16477
- for (const skill of RECOMMENDED_SKILLS) {
16478
- console.log(` ${SYMBOLS.bullet} ${BOLD}${skill.name}${RESET}: ${skill.description}`);
16479
- }
16480
- console.log();
16481
- skills = await askYesNo(rl, "Install recommended skills?", "yes");
16482
- console.log();
16483
- console.log(`${BOLD}Custom Skills:${RESET}`);
16484
- for (const skill of CUSTOM_SKILLS) {
16485
- console.log(` ${SYMBOLS.bullet} ${BOLD}${skill.name}${RESET}: ${skill.description}`);
16486
- }
16487
- console.log();
16488
- customSkills = await askYesNo(rl, "Install custom skills?", "yes");
16489
- console.log();
16490
- } else {
16491
- printInfo("Models-only mode: skipping plugin/auth setup and skills prompts.");
16492
- console.log();
16493
- }
16494
- return {
16495
- hasKimi: kimi === "yes",
16496
- hasOpenAI: openai === "yes",
16497
- hasAnthropic: anthropic === "yes",
16498
- hasCopilot: copilot === "yes",
16499
- hasZaiPlan: zaiPlan === "yes",
16500
- hasAntigravity: antigravity === "yes",
16501
- hasChutes: chutes === "yes",
16502
- hasOpencodeZen: true,
16503
- useOpenCodeFreeModels: useOpenCodeFree === "yes" && availableOpenCodeFreeModels !== undefined,
16504
- availableOpenCodeFreeModels,
16505
- availableChutesModels,
16506
- artificialAnalysisApiKey,
16507
- openRouterApiKey,
16508
- balanceProviderUsage: balancedSpend === "yes",
16509
- hasTmux: false,
16510
- installSkills: skills === "yes",
16511
- installCustomSkills: customSkills === "yes",
16512
- setupMode: "manual",
16513
- manualAgentConfigs,
16514
- modelsOnly
16515
- };
16516
- }
16517
- async function runInteractiveMode(detected, modelsOnly = false) {
16518
- const rl = readline.createInterface({
16519
- input: process.stdin,
16520
- output: process.stdout
16521
- });
16522
- try {
16523
- console.log();
16524
- console.log(`${BOLD}oh-my-opencode-slim Setup${RESET}`);
16525
- console.log("=".repeat(25));
16526
- console.log();
16527
- const setupMode = await askSetupMode(rl);
16528
- if (setupMode === "manual") {
16529
- const config2 = await runManualSetupMode(rl, detected, modelsOnly);
16530
- rl.close();
16531
- return config2;
16532
- }
16533
- const totalQuestions = 11;
16534
- const existingAaKey = getEnv("ARTIFICIAL_ANALYSIS_API_KEY");
16535
- const existingOpenRouterKey = getEnv("OPENROUTER_API_KEY");
16536
- console.log(`${BOLD}Question 1/${totalQuestions}:${RESET}`);
16537
- const artificialAnalysisApiKey = await askOptionalApiKey(rl, "Artificial Analysis API key for better ranking signals", existingAaKey);
16538
- if (existingAaKey && !artificialAnalysisApiKey) {
16539
- printInfo("Using existing ARTIFICIAL_ANALYSIS_API_KEY from environment.");
16540
- }
16541
- console.log();
16542
- console.log(`${BOLD}Question 2/${totalQuestions}:${RESET}`);
16543
- const openRouterApiKey = await askOptionalApiKey(rl, "OpenRouter API key for pricing/metadata signals", existingOpenRouterKey);
16544
- if (existingOpenRouterKey && !openRouterApiKey) {
16545
- printInfo("Using existing OPENROUTER_API_KEY from environment.");
16546
- }
16547
- console.log();
16548
- console.log(`${BOLD}Question 3/${totalQuestions}:${RESET}`);
16549
- const useOpenCodeFree = await askYesNo(rl, "Use Opencode Free models (opencode/*)?", "yes");
16550
- console.log();
16551
- let availableOpenCodeFreeModels;
16552
- let selectedOpenCodePrimaryModel;
16553
- let selectedOpenCodeSecondaryModel;
16554
- let availableChutesModels;
16555
- let selectedChutesPrimaryModel;
16556
- let selectedChutesSecondaryModel;
16557
- if (useOpenCodeFree === "yes") {
16558
- printInfo("Refreshing models with: opencode models --refresh --verbose");
16559
- const discovery = await discoverOpenCodeFreeModels();
16560
- if (discovery.models.length === 0) {
16561
- printWarning(discovery.error ?? "No OpenCode free models found. Continuing without OpenCode free-model assignment.");
16562
- } else {
16563
- availableOpenCodeFreeModels = discovery.models;
16564
- const recommendedPrimary = pickBestCodingOpenCodeModel(discovery.models)?.model ?? discovery.models[0]?.model;
16565
- if (recommendedPrimary) {
16566
- printInfo("This step configures only OpenCode Free primary/support models.");
16567
- console.log(`${BOLD}OpenCode Free Models:${RESET}`);
16568
- selectedOpenCodePrimaryModel = await askModelSelection(rl, discovery.models, recommendedPrimary, "Choose primary model for orchestrator/oracle");
16569
- }
16570
- if (selectedOpenCodePrimaryModel) {
16571
- const recommendedSecondary = pickSupportOpenCodeModel(discovery.models, selectedOpenCodePrimaryModel)?.model ?? selectedOpenCodePrimaryModel;
16572
- const openCodeSupportList = discovery.models.filter((model) => model.model !== selectedOpenCodePrimaryModel);
16573
- const openCodeSupportDefault = recommendedSecondary === selectedOpenCodePrimaryModel ? openCodeSupportList[0]?.model ?? recommendedSecondary : recommendedSecondary;
16574
- selectedOpenCodeSecondaryModel = await askModelSelection(rl, openCodeSupportList, openCodeSupportDefault, "Choose support model for explorer/librarian/fixer");
16575
- }
16576
- console.log();
16577
- }
16578
- }
16579
- console.log(`${BOLD}Question 4/${totalQuestions}:${RESET}`);
16580
- const kimi = await askYesNo(rl, "Enable Kimi provider?", detected.hasKimi ? "yes" : "no");
16581
- console.log();
16582
- console.log(`${BOLD}Question 5/${totalQuestions}:${RESET}`);
16583
- const openai = await askYesNo(rl, "Enable OpenAI provider?", detected.hasOpenAI ? "yes" : "no");
16584
- console.log();
16585
- console.log(`${BOLD}Question 6/${totalQuestions}:${RESET}`);
16586
- const anthropic = await askYesNo(rl, "Enable Anthropic provider?", detected.hasAnthropic ? "yes" : "no");
16587
- console.log();
16588
- console.log(`${BOLD}Question 7/${totalQuestions}:${RESET}`);
16589
- const copilot = await askYesNo(rl, "Enable GitHub Copilot provider?", detected.hasCopilot ? "yes" : "no");
16590
- console.log();
16591
- console.log(`${BOLD}Question 8/${totalQuestions}:${RESET}`);
16592
- const zaiPlan = await askYesNo(rl, "Enable ZAI Coding Plan provider?", detected.hasZaiPlan ? "yes" : "no");
16593
- console.log();
16594
- console.log(`${BOLD}Question 9/${totalQuestions}:${RESET}`);
16595
- const antigravity = await askYesNo(rl, "Enable Antigravity (Google) provider?", detected.hasAntigravity ? "yes" : "no");
16596
- console.log();
16597
- console.log(`${BOLD}Question 10/${totalQuestions}:${RESET}`);
16598
- const chutes = await askYesNo(rl, "Enable Chutes provider?", detected.hasChutes ? "yes" : "no");
16599
- console.log();
16600
- if (chutes === "yes") {
16601
- printInfo("Refreshing Chutes model list with: opencode models --refresh --verbose");
16602
- const discovery = await discoverProviderModels("chutes");
16603
- if (discovery.models.length === 0) {
16604
- printWarning(discovery.error ?? "No Chutes models found. Continuing without Chutes dynamic assignment.");
16605
- } else {
16606
- availableChutesModels = discovery.models;
16607
- const recommendedPrimary = pickBestCodingChutesModel(discovery.models)?.model ?? discovery.models[0]?.model;
16608
- if (recommendedPrimary) {
16609
- console.log(`${BOLD}Chutes Models:${RESET}`);
16610
- selectedChutesPrimaryModel = await askModelSelection(rl, discovery.models, recommendedPrimary, "Choose Chutes primary model for orchestrator/oracle/designer");
16611
- }
16612
- if (selectedChutesPrimaryModel) {
16613
- const recommendedSecondary = pickSupportChutesModel(discovery.models, selectedChutesPrimaryModel)?.model ?? selectedChutesPrimaryModel;
16614
- const chutesSupportList = discovery.models.filter((model) => model.model !== selectedChutesPrimaryModel);
16615
- const chutesSupportDefault = recommendedSecondary === selectedChutesPrimaryModel ? chutesSupportList[0]?.model ?? recommendedSecondary : recommendedSecondary;
16616
- selectedChutesSecondaryModel = await askModelSelection(rl, chutesSupportList, chutesSupportDefault, "Choose Chutes support model for explorer/librarian/fixer");
16617
- }
16618
- console.log();
16619
- }
16620
- }
16621
- console.log(`${BOLD}Question 11/${totalQuestions}:${RESET}`);
16622
- const balancedSpend = await askYesNo(rl, "Do you have subscriptions or pay per API? If yes, we will distribute assignments evenly across selected providers so your subscriptions last longer.", "no");
16623
- console.log();
16624
- let skills = "no";
16625
- let customSkills = "no";
16626
- if (!modelsOnly) {
16627
- console.log(`${BOLD}Recommended Skills:${RESET}`);
16628
- for (const skill of RECOMMENDED_SKILLS) {
16629
- console.log(` ${SYMBOLS.bullet} ${BOLD}${skill.name}${RESET}: ${skill.description}`);
16630
- }
16631
- console.log();
16632
- skills = await askYesNo(rl, "Install recommended skills?", "yes");
16633
- console.log();
16634
- console.log(`${BOLD}Custom Skills:${RESET}`);
16635
- for (const skill of CUSTOM_SKILLS) {
16636
- console.log(` ${SYMBOLS.bullet} ${BOLD}${skill.name}${RESET}: ${skill.description}`);
16637
- }
16638
- console.log();
16639
- customSkills = await askYesNo(rl, "Install custom skills?", "yes");
16640
- console.log();
16641
- } else {
16642
- printInfo("Models-only mode: skipping plugin/auth setup and skills prompts.");
16643
- console.log();
16644
- }
16645
- return {
16646
- hasKimi: kimi === "yes",
16647
- hasOpenAI: openai === "yes",
16648
- hasAnthropic: anthropic === "yes",
16649
- hasCopilot: copilot === "yes",
16650
- hasZaiPlan: zaiPlan === "yes",
16651
- hasAntigravity: antigravity === "yes",
16652
- hasChutes: chutes === "yes",
16653
- hasOpencodeZen: true,
16654
- useOpenCodeFreeModels: useOpenCodeFree === "yes" && selectedOpenCodePrimaryModel !== undefined,
16655
- selectedOpenCodePrimaryModel,
16656
- selectedOpenCodeSecondaryModel,
16657
- availableOpenCodeFreeModels,
16658
- selectedChutesPrimaryModel,
16659
- selectedChutesSecondaryModel,
16660
- availableChutesModels,
16661
- artificialAnalysisApiKey,
16662
- openRouterApiKey,
16663
- balanceProviderUsage: balancedSpend === "yes",
16664
- hasTmux: false,
16665
- installSkills: skills === "yes",
16666
- installCustomSkills: customSkills === "yes",
16667
- setupMode: "quick",
16668
- modelsOnly
16669
- };
16670
- } finally {
16671
- rl.close();
16672
- }
16673
- }
16674
14233
  async function runInstall(config2) {
16675
- const resolvedConfig = {
16676
- ...config2
16677
- };
16678
14234
  const detected = detectCurrentConfig();
16679
14235
  const isUpdate = detected.isInstalled;
16680
14236
  printHeader(isUpdate);
16681
- const hasAnyEnabledProvider = resolvedConfig.hasKimi || resolvedConfig.hasOpenAI || resolvedConfig.hasAnthropic || resolvedConfig.hasCopilot || resolvedConfig.hasZaiPlan || resolvedConfig.hasAntigravity || resolvedConfig.hasChutes || resolvedConfig.useOpenCodeFreeModels;
16682
- const modelsOnly = resolvedConfig.modelsOnly === true;
16683
- let totalSteps = modelsOnly ? 2 : 4;
16684
- if (resolvedConfig.useOpenCodeFreeModels)
16685
- totalSteps += 1;
16686
- if (!modelsOnly && resolvedConfig.hasAntigravity)
16687
- totalSteps += 2;
16688
- if (!modelsOnly && resolvedConfig.hasChutes)
16689
- totalSteps += 1;
16690
- if (hasAnyEnabledProvider)
14237
+ let totalSteps = 4;
14238
+ if (config2.installSkills)
16691
14239
  totalSteps += 1;
16692
- if (!modelsOnly && resolvedConfig.installSkills)
16693
- totalSteps += 1;
16694
- if (!modelsOnly && resolvedConfig.installCustomSkills)
14240
+ if (config2.installCustomSkills)
16695
14241
  totalSteps += 1;
16696
14242
  let step = 1;
16697
- if (modelsOnly) {
16698
- printInfo("Models-only mode: updating model assignments without reinstalling plugins/skills.");
16699
- }
16700
14243
  printStep(step++, totalSteps, "Checking OpenCode installation...");
16701
- if (resolvedConfig.dryRun) {
14244
+ if (config2.dryRun) {
16702
14245
  printInfo("Dry run mode - skipping OpenCode check");
16703
14246
  } else {
16704
14247
  const { ok } = await checkOpenCodeInstalled();
16705
14248
  if (!ok)
16706
14249
  return 1;
16707
14250
  }
16708
- if (resolvedConfig.useOpenCodeFreeModels && (resolvedConfig.availableOpenCodeFreeModels?.length ?? 0) === 0) {
16709
- printStep(step++, totalSteps, "Refreshing OpenCode free models (opencode/*)...");
16710
- const discovery = await discoverOpenCodeFreeModels();
16711
- if (discovery.models.length === 0) {
16712
- printWarning(discovery.error ?? "No OpenCode free models found. Continuing without dynamic OpenCode assignment.");
16713
- resolvedConfig.useOpenCodeFreeModels = false;
16714
- } else {
16715
- resolvedConfig.availableOpenCodeFreeModels = discovery.models;
16716
- const selectedPrimary = resolvedConfig.preferredOpenCodeModel && discovery.models.some((model) => model.model === resolvedConfig.preferredOpenCodeModel) ? resolvedConfig.preferredOpenCodeModel : resolvedConfig.selectedOpenCodePrimaryModel ?? pickBestCodingOpenCodeModel(discovery.models)?.model;
16717
- resolvedConfig.selectedOpenCodePrimaryModel = selectedPrimary ?? discovery.models[0]?.model;
16718
- resolvedConfig.selectedOpenCodeSecondaryModel = resolvedConfig.selectedOpenCodeSecondaryModel ?? pickSupportOpenCodeModel(discovery.models, resolvedConfig.selectedOpenCodePrimaryModel)?.model ?? resolvedConfig.selectedOpenCodePrimaryModel;
16719
- printSuccess(`OpenCode free models ready (${discovery.models.length} models found)`);
16720
- }
16721
- } else if (resolvedConfig.useOpenCodeFreeModels && (resolvedConfig.availableOpenCodeFreeModels?.length ?? 0) > 0) {
16722
- const availableModels = resolvedConfig.availableOpenCodeFreeModels ?? [];
16723
- resolvedConfig.selectedOpenCodePrimaryModel = resolvedConfig.selectedOpenCodePrimaryModel ?? pickBestCodingOpenCodeModel(availableModels)?.model;
16724
- resolvedConfig.selectedOpenCodeSecondaryModel = resolvedConfig.selectedOpenCodeSecondaryModel ?? pickSupportOpenCodeModel(availableModels, resolvedConfig.selectedOpenCodePrimaryModel)?.model ?? resolvedConfig.selectedOpenCodePrimaryModel;
16725
- printStep(step++, totalSteps, "Using previously refreshed OpenCode free model list...");
16726
- printSuccess(`OpenCode free models ready (${availableModels.length} models found)`);
16727
- }
16728
- if (resolvedConfig.hasChutes && (resolvedConfig.availableChutesModels?.length ?? 0) === 0) {
16729
- printStep(step++, totalSteps, "Refreshing Chutes models (chutes/*)...");
16730
- const discovery = await discoverProviderModels("chutes");
16731
- if (discovery.models.length === 0) {
16732
- printWarning(discovery.error ?? "No Chutes models found. Continuing with fallback Chutes mapping.");
16733
- } else {
16734
- resolvedConfig.availableChutesModels = discovery.models;
16735
- resolvedConfig.selectedChutesPrimaryModel = resolvedConfig.selectedChutesPrimaryModel ?? pickBestCodingChutesModel(discovery.models)?.model ?? discovery.models[0]?.model;
16736
- resolvedConfig.selectedChutesSecondaryModel = resolvedConfig.selectedChutesSecondaryModel ?? pickSupportChutesModel(discovery.models, resolvedConfig.selectedChutesPrimaryModel)?.model ?? resolvedConfig.selectedChutesPrimaryModel;
16737
- printSuccess(`Chutes models ready (${discovery.models.length} models found)`);
16738
- }
16739
- } else if (resolvedConfig.hasChutes && (resolvedConfig.availableChutesModels?.length ?? 0) > 0) {
16740
- const availableChutes = resolvedConfig.availableChutesModels ?? [];
16741
- resolvedConfig.selectedChutesPrimaryModel = resolvedConfig.selectedChutesPrimaryModel ?? pickBestCodingChutesModel(availableChutes)?.model;
16742
- resolvedConfig.selectedChutesSecondaryModel = resolvedConfig.selectedChutesSecondaryModel ?? pickSupportChutesModel(availableChutes, resolvedConfig.selectedChutesPrimaryModel)?.model ?? resolvedConfig.selectedChutesPrimaryModel;
16743
- printStep(step++, totalSteps, "Using previously refreshed Chutes model list...");
16744
- printSuccess(`Chutes models ready (${availableChutes.length} models found)`);
16745
- }
16746
- if (!modelsOnly) {
14251
+ {
16747
14252
  printStep(step++, totalSteps, "Adding oh-my-opencode-slim plugin...");
16748
- if (resolvedConfig.dryRun) {
14253
+ if (config2.dryRun) {
16749
14254
  printInfo("Dry run mode - skipping plugin installation");
16750
14255
  } else {
16751
14256
  const pluginResult = await addPluginToOpenCodeConfig();
@@ -16753,59 +14258,9 @@ async function runInstall(config2) {
16753
14258
  return 1;
16754
14259
  }
16755
14260
  }
16756
- if (!modelsOnly && resolvedConfig.hasAntigravity) {
16757
- printStep(step++, totalSteps, "Adding Antigravity plugin...");
16758
- if (resolvedConfig.dryRun) {
16759
- printInfo("Dry run mode - skipping Antigravity plugin");
16760
- } else {
16761
- const antigravityPluginResult = addAntigravityPlugin();
16762
- if (!handleStepResult(antigravityPluginResult, "Antigravity plugin added"))
16763
- return 1;
16764
- }
16765
- printStep(step++, totalSteps, "Configuring Google Provider...");
16766
- if (resolvedConfig.dryRun) {
16767
- printInfo("Dry run mode - skipping Google Provider setup");
16768
- } else {
16769
- const googleProviderResult = addGoogleProvider();
16770
- if (!handleStepResult(googleProviderResult, "Google Provider configured"))
16771
- return 1;
16772
- }
16773
- }
16774
- if (!modelsOnly && resolvedConfig.hasChutes) {
16775
- printStep(step++, totalSteps, "Enabling Chutes auth flow...");
16776
- if (resolvedConfig.dryRun) {
16777
- printInfo("Dry run mode - skipping Chutes auth flow");
16778
- } else {
16779
- const chutesProviderResult = addChutesProvider();
16780
- if (!handleStepResult(chutesProviderResult, "Chutes auth flow ready"))
16781
- return 1;
16782
- }
16783
- }
16784
- if (hasAnyEnabledProvider) {
16785
- printStep(step++, totalSteps, "Resolving dynamic model assignments...");
16786
- const catalogDiscovery = await discoverModelCatalog();
16787
- if (catalogDiscovery.models.length === 0) {
16788
- printWarning(catalogDiscovery.error ?? "Unable to discover model catalog. Falling back to static mappings.");
16789
- } else {
16790
- const { signals, warnings } = await fetchExternalModelSignals({
16791
- artificialAnalysisApiKey: resolvedConfig.artificialAnalysisApiKey,
16792
- openRouterApiKey: resolvedConfig.openRouterApiKey
16793
- });
16794
- for (const warning of warnings) {
16795
- printInfo(warning);
16796
- }
16797
- const dynamicPlan = buildDynamicModelPlan(catalogDiscovery.models, resolvedConfig, signals);
16798
- if (!dynamicPlan) {
16799
- printWarning("Dynamic planner found no suitable models. Using static mappings.");
16800
- } else {
16801
- resolvedConfig.dynamicModelPlan = dynamicPlan;
16802
- printSuccess(`Dynamic assignments ready (${Object.keys(dynamicPlan.agents).length} agents)`);
16803
- }
16804
- }
16805
- }
16806
- if (!modelsOnly) {
14261
+ {
16807
14262
  printStep(step++, totalSteps, "Disabling OpenCode default agents...");
16808
- if (resolvedConfig.dryRun) {
14263
+ if (config2.dryRun) {
16809
14264
  printInfo("Dry run mode - skipping agent disabling");
16810
14265
  } else {
16811
14266
  const agentResult = disableDefaultAgents();
@@ -16814,20 +14269,20 @@ async function runInstall(config2) {
16814
14269
  }
16815
14270
  }
16816
14271
  printStep(step++, totalSteps, "Writing oh-my-opencode-slim configuration...");
16817
- if (resolvedConfig.dryRun) {
16818
- const liteConfig = generateLiteConfig(resolvedConfig);
14272
+ if (config2.dryRun) {
14273
+ const liteConfig = generateLiteConfig(config2);
16819
14274
  printInfo("Dry run mode - configuration that would be written:");
16820
14275
  console.log(`
16821
14276
  ${JSON.stringify(liteConfig, null, 2)}
16822
14277
  `);
16823
14278
  } else {
16824
- const liteResult = writeLiteConfig(resolvedConfig);
14279
+ const liteResult = writeLiteConfig(config2);
16825
14280
  if (!handleStepResult(liteResult, "Config written"))
16826
14281
  return 1;
16827
14282
  }
16828
- if (!modelsOnly && resolvedConfig.installSkills) {
14283
+ if (config2.installSkills) {
16829
14284
  printStep(step++, totalSteps, "Installing recommended skills...");
16830
- if (resolvedConfig.dryRun) {
14285
+ if (config2.dryRun) {
16831
14286
  printInfo("Dry run mode - would install skills:");
16832
14287
  for (const skill of RECOMMENDED_SKILLS) {
16833
14288
  printInfo(` - ${skill.name}`);
@@ -16840,15 +14295,15 @@ ${JSON.stringify(liteConfig, null, 2)}
16840
14295
  printSuccess(`Installed: ${skill.name}`);
16841
14296
  skillsInstalled++;
16842
14297
  } else {
16843
- printWarning(`Failed to install: ${skill.name}`);
14298
+ printInfo(`Skipped: ${skill.name} (already installed)`);
16844
14299
  }
16845
14300
  }
16846
- printSuccess(`${skillsInstalled}/${RECOMMENDED_SKILLS.length} skills installed`);
14301
+ printSuccess(`${skillsInstalled}/${RECOMMENDED_SKILLS.length} skills processed`);
16847
14302
  }
16848
14303
  }
16849
- if (!modelsOnly && resolvedConfig.installCustomSkills) {
14304
+ if (config2.installCustomSkills) {
16850
14305
  printStep(step++, totalSteps, "Installing custom skills...");
16851
- if (resolvedConfig.dryRun) {
14306
+ if (config2.dryRun) {
16852
14307
  printInfo("Dry run mode - would install custom skills:");
16853
14308
  for (const skill of CUSTOM_SKILLS) {
16854
14309
  printInfo(` - ${skill.name}`);
@@ -16861,103 +14316,35 @@ ${JSON.stringify(liteConfig, null, 2)}
16861
14316
  printSuccess(`Installed: ${skill.name}`);
16862
14317
  customSkillsInstalled++;
16863
14318
  } else {
16864
- printWarning(`Failed to install: ${skill.name}`);
14319
+ printInfo(`Skipped: ${skill.name} (already installed)`);
16865
14320
  }
16866
14321
  }
16867
- printSuccess(`${customSkillsInstalled}/${CUSTOM_SKILLS.length} custom skills installed`);
14322
+ printSuccess(`${customSkillsInstalled}/${CUSTOM_SKILLS.length} custom skills processed`);
16868
14323
  }
16869
14324
  }
16870
14325
  console.log();
16871
- console.log(formatConfigSummary(resolvedConfig));
14326
+ console.log(formatConfigSummary());
16872
14327
  console.log();
16873
- printAgentModels(resolvedConfig);
16874
- if (!resolvedConfig.hasKimi && !resolvedConfig.hasOpenAI && !resolvedConfig.hasAnthropic && !resolvedConfig.hasCopilot && !resolvedConfig.hasZaiPlan && !resolvedConfig.hasAntigravity && !resolvedConfig.hasChutes) {
16875
- printWarning("No providers configured. Zen Big Pickle models will be used as fallback.");
16876
- }
16877
14328
  console.log(`${SYMBOLS.star} ${BOLD}${GREEN}${isUpdate ? "Configuration updated!" : "Installation complete!"}${RESET}`);
16878
14329
  console.log();
16879
14330
  console.log(`${BOLD}Next steps:${RESET}`);
16880
14331
  console.log();
16881
- let nextStep = 1;
16882
- if (resolvedConfig.hasKimi || resolvedConfig.hasOpenAI || resolvedConfig.hasAnthropic || resolvedConfig.hasCopilot || resolvedConfig.hasZaiPlan || resolvedConfig.hasAntigravity || resolvedConfig.hasChutes) {
16883
- console.log(` ${nextStep++}. Authenticate with your providers:`);
16884
- console.log(` ${BLUE}$ opencode auth login${RESET}`);
16885
- if (resolvedConfig.hasKimi) {
16886
- console.log();
16887
- console.log(` Then select ${BOLD}Kimi For Coding${RESET} provider.`);
16888
- }
16889
- if (resolvedConfig.hasAntigravity) {
16890
- console.log();
16891
- console.log(` Then select ${BOLD}google${RESET} provider.`);
16892
- }
16893
- if (resolvedConfig.hasAnthropic) {
16894
- console.log();
16895
- console.log(` Then select ${BOLD}anthropic${RESET} provider.`);
16896
- }
16897
- if (resolvedConfig.hasCopilot) {
16898
- console.log();
16899
- console.log(` Then select ${BOLD}github-copilot${RESET} provider.`);
16900
- }
16901
- if (resolvedConfig.hasZaiPlan) {
16902
- console.log();
16903
- console.log(` Then select ${BOLD}zai-coding-plan${RESET} provider.`);
16904
- }
16905
- if (resolvedConfig.hasChutes) {
16906
- console.log();
16907
- console.log(` Then select ${BOLD}chutes${RESET} provider.`);
16908
- }
16909
- console.log();
16910
- }
16911
- console.log(` ${nextStep++}. Start OpenCode:`);
14332
+ console.log(` 1. Start OpenCode:`);
16912
14333
  console.log(` ${BLUE}$ opencode${RESET}`);
16913
14334
  console.log();
14335
+ console.log(`${BOLD}Default configuration uses OpenAI models (gpt-5.4 / gpt-5-codex).${RESET}`);
14336
+ console.log(`${BOLD}For alternative providers (Kimi, GitHub Copilot, ZAI Coding Plan), see:${RESET}`);
14337
+ console.log(` ${BLUE}https://github.com/alvinunreal/oh-my-opencode-slim/blob/main/docs/provider-configurations.md${RESET}`);
14338
+ console.log();
16914
14339
  return 0;
16915
14340
  }
16916
14341
  async function install(args) {
16917
- if (!args.tui) {
16918
- const requiredArgs = [
16919
- "kimi",
16920
- "openai",
16921
- "anthropic",
16922
- "copilot",
16923
- "zaiPlan",
16924
- "antigravity",
16925
- "chutes",
16926
- "tmux"
16927
- ];
16928
- const errors3 = requiredArgs.filter((key) => {
16929
- const value = args[key];
16930
- return value === undefined || !["yes", "no"].includes(value);
16931
- });
16932
- if (errors3.length > 0) {
16933
- printHeader(false);
16934
- printError("Missing or invalid arguments:");
16935
- for (const key of errors3) {
16936
- const flagName = key === "zaiPlan" ? "zai-plan" : key;
16937
- console.log(` ${SYMBOLS.bullet} --${flagName}=<yes|no>`);
16938
- }
16939
- console.log();
16940
- printInfo("Usage: bunx oh-my-opencode-slim install --no-tui --kimi=<yes|no> --openai=<yes|no> --anthropic=<yes|no> --copilot=<yes|no> --zai-plan=<yes|no> --antigravity=<yes|no> --chutes=<yes|no> --balanced-spend=<yes|no> --tmux=<yes|no>");
16941
- console.log();
16942
- return 1;
16943
- }
16944
- const nonInteractiveConfig = argsToConfig(args);
16945
- return runInstall(nonInteractiveConfig);
16946
- }
16947
- const detected = detectCurrentConfig();
16948
- printHeader(detected.isInstalled);
16949
- printStep(1, 1, "Checking OpenCode installation...");
16950
- if (args.dryRun) {
16951
- printInfo("Dry run mode - skipping OpenCode check");
16952
- } else {
16953
- const { ok } = await checkOpenCodeInstalled();
16954
- if (!ok)
16955
- return 1;
16956
- }
16957
- console.log();
16958
- const config2 = await runInteractiveMode(detected, args.modelsOnly === true);
16959
- config2.dryRun = args.dryRun;
16960
- config2.modelsOnly = args.modelsOnly;
14342
+ const config2 = {
14343
+ hasTmux: args.tmux === "yes",
14344
+ installSkills: args.skills === "yes",
14345
+ installCustomSkills: args.skills === "yes",
14346
+ dryRun: args.dryRun
14347
+ };
16961
14348
  return runInstall(config2);
16962
14349
  }
16963
14350
 
@@ -16969,38 +14356,12 @@ function parseArgs(args) {
16969
14356
  for (const arg of args) {
16970
14357
  if (arg === "--no-tui") {
16971
14358
  result.tui = false;
16972
- } else if (arg.startsWith("--kimi=")) {
16973
- result.kimi = arg.split("=")[1];
16974
- } else if (arg.startsWith("--openai=")) {
16975
- result.openai = arg.split("=")[1];
16976
- } else if (arg.startsWith("--anthropic=")) {
16977
- result.anthropic = arg.split("=")[1];
16978
- } else if (arg.startsWith("--copilot=")) {
16979
- result.copilot = arg.split("=")[1];
16980
- } else if (arg.startsWith("--zai-plan=")) {
16981
- result.zaiPlan = arg.split("=")[1];
16982
- } else if (arg.startsWith("--antigravity=")) {
16983
- result.antigravity = arg.split("=")[1];
16984
- } else if (arg.startsWith("--chutes=")) {
16985
- result.chutes = arg.split("=")[1];
16986
14359
  } else if (arg.startsWith("--tmux=")) {
16987
14360
  result.tmux = arg.split("=")[1];
16988
14361
  } else if (arg.startsWith("--skills=")) {
16989
14362
  result.skills = arg.split("=")[1];
16990
- } else if (arg.startsWith("--opencode-free=")) {
16991
- result.opencodeFree = arg.split("=")[1];
16992
- } else if (arg.startsWith("--balanced-spend=")) {
16993
- result.balancedSpend = arg.split("=")[1];
16994
- } else if (arg.startsWith("--opencode-free-model=")) {
16995
- result.opencodeFreeModel = arg.split("=")[1];
16996
- } else if (arg.startsWith("--aa-key=")) {
16997
- result.aaKey = arg.slice("--aa-key=".length);
16998
- } else if (arg.startsWith("--openrouter-key=")) {
16999
- result.openrouterKey = arg.slice("--openrouter-key=".length);
17000
14363
  } else if (arg === "--dry-run") {
17001
14364
  result.dryRun = true;
17002
- } else if (arg === "--models-only") {
17003
- result.modelsOnly = true;
17004
14365
  } else if (arg === "-h" || arg === "--help") {
17005
14366
  printHelp();
17006
14367
  process.exit(0);
@@ -17013,42 +14374,27 @@ function printHelp() {
17013
14374
  oh-my-opencode-slim installer
17014
14375
 
17015
14376
  Usage: bunx oh-my-opencode-slim install [OPTIONS]
17016
- bunx oh-my-opencode-slim models [OPTIONS]
17017
14377
 
17018
14378
  Options:
17019
- --kimi=yes|no Kimi API access (yes/no)
17020
- --openai=yes|no OpenAI API access (yes/no)
17021
- --anthropic=yes|no Anthropic access (yes/no)
17022
- --copilot=yes|no GitHub Copilot access (yes/no)
17023
- --zai-plan=yes|no ZAI Coding Plan access (yes/no)
17024
- --antigravity=yes|no Antigravity/Google models (yes/no)
17025
- --chutes=yes|no Chutes models (yes/no)
17026
- --opencode-free=yes|no Use OpenCode free models (opencode/*)
17027
- --balanced-spend=yes|no Evenly spread usage across selected providers when score gaps are within tolerance
17028
- --opencode-free-model Preferred OpenCode model id or "auto"
17029
- --aa-key Artificial Analysis API key (optional)
17030
- --openrouter-key OpenRouter API key (optional)
17031
14379
  --tmux=yes|no Enable tmux integration (yes/no)
17032
14380
  --skills=yes|no Install recommended skills (yes/no)
17033
- --no-tui Non-interactive mode (requires all flags)
17034
- --dry-run Simulate install without writing files or requiring OpenCode
17035
- --models-only Update model assignments only (skip plugin/auth/skills)
14381
+ --no-tui Non-interactive mode
14382
+ --dry-run Simulate install without writing files
17036
14383
  -h, --help Show this help message
17037
14384
 
14385
+ The installer generates an OpenAI configuration by default.
14386
+ For alternative providers, see docs/provider-configurations.md.
14387
+
17038
14388
  Examples:
17039
14389
  bunx oh-my-opencode-slim install
17040
- bunx oh-my-opencode-slim models
17041
- bunx oh-my-opencode-slim install --no-tui --kimi=yes --openai=yes --anthropic=yes --copilot=no --zai-plan=no --antigravity=yes --chutes=no --opencode-free=yes --balanced-spend=yes --opencode-free-model=auto --aa-key=YOUR_AA_KEY --openrouter-key=YOUR_OR_KEY --tmux=no --skills=yes
14390
+ bunx oh-my-opencode-slim install --no-tui --tmux=no --skills=yes
17042
14391
  `);
17043
14392
  }
17044
14393
  async function main() {
17045
14394
  const args = process.argv.slice(2);
17046
- if (args.length === 0 || args[0] === "install" || args[0] === "models") {
17047
- const hasSubcommand = args[0] === "install" || args[0] === "models";
14395
+ if (args.length === 0 || args[0] === "install") {
14396
+ const hasSubcommand = args[0] === "install";
17048
14397
  const installArgs = parseArgs(args.slice(hasSubcommand ? 1 : 0));
17049
- if (args[0] === "models") {
17050
- installArgs.modelsOnly = true;
17051
- }
17052
14398
  const exitCode = await install(installArgs);
17053
14399
  process.exit(exitCode);
17054
14400
  } else if (args[0] === "-h" || args[0] === "--help") {