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