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