oh-my-opencode-slim 0.7.0 → 0.8.1
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 +9 -1
- package/dist/agents/index.d.ts +1 -1
- package/dist/agents/orchestrator.d.ts +1 -1
- package/dist/background/background-manager.d.ts +42 -0
- package/dist/background/tmux-session-manager.d.ts +5 -0
- package/dist/cli/config-manager.d.ts +3 -0
- package/dist/cli/dynamic-model-selection.d.ts +14 -2
- package/dist/cli/external-rankings.d.ts +8 -0
- package/dist/cli/index.js +1897 -272
- package/dist/cli/model-key-normalization.d.ts +1 -0
- package/dist/cli/opencode-models.d.ts +4 -0
- package/dist/cli/paths.d.ts +2 -0
- package/dist/cli/precedence-resolver.d.ts +16 -0
- package/dist/cli/providers.d.ts +1 -1
- 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/skills.d.ts +17 -0
- package/dist/cli/system.d.ts +2 -0
- package/dist/cli/types.d.ts +45 -1
- package/dist/config/constants.d.ts +1 -0
- package/dist/config/schema.d.ts +94 -2
- package/dist/hooks/autopilot/index.d.ts +43 -0
- package/dist/hooks/delegate-task-retry/guidance.d.ts +2 -0
- package/dist/hooks/delegate-task-retry/hook.d.ts +8 -0
- package/dist/hooks/delegate-task-retry/index.d.ts +4 -0
- package/dist/hooks/delegate-task-retry/patterns.d.ts +11 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/json-error-recovery/hook.d.ts +18 -0
- package/dist/hooks/json-error-recovery/index.d.ts +1 -0
- package/dist/index.js +356 -16
- 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
|
@@ -80,11 +80,11 @@ function pickSupportChutesModel(models, primaryModel) {
|
|
|
80
80
|
}
|
|
81
81
|
// src/cli/config-io.ts
|
|
82
82
|
import {
|
|
83
|
-
copyFileSync,
|
|
84
|
-
existsSync as
|
|
83
|
+
copyFileSync as copyFileSync2,
|
|
84
|
+
existsSync as existsSync3,
|
|
85
85
|
readFileSync,
|
|
86
86
|
renameSync,
|
|
87
|
-
statSync,
|
|
87
|
+
statSync as statSync2,
|
|
88
88
|
writeFileSync
|
|
89
89
|
} from "fs";
|
|
90
90
|
|
|
@@ -13666,6 +13666,34 @@ function date4(params) {
|
|
|
13666
13666
|
// node_modules/zod/v4/classic/external.js
|
|
13667
13667
|
config(en_default());
|
|
13668
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();
|
|
13669
13697
|
var AgentModelChainSchema = exports_external.array(exports_external.string()).min(1);
|
|
13670
13698
|
var FallbackChainsSchema = exports_external.object({
|
|
13671
13699
|
orchestrator: AgentModelChainSchema.optional(),
|
|
@@ -13674,7 +13702,7 @@ var FallbackChainsSchema = exports_external.object({
|
|
|
13674
13702
|
explorer: AgentModelChainSchema.optional(),
|
|
13675
13703
|
librarian: AgentModelChainSchema.optional(),
|
|
13676
13704
|
fixer: AgentModelChainSchema.optional()
|
|
13677
|
-
}).
|
|
13705
|
+
}).catchall(AgentModelChainSchema);
|
|
13678
13706
|
var AgentOverrideConfigSchema = exports_external.object({
|
|
13679
13707
|
model: exports_external.string().optional(),
|
|
13680
13708
|
temperature: exports_external.number().min(0).max(2).optional(),
|
|
@@ -13701,11 +13729,14 @@ var BackgroundTaskConfigSchema = exports_external.object({
|
|
|
13701
13729
|
});
|
|
13702
13730
|
var FailoverConfigSchema = exports_external.object({
|
|
13703
13731
|
enabled: exports_external.boolean().default(true),
|
|
13704
|
-
timeoutMs: exports_external.number().min(
|
|
13732
|
+
timeoutMs: exports_external.number().min(0).default(15000),
|
|
13705
13733
|
chains: FallbackChainsSchema.default({})
|
|
13706
13734
|
});
|
|
13707
13735
|
var PluginConfigSchema = exports_external.object({
|
|
13708
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(),
|
|
13709
13740
|
presets: exports_external.record(exports_external.string(), PresetSchema).optional(),
|
|
13710
13741
|
agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
|
|
13711
13742
|
disabled_mcps: exports_external.array(exports_external.string()).optional(),
|
|
@@ -13725,6 +13756,67 @@ var DEFAULT_AGENT_MCPS = {
|
|
|
13725
13756
|
|
|
13726
13757
|
// src/cli/skills.ts
|
|
13727
13758
|
import { spawnSync } from "child_process";
|
|
13759
|
+
|
|
13760
|
+
// src/cli/custom-skills.ts
|
|
13761
|
+
import {
|
|
13762
|
+
copyFileSync,
|
|
13763
|
+
existsSync as existsSync2,
|
|
13764
|
+
mkdirSync as mkdirSync2,
|
|
13765
|
+
readdirSync,
|
|
13766
|
+
statSync
|
|
13767
|
+
} from "fs";
|
|
13768
|
+
import { homedir as homedir2 } from "os";
|
|
13769
|
+
import { dirname, join as join2 } from "path";
|
|
13770
|
+
import { fileURLToPath } from "url";
|
|
13771
|
+
var CUSTOM_SKILLS = [
|
|
13772
|
+
{
|
|
13773
|
+
name: "cartography",
|
|
13774
|
+
description: "Repository understanding and hierarchical codemap generation",
|
|
13775
|
+
allowedAgents: ["orchestrator", "explorer"],
|
|
13776
|
+
sourcePath: "src/skills/cartography"
|
|
13777
|
+
}
|
|
13778
|
+
];
|
|
13779
|
+
function getCustomSkillsDir() {
|
|
13780
|
+
return join2(homedir2(), ".config", "opencode", "skills");
|
|
13781
|
+
}
|
|
13782
|
+
function copyDirRecursive(src, dest) {
|
|
13783
|
+
if (!existsSync2(dest)) {
|
|
13784
|
+
mkdirSync2(dest, { recursive: true });
|
|
13785
|
+
}
|
|
13786
|
+
const entries = readdirSync(src);
|
|
13787
|
+
for (const entry of entries) {
|
|
13788
|
+
const srcPath = join2(src, entry);
|
|
13789
|
+
const destPath = join2(dest, entry);
|
|
13790
|
+
const stat = statSync(srcPath);
|
|
13791
|
+
if (stat.isDirectory()) {
|
|
13792
|
+
copyDirRecursive(srcPath, destPath);
|
|
13793
|
+
} else {
|
|
13794
|
+
const destDir = dirname(destPath);
|
|
13795
|
+
if (!existsSync2(destDir)) {
|
|
13796
|
+
mkdirSync2(destDir, { recursive: true });
|
|
13797
|
+
}
|
|
13798
|
+
copyFileSync(srcPath, destPath);
|
|
13799
|
+
}
|
|
13800
|
+
}
|
|
13801
|
+
}
|
|
13802
|
+
function installCustomSkill(skill) {
|
|
13803
|
+
try {
|
|
13804
|
+
const packageRoot = fileURLToPath(new URL("../..", import.meta.url));
|
|
13805
|
+
const sourcePath = join2(packageRoot, skill.sourcePath);
|
|
13806
|
+
const targetPath = join2(getCustomSkillsDir(), skill.name);
|
|
13807
|
+
if (!existsSync2(sourcePath)) {
|
|
13808
|
+
console.error(`Custom skill source not found: ${sourcePath}`);
|
|
13809
|
+
return false;
|
|
13810
|
+
}
|
|
13811
|
+
copyDirRecursive(sourcePath, targetPath);
|
|
13812
|
+
return true;
|
|
13813
|
+
} catch (error48) {
|
|
13814
|
+
console.error(`Failed to install custom skill: ${skill.name}`, error48);
|
|
13815
|
+
return false;
|
|
13816
|
+
}
|
|
13817
|
+
}
|
|
13818
|
+
|
|
13819
|
+
// src/cli/skills.ts
|
|
13728
13820
|
var RECOMMENDED_SKILLS = [
|
|
13729
13821
|
{
|
|
13730
13822
|
name: "simplify",
|
|
@@ -13832,7 +13924,7 @@ var MODEL_MAPPINGS = {
|
|
|
13832
13924
|
},
|
|
13833
13925
|
antigravity: {
|
|
13834
13926
|
orchestrator: { model: "google/antigravity-gemini-3-flash" },
|
|
13835
|
-
oracle: { model: "google/antigravity-gemini-3-pro" },
|
|
13927
|
+
oracle: { model: "google/antigravity-gemini-3.1-pro" },
|
|
13836
13928
|
librarian: {
|
|
13837
13929
|
model: "google/antigravity-gemini-3-flash",
|
|
13838
13930
|
variant: "low"
|
|
@@ -13942,8 +14034,45 @@ function generateAntigravityMixedPreset(config2, existingPreset) {
|
|
|
13942
14034
|
function generateLiteConfig(installConfig) {
|
|
13943
14035
|
const config2 = {
|
|
13944
14036
|
preset: "zen-free",
|
|
13945
|
-
presets: {}
|
|
14037
|
+
presets: {},
|
|
14038
|
+
balanceProviderUsage: installConfig.balanceProviderUsage ?? false
|
|
13946
14039
|
};
|
|
14040
|
+
if (installConfig.setupMode === "manual" && installConfig.manualAgentConfigs) {
|
|
14041
|
+
config2.preset = "manual";
|
|
14042
|
+
const manualPreset = {};
|
|
14043
|
+
const chains = {};
|
|
14044
|
+
for (const agentName of AGENT_NAMES) {
|
|
14045
|
+
const manualConfig = installConfig.manualAgentConfigs[agentName];
|
|
14046
|
+
if (manualConfig) {
|
|
14047
|
+
manualPreset[agentName] = {
|
|
14048
|
+
model: manualConfig.primary,
|
|
14049
|
+
skills: agentName === "orchestrator" ? ["*"] : RECOMMENDED_SKILLS.filter((s) => s.allowedAgents.includes("*") || s.allowedAgents.includes(agentName)).map((s) => s.skillName),
|
|
14050
|
+
mcps: DEFAULT_AGENT_MCPS[agentName] ?? []
|
|
14051
|
+
};
|
|
14052
|
+
const fallbackChain = [
|
|
14053
|
+
manualConfig.primary,
|
|
14054
|
+
manualConfig.fallback1,
|
|
14055
|
+
manualConfig.fallback2,
|
|
14056
|
+
manualConfig.fallback3
|
|
14057
|
+
].filter((m, i, arr) => m && arr.indexOf(m) === i);
|
|
14058
|
+
chains[agentName] = fallbackChain;
|
|
14059
|
+
}
|
|
14060
|
+
}
|
|
14061
|
+
config2.presets.manual = manualPreset;
|
|
14062
|
+
config2.fallback = {
|
|
14063
|
+
enabled: true,
|
|
14064
|
+
timeoutMs: 15000,
|
|
14065
|
+
chains
|
|
14066
|
+
};
|
|
14067
|
+
if (installConfig.hasTmux) {
|
|
14068
|
+
config2.tmux = {
|
|
14069
|
+
enabled: true,
|
|
14070
|
+
layout: "main-vertical",
|
|
14071
|
+
main_pane_size: 60
|
|
14072
|
+
};
|
|
14073
|
+
}
|
|
14074
|
+
return config2;
|
|
14075
|
+
}
|
|
13947
14076
|
let activePreset = "zen-free";
|
|
13948
14077
|
if (installConfig.hasAntigravity && installConfig.hasKimi && installConfig.hasOpenAI) {
|
|
13949
14078
|
activePreset = "antigravity-mixed-both";
|
|
@@ -14023,7 +14152,7 @@ function generateLiteConfig(installConfig) {
|
|
|
14023
14152
|
const applyChutesAssignments = (presetAgents) => {
|
|
14024
14153
|
if (!installConfig.hasChutes)
|
|
14025
14154
|
return;
|
|
14026
|
-
const hasExternalProviders = installConfig.hasKimi || installConfig.hasOpenAI || installConfig.hasAntigravity;
|
|
14155
|
+
const hasExternalProviders = installConfig.hasKimi || installConfig.hasOpenAI || installConfig.hasAnthropic || installConfig.hasCopilot || installConfig.hasZaiPlan || installConfig.hasAntigravity;
|
|
14027
14156
|
if (hasExternalProviders && activePreset !== "chutes")
|
|
14028
14157
|
return;
|
|
14029
14158
|
const primaryModel = installConfig.selectedChutesPrimaryModel;
|
|
@@ -14135,9 +14264,9 @@ function stripJsonComments(json2) {
|
|
|
14135
14264
|
}
|
|
14136
14265
|
function parseConfigFile(path) {
|
|
14137
14266
|
try {
|
|
14138
|
-
if (!
|
|
14267
|
+
if (!existsSync3(path))
|
|
14139
14268
|
return { config: null };
|
|
14140
|
-
const stat =
|
|
14269
|
+
const stat = statSync2(path);
|
|
14141
14270
|
if (stat.size === 0)
|
|
14142
14271
|
return { config: null };
|
|
14143
14272
|
const content = readFileSync(path, "utf-8");
|
|
@@ -14166,8 +14295,8 @@ function writeConfig(configPath, config2) {
|
|
|
14166
14295
|
const bakPath = `${configPath}.bak`;
|
|
14167
14296
|
const content = `${JSON.stringify(config2, null, 2)}
|
|
14168
14297
|
`;
|
|
14169
|
-
if (
|
|
14170
|
-
|
|
14298
|
+
if (existsSync3(configPath)) {
|
|
14299
|
+
copyFileSync2(configPath, bakPath);
|
|
14171
14300
|
}
|
|
14172
14301
|
writeFileSync(tmpPath, content);
|
|
14173
14302
|
renameSync(tmpPath, configPath);
|
|
@@ -14216,8 +14345,8 @@ function writeLiteConfig(installConfig) {
|
|
|
14216
14345
|
const bakPath = `${configPath}.bak`;
|
|
14217
14346
|
const content = `${JSON.stringify(config2, null, 2)}
|
|
14218
14347
|
`;
|
|
14219
|
-
if (
|
|
14220
|
-
|
|
14348
|
+
if (existsSync3(configPath)) {
|
|
14349
|
+
copyFileSync2(configPath, bakPath);
|
|
14221
14350
|
}
|
|
14222
14351
|
writeFileSync(tmpPath, content);
|
|
14223
14352
|
renameSync(tmpPath, configPath);
|
|
@@ -14300,8 +14429,8 @@ function addGoogleProvider() {
|
|
|
14300
14429
|
const providers = config2.provider ?? {};
|
|
14301
14430
|
providers.google = {
|
|
14302
14431
|
models: {
|
|
14303
|
-
"antigravity-gemini-3-pro": {
|
|
14304
|
-
name: "Gemini 3 Pro (Antigravity)",
|
|
14432
|
+
"antigravity-gemini-3.1-pro": {
|
|
14433
|
+
name: "Gemini 3.1 Pro (Antigravity)",
|
|
14305
14434
|
limit: { context: 1048576, output: 65535 },
|
|
14306
14435
|
modalities: { input: ["text", "image", "pdf"], output: ["text"] },
|
|
14307
14436
|
variants: {
|
|
@@ -14358,8 +14487,8 @@ function addGoogleProvider() {
|
|
|
14358
14487
|
limit: { context: 1048576, output: 65536 },
|
|
14359
14488
|
modalities: { input: ["text", "image", "pdf"], output: ["text"] }
|
|
14360
14489
|
},
|
|
14361
|
-
"gemini-3-pro-preview": {
|
|
14362
|
-
name: "Gemini 3 Pro Preview (Gemini CLI)",
|
|
14490
|
+
"gemini-3.1-pro-preview": {
|
|
14491
|
+
name: "Gemini 3.1 Pro Preview (Gemini CLI)",
|
|
14363
14492
|
limit: { context: 1048576, output: 65535 },
|
|
14364
14493
|
modalities: { input: ["text", "image", "pdf"], output: ["text"] }
|
|
14365
14494
|
}
|
|
@@ -14379,7 +14508,7 @@ function addGoogleProvider() {
|
|
|
14379
14508
|
function addChutesProvider() {
|
|
14380
14509
|
const configPath = getExistingConfigPath();
|
|
14381
14510
|
try {
|
|
14382
|
-
const {
|
|
14511
|
+
const { error: error48 } = parseConfig(configPath);
|
|
14383
14512
|
if (error48) {
|
|
14384
14513
|
return {
|
|
14385
14514
|
success: false,
|
|
@@ -14387,24 +14516,12 @@ function addChutesProvider() {
|
|
|
14387
14516
|
error: `Failed to parse config: ${error48}`
|
|
14388
14517
|
};
|
|
14389
14518
|
}
|
|
14390
|
-
const config2 = parsedConfig ?? {};
|
|
14391
|
-
const providers = config2.provider ?? {};
|
|
14392
|
-
providers.chutes = {
|
|
14393
|
-
npm: "@ai-sdk/openai-compatible",
|
|
14394
|
-
name: "Chutes",
|
|
14395
|
-
options: {
|
|
14396
|
-
baseURL: "https://llm.chutes.ai/v1",
|
|
14397
|
-
apiKey: "{env:CHUTES_API_KEY}"
|
|
14398
|
-
}
|
|
14399
|
-
};
|
|
14400
|
-
config2.provider = providers;
|
|
14401
|
-
writeConfig(configPath, config2);
|
|
14402
14519
|
return { success: true, configPath };
|
|
14403
14520
|
} catch (err) {
|
|
14404
14521
|
return {
|
|
14405
14522
|
success: false,
|
|
14406
14523
|
configPath,
|
|
14407
|
-
error: `Failed to
|
|
14524
|
+
error: `Failed to validate chutes provider config: ${err}`
|
|
14408
14525
|
};
|
|
14409
14526
|
}
|
|
14410
14527
|
}
|
|
@@ -14462,6 +14579,317 @@ function detectCurrentConfig() {
|
|
|
14462
14579
|
}
|
|
14463
14580
|
return result;
|
|
14464
14581
|
}
|
|
14582
|
+
// src/cli/model-key-normalization.ts
|
|
14583
|
+
function cleanupAlias(input, preserveSlash) {
|
|
14584
|
+
let value = input.toLowerCase().trim();
|
|
14585
|
+
value = value.replace(/\bfp[a-z0-9.-]*\b/g, " ");
|
|
14586
|
+
value = value.replace(/\btee\b/g, " ");
|
|
14587
|
+
if (preserveSlash) {
|
|
14588
|
+
value = value.replace(/[_\s]+/g, "-");
|
|
14589
|
+
value = value.replace(/-+/g, "-");
|
|
14590
|
+
value = value.replace(/\/+/g, "/");
|
|
14591
|
+
value = value.replace(/\/-+/g, "/");
|
|
14592
|
+
value = value.replace(/-+\//g, "/");
|
|
14593
|
+
value = value.replace(/^\/+|\/+$/g, "");
|
|
14594
|
+
value = value.replace(/^-+|-+$/g, "");
|
|
14595
|
+
return value;
|
|
14596
|
+
}
|
|
14597
|
+
value = value.replace(/[/_\s]+/g, "-");
|
|
14598
|
+
value = value.replace(/-+/g, "-");
|
|
14599
|
+
value = value.replace(/^-+|-+$/g, "");
|
|
14600
|
+
return value;
|
|
14601
|
+
}
|
|
14602
|
+
function addDerivedAliases(seed, aliases) {
|
|
14603
|
+
const slashAlias = cleanupAlias(seed, true);
|
|
14604
|
+
const flatAlias = cleanupAlias(seed, false);
|
|
14605
|
+
if (slashAlias)
|
|
14606
|
+
aliases.add(slashAlias);
|
|
14607
|
+
if (flatAlias)
|
|
14608
|
+
aliases.add(flatAlias);
|
|
14609
|
+
if (slashAlias) {
|
|
14610
|
+
aliases.add(slashAlias.replace(/-(free|flash)$/i, ""));
|
|
14611
|
+
}
|
|
14612
|
+
if (flatAlias) {
|
|
14613
|
+
aliases.add(flatAlias.replace(/-(free|flash)$/i, ""));
|
|
14614
|
+
}
|
|
14615
|
+
if (slashAlias.includes("/")) {
|
|
14616
|
+
aliases.add(cleanupAlias(slashAlias.replace(/\//g, " "), false));
|
|
14617
|
+
aliases.add(cleanupAlias(slashAlias.replace(/\//g, "-"), false));
|
|
14618
|
+
const lastPart = slashAlias.split("/").at(-1);
|
|
14619
|
+
if (lastPart) {
|
|
14620
|
+
addDerivedAliases(lastPart, aliases);
|
|
14621
|
+
}
|
|
14622
|
+
}
|
|
14623
|
+
}
|
|
14624
|
+
function buildModelKeyAliases(input) {
|
|
14625
|
+
const normalized = input.trim().toLowerCase();
|
|
14626
|
+
if (!normalized)
|
|
14627
|
+
return [];
|
|
14628
|
+
const aliases = new Set;
|
|
14629
|
+
const slashIndex = normalized.indexOf("/");
|
|
14630
|
+
const afterProvider = slashIndex >= 0 ? normalized.slice(slashIndex + 1) : normalized;
|
|
14631
|
+
addDerivedAliases(normalized, aliases);
|
|
14632
|
+
addDerivedAliases(afterProvider, aliases);
|
|
14633
|
+
return [...aliases].filter((alias) => alias.length > 0);
|
|
14634
|
+
}
|
|
14635
|
+
|
|
14636
|
+
// src/cli/precedence-resolver.ts
|
|
14637
|
+
function dedupe(models) {
|
|
14638
|
+
const seen = new Set;
|
|
14639
|
+
const result = [];
|
|
14640
|
+
for (const model of models) {
|
|
14641
|
+
if (!model || seen.has(model))
|
|
14642
|
+
continue;
|
|
14643
|
+
seen.add(model);
|
|
14644
|
+
result.push(model);
|
|
14645
|
+
}
|
|
14646
|
+
return result;
|
|
14647
|
+
}
|
|
14648
|
+
function buildLayerOrder(input) {
|
|
14649
|
+
return [
|
|
14650
|
+
{
|
|
14651
|
+
layer: "opencode-direct-override",
|
|
14652
|
+
models: input.openCodeDirectOverride ? [input.openCodeDirectOverride] : []
|
|
14653
|
+
},
|
|
14654
|
+
{
|
|
14655
|
+
layer: "manual-user-plan",
|
|
14656
|
+
models: input.manualUserPlan ?? []
|
|
14657
|
+
},
|
|
14658
|
+
{
|
|
14659
|
+
layer: "pinned-model",
|
|
14660
|
+
models: input.pinnedModel ? [input.pinnedModel] : []
|
|
14661
|
+
},
|
|
14662
|
+
{
|
|
14663
|
+
layer: "dynamic-recommendation",
|
|
14664
|
+
models: input.dynamicRecommendation ?? []
|
|
14665
|
+
},
|
|
14666
|
+
{
|
|
14667
|
+
layer: "provider-fallback-policy",
|
|
14668
|
+
models: input.providerFallbackPolicy ?? []
|
|
14669
|
+
},
|
|
14670
|
+
{
|
|
14671
|
+
layer: "system-default",
|
|
14672
|
+
models: input.systemDefault
|
|
14673
|
+
}
|
|
14674
|
+
];
|
|
14675
|
+
}
|
|
14676
|
+
function resolveAgentWithPrecedence(input) {
|
|
14677
|
+
const ordered = buildLayerOrder(input);
|
|
14678
|
+
const firstWinningIndex = ordered.findIndex((layer) => layer.models.length > 0);
|
|
14679
|
+
const winnerIndex = firstWinningIndex >= 0 ? firstWinningIndex : ordered.length - 1;
|
|
14680
|
+
const winnerLayer = ordered[winnerIndex];
|
|
14681
|
+
const chain = dedupe(ordered.slice(winnerIndex).flatMap((layer) => layer.models).concat(input.systemDefault));
|
|
14682
|
+
const model = chain[0] ?? input.systemDefault[0] ?? "opencode/big-pickle";
|
|
14683
|
+
return {
|
|
14684
|
+
model,
|
|
14685
|
+
chain,
|
|
14686
|
+
provenance: {
|
|
14687
|
+
winnerLayer: winnerLayer?.layer ?? "system-default",
|
|
14688
|
+
winnerModel: model
|
|
14689
|
+
}
|
|
14690
|
+
};
|
|
14691
|
+
}
|
|
14692
|
+
|
|
14693
|
+
// src/cli/scoring-v2/features.ts
|
|
14694
|
+
function modelLookupKeys(model) {
|
|
14695
|
+
return buildModelKeyAliases(model.model);
|
|
14696
|
+
}
|
|
14697
|
+
function findSignal(model, externalSignals) {
|
|
14698
|
+
if (!externalSignals)
|
|
14699
|
+
return;
|
|
14700
|
+
return modelLookupKeys(model).map((key) => externalSignals[key]).find((item) => item !== undefined);
|
|
14701
|
+
}
|
|
14702
|
+
function statusValue(status) {
|
|
14703
|
+
if (status === "active")
|
|
14704
|
+
return 1;
|
|
14705
|
+
if (status === "beta")
|
|
14706
|
+
return 0.4;
|
|
14707
|
+
if (status === "alpha")
|
|
14708
|
+
return -0.25;
|
|
14709
|
+
return -1;
|
|
14710
|
+
}
|
|
14711
|
+
function capability(value) {
|
|
14712
|
+
return value ? 1 : 0;
|
|
14713
|
+
}
|
|
14714
|
+
function blendedPrice(signal) {
|
|
14715
|
+
if (!signal)
|
|
14716
|
+
return 0;
|
|
14717
|
+
if (signal.inputPricePer1M !== undefined && signal.outputPricePer1M !== undefined) {
|
|
14718
|
+
return signal.inputPricePer1M * 0.75 + signal.outputPricePer1M * 0.25;
|
|
14719
|
+
}
|
|
14720
|
+
return signal.inputPricePer1M ?? signal.outputPricePer1M ?? 0;
|
|
14721
|
+
}
|
|
14722
|
+
function kimiVersionBonus(agent, model) {
|
|
14723
|
+
const lowered = `${model.model} ${model.name}`.toLowerCase();
|
|
14724
|
+
const isChutes = model.providerID === "chutes";
|
|
14725
|
+
const isQwen3 = isChutes && /qwen3/.test(lowered);
|
|
14726
|
+
const isKimiK25 = /kimi-k2\.5|k2\.5/.test(lowered);
|
|
14727
|
+
const isMinimaxM21 = isChutes && /minimax[-_ ]?m2\.1/.test(lowered);
|
|
14728
|
+
const qwenPenalty = {
|
|
14729
|
+
orchestrator: -6,
|
|
14730
|
+
oracle: -6,
|
|
14731
|
+
designer: -8,
|
|
14732
|
+
explorer: -6,
|
|
14733
|
+
librarian: -12,
|
|
14734
|
+
fixer: -12
|
|
14735
|
+
};
|
|
14736
|
+
const kimiBonus = {
|
|
14737
|
+
orchestrator: 1,
|
|
14738
|
+
oracle: 1,
|
|
14739
|
+
designer: 3,
|
|
14740
|
+
explorer: 2,
|
|
14741
|
+
librarian: 2,
|
|
14742
|
+
fixer: 3
|
|
14743
|
+
};
|
|
14744
|
+
const minimaxBonus = {
|
|
14745
|
+
orchestrator: 1,
|
|
14746
|
+
oracle: 1,
|
|
14747
|
+
designer: 2,
|
|
14748
|
+
explorer: 4,
|
|
14749
|
+
librarian: 4,
|
|
14750
|
+
fixer: 4
|
|
14751
|
+
};
|
|
14752
|
+
if (isQwen3)
|
|
14753
|
+
return qwenPenalty[agent];
|
|
14754
|
+
if (isKimiK25)
|
|
14755
|
+
return kimiBonus[agent];
|
|
14756
|
+
if (isMinimaxM21)
|
|
14757
|
+
return minimaxBonus[agent];
|
|
14758
|
+
return 0;
|
|
14759
|
+
}
|
|
14760
|
+
function extractFeatureVector(model, agent, externalSignals) {
|
|
14761
|
+
const signal = findSignal(model, externalSignals);
|
|
14762
|
+
const latency = signal?.latencySeconds ?? 0;
|
|
14763
|
+
const normalizedContext = Math.min(model.contextLimit, 1e6) / 1e5;
|
|
14764
|
+
const normalizedOutput = Math.min(model.outputLimit, 300000) / 30000;
|
|
14765
|
+
const designerOutputScore = model.outputLimit < 64000 ? -1 : 0;
|
|
14766
|
+
const versionBonus = kimiVersionBonus(agent, model);
|
|
14767
|
+
const quality = (signal?.qualityScore ?? 0) / 100;
|
|
14768
|
+
const coding = (signal?.codingScore ?? 0) / 100;
|
|
14769
|
+
const pricePenalty = Math.min(blendedPrice(signal), 50) / 10;
|
|
14770
|
+
const explorerLatencyMultiplier = agent === "explorer" ? 1.4 : 1;
|
|
14771
|
+
return {
|
|
14772
|
+
status: statusValue(model.status),
|
|
14773
|
+
context: normalizedContext,
|
|
14774
|
+
output: agent === "designer" ? designerOutputScore : normalizedOutput,
|
|
14775
|
+
versionBonus,
|
|
14776
|
+
reasoning: capability(model.reasoning),
|
|
14777
|
+
toolcall: capability(model.toolcall),
|
|
14778
|
+
attachment: capability(model.attachment),
|
|
14779
|
+
quality,
|
|
14780
|
+
coding,
|
|
14781
|
+
latencyPenalty: Math.min(latency, 20) * explorerLatencyMultiplier,
|
|
14782
|
+
pricePenalty
|
|
14783
|
+
};
|
|
14784
|
+
}
|
|
14785
|
+
|
|
14786
|
+
// src/cli/scoring-v2/weights.ts
|
|
14787
|
+
var BASE_WEIGHTS = {
|
|
14788
|
+
status: 22,
|
|
14789
|
+
context: 6,
|
|
14790
|
+
output: 6,
|
|
14791
|
+
versionBonus: 8,
|
|
14792
|
+
reasoning: 10,
|
|
14793
|
+
toolcall: 16,
|
|
14794
|
+
attachment: 2,
|
|
14795
|
+
quality: 14,
|
|
14796
|
+
coding: 18,
|
|
14797
|
+
latencyPenalty: -3,
|
|
14798
|
+
pricePenalty: -2
|
|
14799
|
+
};
|
|
14800
|
+
var AGENT_WEIGHT_OVERRIDES = {
|
|
14801
|
+
orchestrator: {
|
|
14802
|
+
reasoning: 22,
|
|
14803
|
+
toolcall: 22,
|
|
14804
|
+
quality: 16,
|
|
14805
|
+
coding: 16,
|
|
14806
|
+
latencyPenalty: -2
|
|
14807
|
+
},
|
|
14808
|
+
oracle: {
|
|
14809
|
+
reasoning: 26,
|
|
14810
|
+
quality: 20,
|
|
14811
|
+
coding: 18,
|
|
14812
|
+
latencyPenalty: -2,
|
|
14813
|
+
output: 7
|
|
14814
|
+
},
|
|
14815
|
+
designer: {
|
|
14816
|
+
attachment: 12,
|
|
14817
|
+
output: 10,
|
|
14818
|
+
quality: 16,
|
|
14819
|
+
coding: 10
|
|
14820
|
+
},
|
|
14821
|
+
explorer: {
|
|
14822
|
+
latencyPenalty: -8,
|
|
14823
|
+
toolcall: 24,
|
|
14824
|
+
reasoning: 2,
|
|
14825
|
+
context: 4,
|
|
14826
|
+
output: 4
|
|
14827
|
+
},
|
|
14828
|
+
librarian: {
|
|
14829
|
+
context: 14,
|
|
14830
|
+
output: 10,
|
|
14831
|
+
quality: 18,
|
|
14832
|
+
coding: 14
|
|
14833
|
+
},
|
|
14834
|
+
fixer: {
|
|
14835
|
+
coding: 28,
|
|
14836
|
+
toolcall: 22,
|
|
14837
|
+
reasoning: 12,
|
|
14838
|
+
output: 10
|
|
14839
|
+
}
|
|
14840
|
+
};
|
|
14841
|
+
function getFeatureWeights(agent) {
|
|
14842
|
+
return {
|
|
14843
|
+
...BASE_WEIGHTS,
|
|
14844
|
+
...AGENT_WEIGHT_OVERRIDES[agent]
|
|
14845
|
+
};
|
|
14846
|
+
}
|
|
14847
|
+
|
|
14848
|
+
// src/cli/scoring-v2/engine.ts
|
|
14849
|
+
function weightedFeatures(features, weights) {
|
|
14850
|
+
return {
|
|
14851
|
+
status: features.status * weights.status,
|
|
14852
|
+
context: features.context * weights.context,
|
|
14853
|
+
output: features.output * weights.output,
|
|
14854
|
+
versionBonus: features.versionBonus * weights.versionBonus,
|
|
14855
|
+
reasoning: features.reasoning * weights.reasoning,
|
|
14856
|
+
toolcall: features.toolcall * weights.toolcall,
|
|
14857
|
+
attachment: features.attachment * weights.attachment,
|
|
14858
|
+
quality: features.quality * weights.quality,
|
|
14859
|
+
coding: features.coding * weights.coding,
|
|
14860
|
+
latencyPenalty: features.latencyPenalty * weights.latencyPenalty,
|
|
14861
|
+
pricePenalty: features.pricePenalty * weights.pricePenalty
|
|
14862
|
+
};
|
|
14863
|
+
}
|
|
14864
|
+
function sumFeatures(features) {
|
|
14865
|
+
return features.status + features.context + features.output + features.versionBonus + features.reasoning + features.toolcall + features.attachment + features.quality + features.coding + features.latencyPenalty + features.pricePenalty;
|
|
14866
|
+
}
|
|
14867
|
+
function withStableTieBreak(left, right) {
|
|
14868
|
+
if (left.totalScore !== right.totalScore) {
|
|
14869
|
+
return right.totalScore - left.totalScore;
|
|
14870
|
+
}
|
|
14871
|
+
const providerDelta = left.model.providerID.localeCompare(right.model.providerID);
|
|
14872
|
+
if (providerDelta !== 0) {
|
|
14873
|
+
return providerDelta;
|
|
14874
|
+
}
|
|
14875
|
+
return left.model.model.localeCompare(right.model.model);
|
|
14876
|
+
}
|
|
14877
|
+
function scoreCandidateV2(model, agent, externalSignals) {
|
|
14878
|
+
const features = extractFeatureVector(model, agent, externalSignals);
|
|
14879
|
+
const weights = getFeatureWeights(agent);
|
|
14880
|
+
const weighted = weightedFeatures(features, weights);
|
|
14881
|
+
return {
|
|
14882
|
+
model,
|
|
14883
|
+
totalScore: Math.round(sumFeatures(weighted) * 1000) / 1000,
|
|
14884
|
+
scoreBreakdown: {
|
|
14885
|
+
features,
|
|
14886
|
+
weighted
|
|
14887
|
+
}
|
|
14888
|
+
};
|
|
14889
|
+
}
|
|
14890
|
+
function rankModelsV2(models, agent, externalSignals) {
|
|
14891
|
+
return models.map((model) => scoreCandidateV2(model, agent, externalSignals)).sort(withStableTieBreak);
|
|
14892
|
+
}
|
|
14465
14893
|
// src/cli/dynamic-model-selection.ts
|
|
14466
14894
|
var AGENTS = [
|
|
14467
14895
|
"orchestrator",
|
|
@@ -14471,6 +14899,15 @@ var AGENTS = [
|
|
|
14471
14899
|
"librarian",
|
|
14472
14900
|
"fixer"
|
|
14473
14901
|
];
|
|
14902
|
+
var FREE_BIASED_PROVIDERS = new Set(["opencode"]);
|
|
14903
|
+
var PRIMARY_ASSIGNMENT_ORDER = [
|
|
14904
|
+
"oracle",
|
|
14905
|
+
"orchestrator",
|
|
14906
|
+
"fixer",
|
|
14907
|
+
"designer",
|
|
14908
|
+
"librarian",
|
|
14909
|
+
"explorer"
|
|
14910
|
+
];
|
|
14474
14911
|
var ROLE_VARIANT = {
|
|
14475
14912
|
orchestrator: undefined,
|
|
14476
14913
|
oracle: "high",
|
|
@@ -14511,17 +14948,156 @@ function statusScore(status) {
|
|
|
14511
14948
|
return -5;
|
|
14512
14949
|
return -40;
|
|
14513
14950
|
}
|
|
14514
|
-
function
|
|
14951
|
+
function toVersionTuple(major, minor, patch) {
|
|
14952
|
+
return [
|
|
14953
|
+
Number.parseInt(major, 10) || 0,
|
|
14954
|
+
Number.parseInt(minor ?? "0", 10) || 0,
|
|
14955
|
+
Number.parseInt(patch ?? "0", 10) || 0
|
|
14956
|
+
];
|
|
14957
|
+
}
|
|
14958
|
+
function compareVersionTuple(a, b) {
|
|
14959
|
+
if (a[0] !== b[0])
|
|
14960
|
+
return a[0] - b[0];
|
|
14961
|
+
if (a[1] !== b[1])
|
|
14962
|
+
return a[1] - b[1];
|
|
14963
|
+
return a[2] - b[2];
|
|
14964
|
+
}
|
|
14965
|
+
function extractVersionFamily(model) {
|
|
14966
|
+
const text = `${model.model} ${model.name}`.toLowerCase();
|
|
14967
|
+
const gpt = text.match(/\bgpt[-_ ]?(\d+)(?:[.-](\d+))?(?:[.-](\d+))?\b/);
|
|
14968
|
+
if (gpt) {
|
|
14969
|
+
return {
|
|
14970
|
+
family: "gpt",
|
|
14971
|
+
version: toVersionTuple(gpt[1] ?? "0", gpt[2], gpt[3]),
|
|
14972
|
+
confidence: 1,
|
|
14973
|
+
prereleasePenalty: /preview|experimental|exp|\brc\b/.test(text) ? -2 : 0
|
|
14974
|
+
};
|
|
14975
|
+
}
|
|
14976
|
+
const gemini = text.match(/\bgemini[-_ ]?(\d+)(?:[.-](\d+))?(?:[.-](\d+))?\b/);
|
|
14977
|
+
if (gemini) {
|
|
14978
|
+
return {
|
|
14979
|
+
family: "gemini",
|
|
14980
|
+
version: toVersionTuple(gemini[1] ?? "0", gemini[2], gemini[3]),
|
|
14981
|
+
confidence: 1,
|
|
14982
|
+
prereleasePenalty: /preview|experimental|exp|\brc\b/.test(text) ? -2 : 0
|
|
14983
|
+
};
|
|
14984
|
+
}
|
|
14985
|
+
const kimi = text.match(/\bkimi[-_ ]?k(\d+)(?:[.-]?(\d+))?(?:[.-](\d+))?\b/);
|
|
14986
|
+
if (kimi) {
|
|
14987
|
+
return {
|
|
14988
|
+
family: "kimi-k",
|
|
14989
|
+
version: toVersionTuple(kimi[1] ?? "0", kimi[2], kimi[3]),
|
|
14990
|
+
confidence: 1,
|
|
14991
|
+
prereleasePenalty: /preview|experimental|exp|\brc\b/.test(text) ? -2 : 0
|
|
14992
|
+
};
|
|
14993
|
+
}
|
|
14994
|
+
const generic = text.match(/\b([a-z][a-z0-9-]{1,20})[-_ ](\d+)(?:[.-](\d+))?(?:[.-](\d+))?\b/);
|
|
14995
|
+
if (generic) {
|
|
14996
|
+
return {
|
|
14997
|
+
family: generic[1] ?? "generic",
|
|
14998
|
+
version: toVersionTuple(generic[2] ?? "0", generic[3], generic[4]),
|
|
14999
|
+
confidence: 0.7,
|
|
15000
|
+
prereleasePenalty: /preview|experimental|exp|\brc\b/.test(text) ? -2 : 0
|
|
15001
|
+
};
|
|
15002
|
+
}
|
|
15003
|
+
return null;
|
|
15004
|
+
}
|
|
15005
|
+
function getVersionRecencyMap(models) {
|
|
15006
|
+
const familyVersions = new Map;
|
|
15007
|
+
const modelInfo = new Map;
|
|
15008
|
+
for (const model of models) {
|
|
15009
|
+
const info = extractVersionFamily(model);
|
|
15010
|
+
if (!info)
|
|
15011
|
+
continue;
|
|
15012
|
+
modelInfo.set(model.model, info);
|
|
15013
|
+
const current = familyVersions.get(info.family) ?? [];
|
|
15014
|
+
current.push(info.version);
|
|
15015
|
+
familyVersions.set(info.family, current);
|
|
15016
|
+
}
|
|
15017
|
+
const recencyMap = {};
|
|
15018
|
+
for (const model of models) {
|
|
15019
|
+
const info = modelInfo.get(model.model);
|
|
15020
|
+
if (!info) {
|
|
15021
|
+
recencyMap[model.model] = 0;
|
|
15022
|
+
continue;
|
|
15023
|
+
}
|
|
15024
|
+
const versions2 = familyVersions.get(info.family) ?? [];
|
|
15025
|
+
const unique = versions2.map((tuple2) => `${tuple2[0]}.${tuple2[1]}.${tuple2[2]}`).filter((value, index2, arr) => arr.indexOf(value) === index2).map((value) => {
|
|
15026
|
+
const [major, minor, patch] = value.split(".").map((v) => Number.parseInt(v, 10) || 0);
|
|
15027
|
+
return [major, minor, patch];
|
|
15028
|
+
}).sort(compareVersionTuple);
|
|
15029
|
+
if (unique.length === 0) {
|
|
15030
|
+
recencyMap[model.model] = 0;
|
|
15031
|
+
continue;
|
|
15032
|
+
}
|
|
15033
|
+
const index = unique.findIndex((tuple2) => compareVersionTuple(tuple2, info.version) === 0);
|
|
15034
|
+
const percentile = unique.length === 1 ? 0.5 : index / (unique.length - 1);
|
|
15035
|
+
const raw = -3 + percentile * (12 - -3);
|
|
15036
|
+
const final = Math.max(-3, Math.min(12, raw * info.confidence + info.prereleasePenalty));
|
|
15037
|
+
recencyMap[model.model] = final;
|
|
15038
|
+
}
|
|
15039
|
+
return recencyMap;
|
|
15040
|
+
}
|
|
15041
|
+
function baseScore(model, versionRecencyBoost = 0) {
|
|
14515
15042
|
const lowered = `${model.model} ${model.name}`.toLowerCase();
|
|
14516
15043
|
const context = Math.min(model.contextLimit, 1e6) / 50000;
|
|
14517
15044
|
const output = Math.min(model.outputLimit, 300000) / 30000;
|
|
14518
15045
|
const deep = tokenScore(lowered, /(opus|pro|thinking|reason|r1|gpt-5|k2\.5)/i, 12);
|
|
14519
|
-
const fast = tokenScore(lowered, /(nano|flash|mini|lite|fast|turbo|haiku|small)/i,
|
|
15046
|
+
const fast = tokenScore(lowered, /(nano|flash|mini|lite|fast|turbo|haiku|small)/i, 4);
|
|
14520
15047
|
const code = tokenScore(lowered, /(codex|coder|code|dev|program)/i, 12);
|
|
14521
|
-
|
|
14522
|
-
|
|
15048
|
+
return statusScore(model.status) + context + output + deep + fast + code + versionRecencyBoost + (model.toolcall ? 25 : 0);
|
|
15049
|
+
}
|
|
15050
|
+
function hasFlashToken(model) {
|
|
15051
|
+
return /flash/i.test(`${model.model} ${model.name}`);
|
|
14523
15052
|
}
|
|
14524
|
-
function
|
|
15053
|
+
function isZai47Model(model) {
|
|
15054
|
+
return model.providerID === "zai-coding-plan" && /glm-4\.7/i.test(`${model.model} ${model.name}`);
|
|
15055
|
+
}
|
|
15056
|
+
function isKimiK25Model(model) {
|
|
15057
|
+
return /kimi-k2\.?5|k2\.?5/i.test(`${model.model} ${model.name}`);
|
|
15058
|
+
}
|
|
15059
|
+
function geminiPreferenceAdjustment(_agent, model) {
|
|
15060
|
+
const lowered = `${model.model} ${model.name}`.toLowerCase();
|
|
15061
|
+
const isGemini25Pro = /gemini-2\.5-pro/.test(lowered);
|
|
15062
|
+
return isGemini25Pro ? -14 : 0;
|
|
15063
|
+
}
|
|
15064
|
+
function chutesPreferenceAdjustment(agent, model) {
|
|
15065
|
+
if (model.providerID !== "chutes")
|
|
15066
|
+
return 0;
|
|
15067
|
+
const lowered = `${model.model} ${model.name}`.toLowerCase();
|
|
15068
|
+
const isQwen3 = /qwen3/.test(lowered);
|
|
15069
|
+
const isKimiK25 = /kimi-k2\.5|k2\.5/.test(lowered);
|
|
15070
|
+
const isMinimaxM21 = /minimax[-_ ]?m2\.1/.test(lowered);
|
|
15071
|
+
const qwenPenalty = {
|
|
15072
|
+
oracle: -12,
|
|
15073
|
+
orchestrator: -10,
|
|
15074
|
+
fixer: -22,
|
|
15075
|
+
designer: -14,
|
|
15076
|
+
librarian: -18,
|
|
15077
|
+
explorer: -10
|
|
15078
|
+
};
|
|
15079
|
+
const kimiBonus = {
|
|
15080
|
+
oracle: 0,
|
|
15081
|
+
orchestrator: 0,
|
|
15082
|
+
fixer: 8,
|
|
15083
|
+
designer: 6,
|
|
15084
|
+
librarian: 5,
|
|
15085
|
+
explorer: 4
|
|
15086
|
+
};
|
|
15087
|
+
const minimaxBonus = {
|
|
15088
|
+
oracle: 0,
|
|
15089
|
+
orchestrator: 0,
|
|
15090
|
+
fixer: 10,
|
|
15091
|
+
designer: 3,
|
|
15092
|
+
librarian: 9,
|
|
15093
|
+
explorer: 12
|
|
15094
|
+
};
|
|
15095
|
+
return (isQwen3 ? qwenPenalty[agent] : 0) + (isKimiK25 ? kimiBonus[agent] : 0) + (isMinimaxM21 ? minimaxBonus[agent] : 0);
|
|
15096
|
+
}
|
|
15097
|
+
function modelLookupKeys2(model) {
|
|
15098
|
+
return buildModelKeyAliases(model.model);
|
|
15099
|
+
}
|
|
15100
|
+
function roleScore(agent, model, versionRecencyBoost = 0) {
|
|
14525
15101
|
const lowered = `${model.model} ${model.name}`.toLowerCase();
|
|
14526
15102
|
const reasoning = model.reasoning ? 1 : 0;
|
|
14527
15103
|
const toolcall = model.toolcall ? 1 : 0;
|
|
@@ -14537,28 +15113,295 @@ function roleScore(agent, model) {
|
|
|
14537
15113
|
if (model.status === "deprecated") {
|
|
14538
15114
|
return -5000;
|
|
14539
15115
|
}
|
|
14540
|
-
const score = baseScore(model);
|
|
15116
|
+
const score = baseScore(model, versionRecencyBoost);
|
|
15117
|
+
const flash = hasFlashToken(model);
|
|
15118
|
+
const isZai47 = isZai47Model(model);
|
|
15119
|
+
const zai47Flash = isZai47 && flash;
|
|
15120
|
+
const zai47NonFlash = isZai47 && !flash;
|
|
15121
|
+
const providerBias = model.providerID === "openai" ? 3 : model.providerID === "anthropic" ? 3 : model.providerID === "kimi-for-coding" ? 2 : model.providerID === "google" ? 2 : model.providerID === "github-copilot" ? 1 : model.providerID === "zai-coding-plan" ? 0 : model.providerID === "chutes" ? 2 : model.providerID === "opencode" ? -2 : 0;
|
|
15122
|
+
const geminiAdjustment = geminiPreferenceAdjustment(agent, model);
|
|
15123
|
+
const chutesAdjustment = chutesPreferenceAdjustment(agent, model);
|
|
14541
15124
|
if (agent === "orchestrator") {
|
|
14542
|
-
|
|
15125
|
+
const flashAdjustment2 = flash ? -22 : 0;
|
|
15126
|
+
const zaiAdjustment2 = zai47NonFlash ? 16 : zai47Flash ? -18 : 0;
|
|
15127
|
+
const nonReasoningFlashPenalty2 = flash && !model.reasoning ? -16 : 0;
|
|
15128
|
+
return score + reasoning * 40 + toolcall * 25 + deep * 10 + code * 8 + context + flashAdjustment2 + zaiAdjustment2 + nonReasoningFlashPenalty2 + geminiAdjustment + chutesAdjustment + providerBias;
|
|
14543
15129
|
}
|
|
14544
15130
|
if (agent === "oracle") {
|
|
14545
|
-
|
|
15131
|
+
const flashAdjustment2 = flash ? -34 : 0;
|
|
15132
|
+
const zaiAdjustment2 = zai47NonFlash ? 16 : zai47Flash ? -18 : 0;
|
|
15133
|
+
const nonReasoningFlashPenalty2 = flash && !model.reasoning ? -16 : 0;
|
|
15134
|
+
return score + reasoning * 55 + deep * 18 + context * 1.2 + toolcall * 10 + flashAdjustment2 + zaiAdjustment2 + nonReasoningFlashPenalty2 + geminiAdjustment + chutesAdjustment + providerBias;
|
|
14546
15135
|
}
|
|
14547
15136
|
if (agent === "designer") {
|
|
14548
|
-
|
|
15137
|
+
const flashAdjustment2 = flash ? -8 : 0;
|
|
15138
|
+
const zaiAdjustment2 = zai47NonFlash ? 10 : zai47Flash ? -8 : 0;
|
|
15139
|
+
return score + attachment * 25 + reasoning * 18 + toolcall * 15 + context * 0.8 + output + flashAdjustment2 + zaiAdjustment2 + geminiAdjustment + chutesAdjustment + providerBias;
|
|
14549
15140
|
}
|
|
14550
15141
|
if (agent === "explorer") {
|
|
14551
|
-
|
|
15142
|
+
const flashAdjustment2 = flash ? 26 : -10;
|
|
15143
|
+
const zaiAdjustment2 = zai47NonFlash ? 2 : zai47Flash ? 6 : 0;
|
|
15144
|
+
const deepPenalty = deep * -18;
|
|
15145
|
+
return score + fast * 68 + toolcall * 28 + reasoning * 2 + context * 0.2 + flashAdjustment2 + zaiAdjustment2 + deepPenalty + geminiAdjustment + chutesAdjustment + providerBias;
|
|
14552
15146
|
}
|
|
14553
15147
|
if (agent === "librarian") {
|
|
14554
|
-
|
|
15148
|
+
const flashAdjustment2 = flash ? -12 : 0;
|
|
15149
|
+
const zaiAdjustment2 = zai47NonFlash ? 16 : zai47Flash ? -18 : 0;
|
|
15150
|
+
return score + context * 30 + toolcall * 22 + reasoning * 15 + output * 10 + flashAdjustment2 + zaiAdjustment2 + geminiAdjustment + chutesAdjustment + providerBias;
|
|
15151
|
+
}
|
|
15152
|
+
const flashAdjustment = flash ? -18 : 0;
|
|
15153
|
+
const zaiAdjustment = zai47NonFlash ? 16 : zai47Flash ? -18 : 0;
|
|
15154
|
+
const nonReasoningFlashPenalty = flash && !model.reasoning ? -16 : 0;
|
|
15155
|
+
return score + code * 28 + toolcall * 24 + fast * 18 + reasoning * 14 + output * 8 + flashAdjustment + zaiAdjustment + nonReasoningFlashPenalty + geminiAdjustment + chutesAdjustment + providerBias;
|
|
15156
|
+
}
|
|
15157
|
+
function getExternalSignalBoost(agent, model, externalSignals) {
|
|
15158
|
+
if (!externalSignals)
|
|
15159
|
+
return 0;
|
|
15160
|
+
const signal = modelLookupKeys2(model).map((key) => externalSignals[key]).find((item) => item !== undefined);
|
|
15161
|
+
if (!signal)
|
|
15162
|
+
return 0;
|
|
15163
|
+
const qualityScore = signal.qualityScore ?? 0;
|
|
15164
|
+
const codingScore = signal.codingScore ?? 0;
|
|
15165
|
+
const latencySeconds = signal.latencySeconds;
|
|
15166
|
+
const blendedPrice2 = signal.inputPricePer1M !== undefined && signal.outputPricePer1M !== undefined ? signal.inputPricePer1M * 0.75 + signal.outputPricePer1M * 0.25 : signal.inputPricePer1M ?? signal.outputPricePer1M ?? 0;
|
|
15167
|
+
if (agent === "explorer") {
|
|
15168
|
+
const qualityBoost2 = qualityScore * 0.05;
|
|
15169
|
+
const codingBoost2 = codingScore * 0.08;
|
|
15170
|
+
const latencyPenalty2 = typeof latencySeconds === "number" && Number.isFinite(latencySeconds) ? Math.min(latencySeconds, 12) * 3.2 + (latencySeconds > 7 ? 16 : latencySeconds > 4 ? 10 : 0) : 0;
|
|
15171
|
+
const pricePenalty2 = Math.min(blendedPrice2, 30) * 0.03;
|
|
15172
|
+
const qualityFloorPenalty = qualityScore > 0 && qualityScore < 35 ? (35 - qualityScore) * 0.8 : 0;
|
|
15173
|
+
const boost2 = qualityBoost2 + codingBoost2 - latencyPenalty2 - pricePenalty2 - qualityFloorPenalty;
|
|
15174
|
+
return Math.max(-90, Math.min(25, boost2));
|
|
15175
|
+
}
|
|
15176
|
+
const qualityBoost = qualityScore * 0.16;
|
|
15177
|
+
const codingBoost = codingScore * 0.24;
|
|
15178
|
+
const latencyPenalty = typeof latencySeconds === "number" && Number.isFinite(latencySeconds) ? Math.min(latencySeconds, 25) * 0.22 : 0;
|
|
15179
|
+
const pricePenalty = Math.min(blendedPrice2, 30) * 0.08;
|
|
15180
|
+
const boost = qualityBoost + codingBoost - latencyPenalty - pricePenalty;
|
|
15181
|
+
return Math.max(-30, Math.min(45, boost));
|
|
15182
|
+
}
|
|
15183
|
+
function rankModels2(models, agent, externalSignals) {
|
|
15184
|
+
const versionRecencyMap = getVersionRecencyMap(models);
|
|
15185
|
+
return [...models].sort((a, b) => {
|
|
15186
|
+
const scoreA = roleScore(agent, a, versionRecencyMap[a.model] ?? 0) + getExternalSignalBoost(agent, a, externalSignals);
|
|
15187
|
+
const scoreB = roleScore(agent, b, versionRecencyMap[b.model] ?? 0) + getExternalSignalBoost(agent, b, externalSignals);
|
|
15188
|
+
const scoreDelta = scoreB - scoreA;
|
|
15189
|
+
if (scoreDelta !== 0)
|
|
15190
|
+
return scoreDelta;
|
|
15191
|
+
const providerTieBreak = a.providerID.localeCompare(b.providerID);
|
|
15192
|
+
if (providerTieBreak !== 0)
|
|
15193
|
+
return providerTieBreak;
|
|
15194
|
+
return a.model.localeCompare(b.model);
|
|
15195
|
+
});
|
|
15196
|
+
}
|
|
15197
|
+
function combinedScore(agent, model, externalSignals, versionRecencyMap) {
|
|
15198
|
+
return roleScore(agent, model, versionRecencyMap?.[model.model] ?? 0) + getExternalSignalBoost(agent, model, externalSignals);
|
|
15199
|
+
}
|
|
15200
|
+
function effectiveEngine(engineVersion) {
|
|
15201
|
+
return engineVersion === "v2" ? "v2" : "v1";
|
|
15202
|
+
}
|
|
15203
|
+
function scoreForEngine(engineVersion, agent, model, externalSignals, versionRecencyMap) {
|
|
15204
|
+
if (effectiveEngine(engineVersion) === "v2") {
|
|
15205
|
+
return scoreCandidateV2(model, agent, externalSignals).totalScore;
|
|
14555
15206
|
}
|
|
14556
|
-
return
|
|
15207
|
+
return combinedScore(agent, model, externalSignals, versionRecencyMap);
|
|
14557
15208
|
}
|
|
14558
|
-
function
|
|
14559
|
-
|
|
15209
|
+
function selectTopModelsPerProvider(models, engineVersion, externalSignals, versionRecencyMap) {
|
|
15210
|
+
const byProvider = new Map;
|
|
15211
|
+
for (const model of models) {
|
|
15212
|
+
const current = byProvider.get(model.providerID) ?? [];
|
|
15213
|
+
current.push(model);
|
|
15214
|
+
byProvider.set(model.providerID, current);
|
|
15215
|
+
}
|
|
15216
|
+
const selected = [];
|
|
15217
|
+
for (const providerModels of byProvider.values()) {
|
|
15218
|
+
if (providerModels.length <= 2) {
|
|
15219
|
+
selected.push(...providerModels);
|
|
15220
|
+
continue;
|
|
15221
|
+
}
|
|
15222
|
+
const ranked = [...providerModels].map((model) => {
|
|
15223
|
+
const total = AGENTS.reduce((sum, agent) => {
|
|
15224
|
+
return sum + scoreForEngine(engineVersion, agent, model, externalSignals, versionRecencyMap);
|
|
15225
|
+
}, 0);
|
|
15226
|
+
return {
|
|
15227
|
+
model,
|
|
15228
|
+
score: total / AGENTS.length
|
|
15229
|
+
};
|
|
15230
|
+
}).sort((a, b) => {
|
|
15231
|
+
if (a.score !== b.score)
|
|
15232
|
+
return b.score - a.score;
|
|
15233
|
+
return a.model.model.localeCompare(b.model.model);
|
|
15234
|
+
}).slice(0, 2).map((entry) => entry.model);
|
|
15235
|
+
selected.push(...ranked);
|
|
15236
|
+
}
|
|
15237
|
+
return selected;
|
|
15238
|
+
}
|
|
15239
|
+
function countProviderUsage(agents) {
|
|
15240
|
+
const counts = new Map;
|
|
15241
|
+
for (const assignment of Object.values(agents)) {
|
|
15242
|
+
const provider = assignment.model.split("/")[0];
|
|
15243
|
+
if (!provider)
|
|
15244
|
+
continue;
|
|
15245
|
+
counts.set(provider, (counts.get(provider) ?? 0) + 1);
|
|
15246
|
+
}
|
|
15247
|
+
return counts;
|
|
14560
15248
|
}
|
|
14561
|
-
function
|
|
15249
|
+
function rebalanceForSubscriptionMode(agents, chains, provenance, paidProviders, getRankedModels, getPinnedModelForProvider, targetByProvider, externalSignals, versionRecencyMap, engineVersion) {
|
|
15250
|
+
if (paidProviders.length <= 1)
|
|
15251
|
+
return;
|
|
15252
|
+
const MAX_ALLOWED_SCORE_LOSS = 20;
|
|
15253
|
+
while (true) {
|
|
15254
|
+
const providerUsage = countProviderUsage(agents);
|
|
15255
|
+
const underProviders = paidProviders.filter((providerID) => (providerUsage.get(providerID) ?? 0) < (targetByProvider[providerID] ?? 0));
|
|
15256
|
+
const overProviders = paidProviders.filter((providerID) => (providerUsage.get(providerID) ?? 0) > (targetByProvider[providerID] ?? 0));
|
|
15257
|
+
if (underProviders.length === 0 || overProviders.length === 0)
|
|
15258
|
+
break;
|
|
15259
|
+
let bestSwap;
|
|
15260
|
+
for (const agent of PRIMARY_ASSIGNMENT_ORDER) {
|
|
15261
|
+
const currentModelID = agents[agent]?.model;
|
|
15262
|
+
if (!currentModelID)
|
|
15263
|
+
continue;
|
|
15264
|
+
const currentProvider = currentModelID.split("/")[0];
|
|
15265
|
+
if (!currentProvider || !overProviders.includes(currentProvider))
|
|
15266
|
+
continue;
|
|
15267
|
+
const ranked = getRankedModels(agent);
|
|
15268
|
+
const currentModel = ranked.find((model) => model.model === currentModelID) ?? ranked.find((model) => model.providerID === currentProvider);
|
|
15269
|
+
if (!currentModel)
|
|
15270
|
+
continue;
|
|
15271
|
+
const currentScore = scoreForEngine(engineVersion, agent, currentModel, externalSignals, versionRecencyMap);
|
|
15272
|
+
for (const underProvider of underProviders) {
|
|
15273
|
+
const pinned = getPinnedModelForProvider(agent, underProvider);
|
|
15274
|
+
const candidate = ranked.find((model) => model.model === pinned) ?? ranked.find((model) => model.providerID === underProvider);
|
|
15275
|
+
if (!candidate)
|
|
15276
|
+
continue;
|
|
15277
|
+
const candidateScore = scoreForEngine(engineVersion, agent, candidate, externalSignals, versionRecencyMap);
|
|
15278
|
+
const loss = currentScore - candidateScore;
|
|
15279
|
+
if (loss > MAX_ALLOWED_SCORE_LOSS)
|
|
15280
|
+
continue;
|
|
15281
|
+
if (!bestSwap || loss < bestSwap.loss) {
|
|
15282
|
+
bestSwap = { agent, candidate, loss };
|
|
15283
|
+
}
|
|
15284
|
+
}
|
|
15285
|
+
}
|
|
15286
|
+
if (!bestSwap)
|
|
15287
|
+
break;
|
|
15288
|
+
agents[bestSwap.agent].model = bestSwap.candidate.model;
|
|
15289
|
+
chains[bestSwap.agent] = dedupe2([
|
|
15290
|
+
bestSwap.candidate.model,
|
|
15291
|
+
...chains[bestSwap.agent] ?? []
|
|
15292
|
+
]).slice(0, 10);
|
|
15293
|
+
provenance[bestSwap.agent] = {
|
|
15294
|
+
winnerLayer: "provider-fallback-policy",
|
|
15295
|
+
winnerModel: bestSwap.candidate.model
|
|
15296
|
+
};
|
|
15297
|
+
}
|
|
15298
|
+
}
|
|
15299
|
+
function chooseProviderRepresentative(providerModels, agent, externalSignals, versionRecencyMap) {
|
|
15300
|
+
if (providerModels.length === 0)
|
|
15301
|
+
return null;
|
|
15302
|
+
const flashBest = providerModels.find((model) => hasFlashToken(model));
|
|
15303
|
+
const nonFlashBest = providerModels.find((model) => !hasFlashToken(model));
|
|
15304
|
+
if (!nonFlashBest)
|
|
15305
|
+
return providerModels[0] ?? null;
|
|
15306
|
+
if (!flashBest)
|
|
15307
|
+
return nonFlashBest;
|
|
15308
|
+
const flashScore = combinedScore(agent, flashBest, externalSignals, versionRecencyMap);
|
|
15309
|
+
const nonFlashScore = combinedScore(agent, nonFlashBest, externalSignals, versionRecencyMap);
|
|
15310
|
+
const threshold = agent === "explorer" ? -6 : 12;
|
|
15311
|
+
return flashScore >= nonFlashScore + threshold ? flashBest : nonFlashBest;
|
|
15312
|
+
}
|
|
15313
|
+
function getQualityWindow(agent) {
|
|
15314
|
+
if (agent === "oracle" || agent === "orchestrator")
|
|
15315
|
+
return 12;
|
|
15316
|
+
if (agent === "fixer")
|
|
15317
|
+
return 15;
|
|
15318
|
+
if (agent === "designer")
|
|
15319
|
+
return 16;
|
|
15320
|
+
if (agent === "librarian")
|
|
15321
|
+
return 18;
|
|
15322
|
+
return 22;
|
|
15323
|
+
}
|
|
15324
|
+
function getProviderBundle(providerModels, agent, externalSignals, versionRecencyMap) {
|
|
15325
|
+
if (providerModels.length === 0)
|
|
15326
|
+
return [];
|
|
15327
|
+
const representative = chooseProviderRepresentative(providerModels, agent, externalSignals, versionRecencyMap);
|
|
15328
|
+
if (!representative)
|
|
15329
|
+
return [];
|
|
15330
|
+
const second = providerModels.find((m) => m.model !== representative.model);
|
|
15331
|
+
if (!second)
|
|
15332
|
+
return [representative.model];
|
|
15333
|
+
const score1 = combinedScore(agent, representative, externalSignals, versionRecencyMap);
|
|
15334
|
+
const score2 = combinedScore(agent, second, externalSignals, versionRecencyMap);
|
|
15335
|
+
const gap = Math.abs(score1 - score2);
|
|
15336
|
+
const includeSecond = representative.providerID === "chutes" || gap <= (agent === "oracle" || agent === "orchestrator" ? 8 : agent === "designer" || agent === "librarian" ? 12 : agent === "fixer" ? 15 : 18);
|
|
15337
|
+
return includeSecond ? [representative.model, second.model] : [representative.model];
|
|
15338
|
+
}
|
|
15339
|
+
function selectPrimaryWithDiversity(candidates, agent, providerUsage, targetByProvider, remainingSlots, externalSignals, versionRecencyMap) {
|
|
15340
|
+
if (candidates.length === 0)
|
|
15341
|
+
return null;
|
|
15342
|
+
const candidateScores = candidates.map((model) => {
|
|
15343
|
+
const usage = providerUsage.get(model.providerID) ?? 0;
|
|
15344
|
+
const target = targetByProvider[model.providerID] ?? 1;
|
|
15345
|
+
const softCap = target;
|
|
15346
|
+
const hardCap = Math.min(target + 1, 4);
|
|
15347
|
+
const deficit = Math.max(0, target - usage);
|
|
15348
|
+
const softOverflow = Math.max(0, usage + 1 - softCap);
|
|
15349
|
+
const hardOverflow = Math.max(0, usage + 1 - hardCap);
|
|
15350
|
+
const rawScore = combinedScore(agent, model, externalSignals, versionRecencyMap);
|
|
15351
|
+
const adjustedScore = rawScore + deficit * 14 - softOverflow * 18 - hardOverflow * 100;
|
|
15352
|
+
return {
|
|
15353
|
+
model,
|
|
15354
|
+
usage,
|
|
15355
|
+
target,
|
|
15356
|
+
rawScore,
|
|
15357
|
+
adjustedScore: Math.round(adjustedScore * 1000) / 1000
|
|
15358
|
+
};
|
|
15359
|
+
});
|
|
15360
|
+
const bestRaw = Math.max(...candidateScores.map((item) => item.rawScore));
|
|
15361
|
+
const window = getQualityWindow(agent);
|
|
15362
|
+
let eligible = candidateScores.filter((item) => item.rawScore >= bestRaw - window);
|
|
15363
|
+
const mustFillProviders = Object.entries(targetByProvider).filter(([providerID, target]) => {
|
|
15364
|
+
const usage = providerUsage.get(providerID) ?? 0;
|
|
15365
|
+
return Math.max(0, target - usage) >= remainingSlots;
|
|
15366
|
+
}).map(([providerID]) => providerID);
|
|
15367
|
+
if (mustFillProviders.length > 0) {
|
|
15368
|
+
const forced = eligible.filter((item) => mustFillProviders.includes(item.model.providerID));
|
|
15369
|
+
if (forced.length > 0)
|
|
15370
|
+
eligible = forced;
|
|
15371
|
+
}
|
|
15372
|
+
eligible.sort((a, b) => {
|
|
15373
|
+
const delta = b.adjustedScore - a.adjustedScore;
|
|
15374
|
+
if (delta !== 0)
|
|
15375
|
+
return delta;
|
|
15376
|
+
const ratioA = a.target > 0 ? a.usage / a.target : a.usage;
|
|
15377
|
+
const ratioB = b.target > 0 ? b.usage / b.target : b.usage;
|
|
15378
|
+
if (ratioA !== ratioB)
|
|
15379
|
+
return ratioA - ratioB;
|
|
15380
|
+
if (a.rawScore !== b.rawScore)
|
|
15381
|
+
return b.rawScore - a.rawScore;
|
|
15382
|
+
const providerTie = a.model.providerID.localeCompare(b.model.providerID);
|
|
15383
|
+
if (providerTie !== 0)
|
|
15384
|
+
return providerTie;
|
|
15385
|
+
return a.model.model.localeCompare(b.model.model);
|
|
15386
|
+
});
|
|
15387
|
+
let chosen = eligible[0] ?? candidateScores[0];
|
|
15388
|
+
if (!chosen)
|
|
15389
|
+
return null;
|
|
15390
|
+
if (chosen.usage >= 2) {
|
|
15391
|
+
const bestUnused = candidateScores.find((item) => item.usage === 0);
|
|
15392
|
+
if (bestUnused && bestUnused.adjustedScore >= chosen.adjustedScore - 9) {
|
|
15393
|
+
chosen = bestUnused;
|
|
15394
|
+
}
|
|
15395
|
+
}
|
|
15396
|
+
if (agent !== "explorer" && isZai47Model(chosen.model) && hasFlashToken(chosen.model)) {
|
|
15397
|
+
const kimiCandidate = candidateScores.find((item) => isKimiK25Model(item.model));
|
|
15398
|
+
if (kimiCandidate && kimiCandidate.rawScore >= chosen.rawScore - 2) {
|
|
15399
|
+
chosen = kimiCandidate;
|
|
15400
|
+
}
|
|
15401
|
+
}
|
|
15402
|
+
return chosen.model;
|
|
15403
|
+
}
|
|
15404
|
+
function dedupe2(models) {
|
|
14562
15405
|
const seen = new Set;
|
|
14563
15406
|
const result = [];
|
|
14564
15407
|
for (const model of models) {
|
|
@@ -14569,43 +15412,496 @@ function dedupe(models) {
|
|
|
14569
15412
|
}
|
|
14570
15413
|
return result;
|
|
14571
15414
|
}
|
|
14572
|
-
function
|
|
15415
|
+
function finalizeChainWithTail(prefix, preferredTail) {
|
|
15416
|
+
if (!preferredTail) {
|
|
15417
|
+
return dedupe2([...prefix, "opencode/big-pickle"]).slice(0, 10);
|
|
15418
|
+
}
|
|
15419
|
+
const withoutTail = prefix.filter((model) => model !== preferredTail).slice(0, 9);
|
|
15420
|
+
return [...withoutTail, preferredTail];
|
|
15421
|
+
}
|
|
15422
|
+
function ensureSyntheticModel(models, fullModelID) {
|
|
15423
|
+
if (!fullModelID)
|
|
15424
|
+
return models;
|
|
15425
|
+
if (models.some((model) => model.model === fullModelID))
|
|
15426
|
+
return models;
|
|
15427
|
+
const [providerID, modelID] = fullModelID.split("/");
|
|
15428
|
+
if (!providerID || !modelID)
|
|
15429
|
+
return models;
|
|
15430
|
+
return [
|
|
15431
|
+
...models,
|
|
15432
|
+
{
|
|
15433
|
+
providerID,
|
|
15434
|
+
model: fullModelID,
|
|
15435
|
+
name: modelID,
|
|
15436
|
+
status: "active",
|
|
15437
|
+
contextLimit: 200000,
|
|
15438
|
+
outputLimit: 32000,
|
|
15439
|
+
reasoning: true,
|
|
15440
|
+
toolcall: true,
|
|
15441
|
+
attachment: false
|
|
15442
|
+
}
|
|
15443
|
+
];
|
|
15444
|
+
}
|
|
15445
|
+
function buildDynamicModelPlan(catalog, config2, externalSignals, options) {
|
|
15446
|
+
const catalogWithSelectedModels = [
|
|
15447
|
+
config2.selectedChutesPrimaryModel,
|
|
15448
|
+
config2.selectedChutesSecondaryModel,
|
|
15449
|
+
config2.selectedOpenCodePrimaryModel,
|
|
15450
|
+
config2.selectedOpenCodeSecondaryModel
|
|
15451
|
+
].reduce((acc, modelID) => ensureSyntheticModel(acc, modelID), catalog);
|
|
14573
15452
|
const enabledProviders = new Set(getEnabledProviders(config2));
|
|
14574
|
-
const
|
|
15453
|
+
const providerUniverse = catalogWithSelectedModels.filter((m) => {
|
|
15454
|
+
if (!enabledProviders.has(m.providerID))
|
|
15455
|
+
return false;
|
|
15456
|
+
if (m.providerID === "chutes" && /qwen/i.test(m.model)) {
|
|
15457
|
+
return false;
|
|
15458
|
+
}
|
|
15459
|
+
return true;
|
|
15460
|
+
});
|
|
15461
|
+
const engineVersion = options?.scoringEngineVersion ?? config2.scoringEngineVersion ?? "v1";
|
|
15462
|
+
const versionRecencyMap = getVersionRecencyMap(providerUniverse);
|
|
15463
|
+
const providerCandidates = selectTopModelsPerProvider(providerUniverse, engineVersion, externalSignals, versionRecencyMap);
|
|
14575
15464
|
if (providerCandidates.length === 0) {
|
|
14576
15465
|
return null;
|
|
14577
15466
|
}
|
|
15467
|
+
const hasPaidProviderEnabled = config2.hasOpenAI || config2.hasAnthropic || config2.hasCopilot || config2.hasZaiPlan || config2.hasKimi || config2.hasAntigravity;
|
|
15468
|
+
const paidProviders = dedupe2(providerCandidates.map((model) => model.providerID).filter((providerID) => providerID !== "opencode")).sort((a, b) => a.localeCompare(b));
|
|
15469
|
+
const targetByProvider = {};
|
|
15470
|
+
if (paidProviders.length > 0) {
|
|
15471
|
+
const baseTarget = Math.floor(AGENTS.length / paidProviders.length);
|
|
15472
|
+
const extra = AGENTS.length % paidProviders.length;
|
|
15473
|
+
for (const [index, providerID] of paidProviders.entries()) {
|
|
15474
|
+
targetByProvider[providerID] = baseTarget + (index < extra ? 1 : 0);
|
|
15475
|
+
}
|
|
15476
|
+
}
|
|
15477
|
+
const providerUsage = new Map;
|
|
15478
|
+
const rankCache = new Map;
|
|
15479
|
+
const shadowDiffs = {};
|
|
14578
15480
|
const agents = {};
|
|
14579
15481
|
const chains = {};
|
|
14580
|
-
|
|
14581
|
-
|
|
14582
|
-
|
|
15482
|
+
const provenance = {};
|
|
15483
|
+
const getSelectedChutesForAgent = (agent) => {
|
|
15484
|
+
if (!config2.hasChutes)
|
|
15485
|
+
return;
|
|
15486
|
+
return agent === "explorer" || agent === "librarian" || agent === "fixer" ? config2.selectedChutesSecondaryModel ?? config2.selectedChutesPrimaryModel : config2.selectedChutesPrimaryModel;
|
|
15487
|
+
};
|
|
15488
|
+
const getSelectedOpenCodeForAgent = (agent) => {
|
|
15489
|
+
if (!config2.useOpenCodeFreeModels)
|
|
15490
|
+
return;
|
|
15491
|
+
return agent === "explorer" || agent === "librarian" || agent === "fixer" ? config2.selectedOpenCodeSecondaryModel ?? config2.selectedOpenCodePrimaryModel : config2.selectedOpenCodePrimaryModel;
|
|
15492
|
+
};
|
|
15493
|
+
const getPinnedModelForProvider = (agent, providerID) => {
|
|
15494
|
+
if (providerID === "chutes")
|
|
15495
|
+
return getSelectedChutesForAgent(agent);
|
|
15496
|
+
if (providerID === "opencode")
|
|
15497
|
+
return getSelectedOpenCodeForAgent(agent);
|
|
15498
|
+
return;
|
|
15499
|
+
};
|
|
15500
|
+
const getRankedModels = (agent) => {
|
|
15501
|
+
const cached2 = rankCache.get(agent);
|
|
15502
|
+
if (cached2)
|
|
15503
|
+
return cached2;
|
|
15504
|
+
const rankedV1 = rankModels2(providerCandidates, agent, externalSignals);
|
|
15505
|
+
if (engineVersion === "v1") {
|
|
15506
|
+
rankCache.set(agent, rankedV1);
|
|
15507
|
+
return rankedV1;
|
|
15508
|
+
}
|
|
15509
|
+
const rankedV2 = rankModelsV2(providerCandidates, agent, externalSignals).map((candidate) => candidate.model);
|
|
15510
|
+
if (engineVersion === "v2-shadow") {
|
|
15511
|
+
shadowDiffs[agent] = {
|
|
15512
|
+
v1TopModel: rankedV1[0]?.model,
|
|
15513
|
+
v2TopModel: rankedV2[0]?.model
|
|
15514
|
+
};
|
|
15515
|
+
rankCache.set(agent, rankedV1);
|
|
15516
|
+
return rankedV1;
|
|
15517
|
+
}
|
|
15518
|
+
rankCache.set(agent, rankedV2);
|
|
15519
|
+
return rankedV2;
|
|
15520
|
+
};
|
|
15521
|
+
for (const [agentIndex, agent] of PRIMARY_ASSIGNMENT_ORDER.entries()) {
|
|
15522
|
+
const ranked = getRankedModels(agent);
|
|
15523
|
+
const primaryPool = hasPaidProviderEnabled ? ranked.filter((model) => !FREE_BIASED_PROVIDERS.has(model.providerID)) : ranked;
|
|
15524
|
+
const remainingSlots = PRIMARY_ASSIGNMENT_ORDER.length - agentIndex;
|
|
15525
|
+
const primary = selectPrimaryWithDiversity(primaryPool.length > 0 ? primaryPool : ranked, agent, providerUsage, targetByProvider, remainingSlots, externalSignals, versionRecencyMap) ?? ranked[0];
|
|
14583
15526
|
if (!primary)
|
|
14584
15527
|
continue;
|
|
14585
|
-
|
|
14586
|
-
const
|
|
14587
|
-
const
|
|
14588
|
-
|
|
14589
|
-
|
|
15528
|
+
providerUsage.set(primary.providerID, (providerUsage.get(primary.providerID) ?? 0) + 1);
|
|
15529
|
+
const providerOrder = dedupe2(ranked.map((m) => m.providerID));
|
|
15530
|
+
const perProviderBest = providerOrder.flatMap((providerID) => {
|
|
15531
|
+
const providerModels = ranked.filter((m) => m.providerID === providerID);
|
|
15532
|
+
const pinned = getPinnedModelForProvider(agent, providerID);
|
|
15533
|
+
if (pinned && providerModels.some((m) => m.model === pinned)) {
|
|
15534
|
+
return [pinned];
|
|
15535
|
+
}
|
|
15536
|
+
return getProviderBundle(providerModels, agent, externalSignals, versionRecencyMap);
|
|
15537
|
+
});
|
|
15538
|
+
const nonFreePerProviderBest = perProviderBest.filter((model) => !model.startsWith("opencode/"));
|
|
15539
|
+
const freePerProviderBest = perProviderBest.filter((model) => model.startsWith("opencode/"));
|
|
15540
|
+
const selectedOpencode = getSelectedOpenCodeForAgent(agent);
|
|
15541
|
+
const selectedChutes = getSelectedChutesForAgent(agent);
|
|
15542
|
+
const chain = dedupe2([
|
|
14590
15543
|
primary.model,
|
|
14591
|
-
...
|
|
15544
|
+
...nonFreePerProviderBest,
|
|
14592
15545
|
selectedChutes,
|
|
14593
15546
|
selectedOpencode,
|
|
14594
|
-
|
|
14595
|
-
])
|
|
15547
|
+
...freePerProviderBest
|
|
15548
|
+
]);
|
|
15549
|
+
const deterministicFreeTail = selectedOpencode ?? freePerProviderBest[0] ?? ranked.find((model) => model.model.startsWith("opencode/"))?.model;
|
|
15550
|
+
const finalizedChain = finalizeChainWithTail(chain, deterministicFreeTail);
|
|
15551
|
+
const providerPolicyChain = dedupe2([selectedChutes, selectedOpencode]);
|
|
15552
|
+
const systemDefaultModel = selectedOpencode ?? "opencode/big-pickle";
|
|
15553
|
+
const resolved = resolveAgentWithPrecedence({
|
|
15554
|
+
agentName: agent,
|
|
15555
|
+
dynamicRecommendation: finalizedChain,
|
|
15556
|
+
providerFallbackPolicy: providerPolicyChain,
|
|
15557
|
+
systemDefault: [systemDefaultModel]
|
|
15558
|
+
});
|
|
15559
|
+
let finalModel = resolved.model;
|
|
15560
|
+
let finalChain = resolved.chain;
|
|
15561
|
+
const selectedChutesForAgent = getSelectedChutesForAgent(agent);
|
|
15562
|
+
const selectedOpenCodeForAgent = getSelectedOpenCodeForAgent(agent);
|
|
15563
|
+
const forceChutes = finalModel.startsWith("chutes/") && Boolean(selectedChutesForAgent);
|
|
15564
|
+
const forceOpenCode = finalModel.startsWith("opencode/") && Boolean(selectedOpenCodeForAgent);
|
|
15565
|
+
if (forceOpenCode && selectedOpenCodeForAgent) {
|
|
15566
|
+
finalModel = selectedOpenCodeForAgent;
|
|
15567
|
+
finalChain = dedupe2([selectedOpenCodeForAgent, ...finalChain]);
|
|
15568
|
+
}
|
|
15569
|
+
if (forceChutes && selectedChutesForAgent) {
|
|
15570
|
+
finalModel = selectedChutesForAgent;
|
|
15571
|
+
finalChain = dedupe2([selectedChutesForAgent, ...finalChain]);
|
|
15572
|
+
}
|
|
15573
|
+
const wasForced = forceChutes || forceOpenCode;
|
|
14596
15574
|
agents[agent] = {
|
|
14597
|
-
model:
|
|
15575
|
+
model: finalModel,
|
|
14598
15576
|
variant: ROLE_VARIANT[agent]
|
|
14599
15577
|
};
|
|
14600
|
-
chains[agent] =
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
|
|
15578
|
+
chains[agent] = finalChain;
|
|
15579
|
+
provenance[agent] = {
|
|
15580
|
+
winnerLayer: wasForced ? "manual-user-plan" : resolved.provenance.winnerLayer,
|
|
15581
|
+
winnerModel: finalModel
|
|
15582
|
+
};
|
|
14604
15583
|
}
|
|
14605
|
-
|
|
14606
|
-
|
|
14607
|
-
|
|
14608
|
-
|
|
15584
|
+
if (hasPaidProviderEnabled) {
|
|
15585
|
+
for (const providerID of paidProviders) {
|
|
15586
|
+
if ((providerUsage.get(providerID) ?? 0) > 0)
|
|
15587
|
+
continue;
|
|
15588
|
+
let bestSwap;
|
|
15589
|
+
for (const agent of PRIMARY_ASSIGNMENT_ORDER) {
|
|
15590
|
+
const currentModel = agents[agent]?.model;
|
|
15591
|
+
if (!currentModel)
|
|
15592
|
+
continue;
|
|
15593
|
+
const ranked = getRankedModels(agent);
|
|
15594
|
+
const pinned = getPinnedModelForProvider(agent, providerID);
|
|
15595
|
+
const candidate = ranked.find((model) => model.model === pinned) ?? ranked.find((model) => model.providerID === providerID);
|
|
15596
|
+
const current = ranked.find((model) => model.model === currentModel);
|
|
15597
|
+
if (!candidate || !current)
|
|
15598
|
+
continue;
|
|
15599
|
+
const currentScore = combinedScore(agent, current, externalSignals, versionRecencyMap);
|
|
15600
|
+
const candidateScore = combinedScore(agent, candidate, externalSignals, versionRecencyMap);
|
|
15601
|
+
const loss = currentScore - candidateScore;
|
|
15602
|
+
if (!bestSwap || loss < bestSwap.loss) {
|
|
15603
|
+
bestSwap = {
|
|
15604
|
+
agent,
|
|
15605
|
+
candidateModel: candidate.model,
|
|
15606
|
+
loss
|
|
15607
|
+
};
|
|
15608
|
+
}
|
|
15609
|
+
}
|
|
15610
|
+
if (!bestSwap)
|
|
15611
|
+
continue;
|
|
15612
|
+
const existingProvider = agents[bestSwap.agent]?.model.split("/")[0] ?? providerID;
|
|
15613
|
+
agents[bestSwap.agent].model = bestSwap.candidateModel;
|
|
15614
|
+
chains[bestSwap.agent] = dedupe2([
|
|
15615
|
+
bestSwap.candidateModel,
|
|
15616
|
+
...chains[bestSwap.agent] ?? []
|
|
15617
|
+
]).slice(0, 10);
|
|
15618
|
+
provenance[bestSwap.agent] = {
|
|
15619
|
+
winnerLayer: "provider-fallback-policy",
|
|
15620
|
+
winnerModel: bestSwap.candidateModel
|
|
15621
|
+
};
|
|
15622
|
+
providerUsage.set(providerID, (providerUsage.get(providerID) ?? 0) + 1);
|
|
15623
|
+
providerUsage.set(existingProvider, Math.max(0, (providerUsage.get(existingProvider) ?? 1) - 1));
|
|
15624
|
+
}
|
|
15625
|
+
}
|
|
15626
|
+
if (config2.balanceProviderUsage && hasPaidProviderEnabled) {
|
|
15627
|
+
rebalanceForSubscriptionMode(agents, chains, provenance, paidProviders, getRankedModels, getPinnedModelForProvider, targetByProvider, externalSignals, versionRecencyMap, engineVersion);
|
|
15628
|
+
}
|
|
15629
|
+
if (Object.keys(agents).length === 0) {
|
|
15630
|
+
return null;
|
|
15631
|
+
}
|
|
15632
|
+
return {
|
|
15633
|
+
agents,
|
|
15634
|
+
chains,
|
|
15635
|
+
provenance,
|
|
15636
|
+
scoring: {
|
|
15637
|
+
engineVersionApplied: engineVersion === "v2" ? "v2" : "v1",
|
|
15638
|
+
shadowCompared: engineVersion === "v2-shadow",
|
|
15639
|
+
diffs: engineVersion === "v2-shadow" ? shadowDiffs : undefined
|
|
15640
|
+
}
|
|
15641
|
+
};
|
|
15642
|
+
}
|
|
15643
|
+
// src/utils/logger.ts
|
|
15644
|
+
import * as os from "os";
|
|
15645
|
+
import * as path from "path";
|
|
15646
|
+
var logFile = path.join(os.tmpdir(), "oh-my-opencode-slim.log");
|
|
15647
|
+
// src/utils/env.ts
|
|
15648
|
+
function getEnv(name) {
|
|
15649
|
+
const bunValue = globalThis.Bun?.env?.[name];
|
|
15650
|
+
if (typeof bunValue === "string" && bunValue.length > 0)
|
|
15651
|
+
return bunValue;
|
|
15652
|
+
const processValue = globalThis.process?.env?.[name];
|
|
15653
|
+
return typeof processValue === "string" && processValue.length > 0 ? processValue : undefined;
|
|
15654
|
+
}
|
|
15655
|
+
// src/cli/external-rankings.ts
|
|
15656
|
+
function normalizeKey(input) {
|
|
15657
|
+
return input.trim().toLowerCase();
|
|
15658
|
+
}
|
|
15659
|
+
function baseAliases(key) {
|
|
15660
|
+
return buildModelKeyAliases(normalizeKey(key));
|
|
15661
|
+
}
|
|
15662
|
+
function providerScopedAlias(alias, providerPrefix) {
|
|
15663
|
+
if (!providerPrefix || alias.includes("/"))
|
|
15664
|
+
return alias;
|
|
15665
|
+
return `${providerPrefix}/${alias}`;
|
|
15666
|
+
}
|
|
15667
|
+
function mergeSignal(existing, incoming) {
|
|
15668
|
+
if (!existing)
|
|
15669
|
+
return incoming;
|
|
15670
|
+
return {
|
|
15671
|
+
qualityScore: incoming.qualityScore ?? existing.qualityScore,
|
|
15672
|
+
codingScore: incoming.codingScore ?? existing.codingScore,
|
|
15673
|
+
latencySeconds: incoming.latencySeconds ?? existing.latencySeconds,
|
|
15674
|
+
inputPricePer1M: incoming.inputPricePer1M ?? existing.inputPricePer1M,
|
|
15675
|
+
outputPricePer1M: incoming.outputPricePer1M ?? existing.outputPricePer1M,
|
|
15676
|
+
source: "merged"
|
|
15677
|
+
};
|
|
15678
|
+
}
|
|
15679
|
+
function providerPrefixFromCreator(creatorSlug) {
|
|
15680
|
+
if (!creatorSlug)
|
|
15681
|
+
return;
|
|
15682
|
+
const slug = creatorSlug.toLowerCase();
|
|
15683
|
+
if (slug.includes("openai"))
|
|
15684
|
+
return "openai";
|
|
15685
|
+
if (slug.includes("anthropic"))
|
|
15686
|
+
return "anthropic";
|
|
15687
|
+
if (slug.includes("google"))
|
|
15688
|
+
return "google";
|
|
15689
|
+
if (slug.includes("chutes"))
|
|
15690
|
+
return "chutes";
|
|
15691
|
+
if (slug.includes("copilot") || slug.includes("github"))
|
|
15692
|
+
return "github-copilot";
|
|
15693
|
+
if (slug.includes("zai") || slug.includes("z-ai"))
|
|
15694
|
+
return "zai-coding-plan";
|
|
15695
|
+
if (slug.includes("kimi"))
|
|
15696
|
+
return "kimi-for-coding";
|
|
15697
|
+
if (slug.includes("opencode"))
|
|
15698
|
+
return "opencode";
|
|
15699
|
+
return;
|
|
15700
|
+
}
|
|
15701
|
+
function parseOpenRouterPrice(value) {
|
|
15702
|
+
if (!value)
|
|
15703
|
+
return;
|
|
15704
|
+
const parsed = Number.parseFloat(value);
|
|
15705
|
+
if (!Number.isFinite(parsed))
|
|
15706
|
+
return;
|
|
15707
|
+
return parsed * 1e6;
|
|
15708
|
+
}
|
|
15709
|
+
async function fetchJsonWithTimeout(url2, init, timeoutMs) {
|
|
15710
|
+
const controller = new AbortController;
|
|
15711
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
15712
|
+
try {
|
|
15713
|
+
return await fetch(url2, {
|
|
15714
|
+
...init,
|
|
15715
|
+
signal: controller.signal
|
|
15716
|
+
});
|
|
15717
|
+
} finally {
|
|
15718
|
+
clearTimeout(timer);
|
|
15719
|
+
}
|
|
15720
|
+
}
|
|
15721
|
+
async function fetchArtificialAnalysisSignals(apiKey) {
|
|
15722
|
+
const response = await fetchJsonWithTimeout("https://artificialanalysis.ai/api/v2/data/llms/models", {
|
|
15723
|
+
headers: {
|
|
15724
|
+
"x-api-key": apiKey
|
|
15725
|
+
}
|
|
15726
|
+
}, 8000);
|
|
15727
|
+
if (!response.ok) {
|
|
15728
|
+
throw new Error(`Artificial Analysis request failed (${response.status} ${response.statusText})`);
|
|
15729
|
+
}
|
|
15730
|
+
const parsed = await response.json();
|
|
15731
|
+
const map2 = {};
|
|
15732
|
+
for (const model of parsed.data ?? []) {
|
|
15733
|
+
const baseSignal = {
|
|
15734
|
+
qualityScore: model.evaluations?.artificial_analysis_intelligence_index,
|
|
15735
|
+
codingScore: model.evaluations?.artificial_analysis_coding_index ?? model.evaluations?.livecodebench,
|
|
15736
|
+
latencySeconds: model.median_time_to_first_token_seconds,
|
|
15737
|
+
inputPricePer1M: model.pricing?.price_1m_input_tokens ?? model.pricing?.price_1m_blended_3_to_1,
|
|
15738
|
+
outputPricePer1M: model.pricing?.price_1m_output_tokens ?? model.pricing?.price_1m_blended_3_to_1,
|
|
15739
|
+
source: "artificial-analysis"
|
|
15740
|
+
};
|
|
15741
|
+
const id = model.id ? normalizeKey(model.id) : undefined;
|
|
15742
|
+
const slug = model.slug ? normalizeKey(model.slug) : undefined;
|
|
15743
|
+
const name = model.name ? normalizeKey(model.name) : undefined;
|
|
15744
|
+
const providerPrefix = providerPrefixFromCreator(model.model_creator?.slug);
|
|
15745
|
+
for (const key of [id, slug, name]) {
|
|
15746
|
+
if (!key)
|
|
15747
|
+
continue;
|
|
15748
|
+
for (const alias of baseAliases(key)) {
|
|
15749
|
+
if (!providerPrefix || alias.includes("/")) {
|
|
15750
|
+
map2[alias] = mergeSignal(map2[alias], baseSignal);
|
|
15751
|
+
}
|
|
15752
|
+
const scopedAlias = providerScopedAlias(alias, providerPrefix);
|
|
15753
|
+
map2[scopedAlias] = mergeSignal(map2[scopedAlias], baseSignal);
|
|
15754
|
+
}
|
|
15755
|
+
}
|
|
15756
|
+
}
|
|
15757
|
+
return map2;
|
|
15758
|
+
}
|
|
15759
|
+
async function fetchOpenRouterSignals(apiKey) {
|
|
15760
|
+
const response = await fetchJsonWithTimeout("https://openrouter.ai/api/v1/models", {
|
|
15761
|
+
headers: {
|
|
15762
|
+
Authorization: `Bearer ${apiKey}`
|
|
15763
|
+
}
|
|
15764
|
+
}, 8000);
|
|
15765
|
+
if (!response.ok) {
|
|
15766
|
+
throw new Error(`OpenRouter request failed (${response.status} ${response.statusText})`);
|
|
15767
|
+
}
|
|
15768
|
+
const parsed = await response.json();
|
|
15769
|
+
const map2 = {};
|
|
15770
|
+
for (const model of parsed.data ?? []) {
|
|
15771
|
+
if (!model.id)
|
|
15772
|
+
continue;
|
|
15773
|
+
const key = normalizeKey(model.id);
|
|
15774
|
+
const providerPrefix = key.split("/")[0];
|
|
15775
|
+
const signal = {
|
|
15776
|
+
inputPricePer1M: parseOpenRouterPrice(model.pricing?.prompt),
|
|
15777
|
+
outputPricePer1M: parseOpenRouterPrice(model.pricing?.completion),
|
|
15778
|
+
source: "openrouter"
|
|
15779
|
+
};
|
|
15780
|
+
for (const alias of baseAliases(key)) {
|
|
15781
|
+
if (alias.includes("/")) {
|
|
15782
|
+
map2[alias] = mergeSignal(map2[alias], signal);
|
|
15783
|
+
}
|
|
15784
|
+
const scopedAlias = providerScopedAlias(alias, providerPrefix);
|
|
15785
|
+
map2[scopedAlias] = mergeSignal(map2[scopedAlias], signal);
|
|
15786
|
+
}
|
|
15787
|
+
}
|
|
15788
|
+
return map2;
|
|
15789
|
+
}
|
|
15790
|
+
async function fetchExternalModelSignals(options) {
|
|
15791
|
+
const warnings = [];
|
|
15792
|
+
const aggregate = {};
|
|
15793
|
+
const aaKey = options?.artificialAnalysisApiKey ?? getEnv("ARTIFICIAL_ANALYSIS_API_KEY");
|
|
15794
|
+
const orKey = options?.openRouterApiKey ?? getEnv("OPENROUTER_API_KEY");
|
|
15795
|
+
const aaPromise = aaKey ? fetchArtificialAnalysisSignals(aaKey) : Promise.resolve({});
|
|
15796
|
+
const orPromise = orKey ? fetchOpenRouterSignals(orKey) : Promise.resolve({});
|
|
15797
|
+
const [aaResult, orResult] = await Promise.allSettled([aaPromise, orPromise]);
|
|
15798
|
+
if (aaResult.status === "fulfilled") {
|
|
15799
|
+
for (const [key, signal] of Object.entries(aaResult.value)) {
|
|
15800
|
+
aggregate[key] = mergeSignal(aggregate[key], signal);
|
|
15801
|
+
}
|
|
15802
|
+
} else if (aaKey) {
|
|
15803
|
+
warnings.push(`Artificial Analysis unavailable: ${aaResult.reason instanceof Error ? aaResult.reason.message : String(aaResult.reason)}`);
|
|
15804
|
+
}
|
|
15805
|
+
if (orResult.status === "fulfilled") {
|
|
15806
|
+
for (const [key, signal] of Object.entries(orResult.value)) {
|
|
15807
|
+
aggregate[key] = mergeSignal(aggregate[key], signal);
|
|
15808
|
+
}
|
|
15809
|
+
} else if (orKey) {
|
|
15810
|
+
warnings.push(`OpenRouter unavailable: ${orResult.reason instanceof Error ? orResult.reason.message : String(orResult.reason)}`);
|
|
15811
|
+
}
|
|
15812
|
+
return { signals: aggregate, warnings };
|
|
15813
|
+
}
|
|
15814
|
+
// src/cli/system.ts
|
|
15815
|
+
import { statSync as statSync3 } from "fs";
|
|
15816
|
+
var cachedOpenCodePath = null;
|
|
15817
|
+
function getOpenCodePaths() {
|
|
15818
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
15819
|
+
return [
|
|
15820
|
+
"opencode",
|
|
15821
|
+
`${home}/.local/bin/opencode`,
|
|
15822
|
+
`${home}/.opencode/bin/opencode`,
|
|
15823
|
+
`${home}/bin/opencode`,
|
|
15824
|
+
"/usr/local/bin/opencode",
|
|
15825
|
+
"/opt/opencode/bin/opencode",
|
|
15826
|
+
"/usr/bin/opencode",
|
|
15827
|
+
"/bin/opencode",
|
|
15828
|
+
"/Applications/OpenCode.app/Contents/MacOS/opencode",
|
|
15829
|
+
`${home}/Applications/OpenCode.app/Contents/MacOS/opencode`,
|
|
15830
|
+
"/opt/homebrew/bin/opencode",
|
|
15831
|
+
"/home/linuxbrew/.linuxbrew/bin/opencode",
|
|
15832
|
+
`${home}/homebrew/bin/opencode`,
|
|
15833
|
+
`${home}/Library/Application Support/opencode/bin/opencode`,
|
|
15834
|
+
"/snap/bin/opencode",
|
|
15835
|
+
"/var/snap/opencode/current/bin/opencode",
|
|
15836
|
+
"/var/lib/flatpak/exports/bin/ai.opencode.OpenCode",
|
|
15837
|
+
`${home}/.local/share/flatpak/exports/bin/ai.opencode.OpenCode`,
|
|
15838
|
+
"/nix/store/opencode/bin/opencode",
|
|
15839
|
+
`${home}/.nix-profile/bin/opencode`,
|
|
15840
|
+
"/run/current-system/sw/bin/opencode",
|
|
15841
|
+
`${home}/.cargo/bin/opencode`,
|
|
15842
|
+
`${home}/.npm-global/bin/opencode`,
|
|
15843
|
+
"/usr/local/lib/node_modules/opencode/bin/opencode",
|
|
15844
|
+
`${home}/.yarn/bin/opencode`,
|
|
15845
|
+
`${home}/.pnpm-global/bin/opencode`
|
|
15846
|
+
];
|
|
15847
|
+
}
|
|
15848
|
+
function resolveOpenCodePath() {
|
|
15849
|
+
if (cachedOpenCodePath) {
|
|
15850
|
+
return cachedOpenCodePath;
|
|
15851
|
+
}
|
|
15852
|
+
const paths = getOpenCodePaths();
|
|
15853
|
+
for (const opencodePath of paths) {
|
|
15854
|
+
if (opencodePath === "opencode")
|
|
15855
|
+
continue;
|
|
15856
|
+
try {
|
|
15857
|
+
const stat = statSync3(opencodePath);
|
|
15858
|
+
if (stat.isFile()) {
|
|
15859
|
+
cachedOpenCodePath = opencodePath;
|
|
15860
|
+
return opencodePath;
|
|
15861
|
+
}
|
|
15862
|
+
} catch {}
|
|
15863
|
+
}
|
|
15864
|
+
return "opencode";
|
|
15865
|
+
}
|
|
15866
|
+
async function isOpenCodeInstalled() {
|
|
15867
|
+
const paths = getOpenCodePaths();
|
|
15868
|
+
for (const opencodePath of paths) {
|
|
15869
|
+
try {
|
|
15870
|
+
const proc = Bun.spawn([opencodePath, "--version"], {
|
|
15871
|
+
stdout: "pipe",
|
|
15872
|
+
stderr: "pipe"
|
|
15873
|
+
});
|
|
15874
|
+
await proc.exited;
|
|
15875
|
+
if (proc.exitCode === 0) {
|
|
15876
|
+
cachedOpenCodePath = opencodePath;
|
|
15877
|
+
return true;
|
|
15878
|
+
}
|
|
15879
|
+
} catch {}
|
|
15880
|
+
}
|
|
15881
|
+
return false;
|
|
15882
|
+
}
|
|
15883
|
+
async function getOpenCodeVersion() {
|
|
15884
|
+
const opencodePath = resolveOpenCodePath();
|
|
15885
|
+
try {
|
|
15886
|
+
const proc = Bun.spawn([opencodePath, "--version"], {
|
|
15887
|
+
stdout: "pipe",
|
|
15888
|
+
stderr: "pipe"
|
|
15889
|
+
});
|
|
15890
|
+
const output = await new Response(proc.stdout).text();
|
|
15891
|
+
await proc.exited;
|
|
15892
|
+
if (proc.exitCode === 0) {
|
|
15893
|
+
return output.trim();
|
|
15894
|
+
}
|
|
15895
|
+
} catch {}
|
|
15896
|
+
return null;
|
|
15897
|
+
}
|
|
15898
|
+
function getOpenCodePath() {
|
|
15899
|
+
const path2 = resolveOpenCodePath();
|
|
15900
|
+
return path2 === "opencode" ? null : path2;
|
|
15901
|
+
}
|
|
15902
|
+
|
|
15903
|
+
// src/cli/opencode-models.ts
|
|
15904
|
+
function isFreeModel(record2) {
|
|
14609
15905
|
const inputCost = record2.cost?.input ?? 0;
|
|
14610
15906
|
const outputCost = record2.cost?.output ?? 0;
|
|
14611
15907
|
const cacheReadCost = record2.cost?.cache?.read ?? 0;
|
|
@@ -14646,12 +15942,12 @@ function normalizeDiscoveredModel(record2, providerFilter) {
|
|
|
14646
15942
|
function parseOpenCodeModelsVerboseOutput(output, providerFilter, freeOnly = true) {
|
|
14647
15943
|
const lines = output.split(/\r?\n/);
|
|
14648
15944
|
const models = [];
|
|
15945
|
+
const modelHeaderPattern = /^[a-z0-9-]+\/.+$/i;
|
|
14649
15946
|
for (let index = 0;index < lines.length; index++) {
|
|
14650
15947
|
const line = lines[index]?.trim();
|
|
14651
15948
|
if (!line || !line.includes("/"))
|
|
14652
15949
|
continue;
|
|
14653
|
-
|
|
14654
|
-
if (!isModelHeader)
|
|
15950
|
+
if (!modelHeaderPattern.test(line))
|
|
14655
15951
|
continue;
|
|
14656
15952
|
let jsonStart = -1;
|
|
14657
15953
|
for (let search = index + 1;search < lines.length; search++) {
|
|
@@ -14659,7 +15955,7 @@ function parseOpenCodeModelsVerboseOutput(output, providerFilter, freeOnly = tru
|
|
|
14659
15955
|
jsonStart = search;
|
|
14660
15956
|
break;
|
|
14661
15957
|
}
|
|
14662
|
-
if (
|
|
15958
|
+
if (modelHeaderPattern.test(lines[search]?.trim() ?? "")) {
|
|
14663
15959
|
break;
|
|
14664
15960
|
}
|
|
14665
15961
|
}
|
|
@@ -14699,9 +15995,10 @@ function parseOpenCodeModelsVerboseOutput(output, providerFilter, freeOnly = tru
|
|
|
14699
15995
|
}
|
|
14700
15996
|
return models;
|
|
14701
15997
|
}
|
|
14702
|
-
async function
|
|
15998
|
+
async function discoverModelsByProvider(providerID, freeOnly = true) {
|
|
14703
15999
|
try {
|
|
14704
|
-
const
|
|
16000
|
+
const opencodePath = resolveOpenCodePath();
|
|
16001
|
+
const proc = Bun.spawn([opencodePath, "models", "--refresh", "--verbose"], {
|
|
14705
16002
|
stdout: "pipe",
|
|
14706
16003
|
stderr: "pipe"
|
|
14707
16004
|
});
|
|
@@ -14715,7 +16012,7 @@ async function discoverFreeModelsByProvider(providerID) {
|
|
|
14715
16012
|
};
|
|
14716
16013
|
}
|
|
14717
16014
|
return {
|
|
14718
|
-
models: parseOpenCodeModelsVerboseOutput(stdout, providerID,
|
|
16015
|
+
models: parseOpenCodeModelsVerboseOutput(stdout, providerID, freeOnly)
|
|
14719
16016
|
};
|
|
14720
16017
|
} catch {
|
|
14721
16018
|
return {
|
|
@@ -14726,7 +16023,8 @@ async function discoverFreeModelsByProvider(providerID) {
|
|
|
14726
16023
|
}
|
|
14727
16024
|
async function discoverModelCatalog() {
|
|
14728
16025
|
try {
|
|
14729
|
-
const
|
|
16026
|
+
const opencodePath = resolveOpenCodePath();
|
|
16027
|
+
const proc = Bun.spawn([opencodePath, "models", "--refresh", "--verbose"], {
|
|
14730
16028
|
stdout: "pipe",
|
|
14731
16029
|
stderr: "pipe"
|
|
14732
16030
|
});
|
|
@@ -14750,10 +16048,11 @@ async function discoverModelCatalog() {
|
|
|
14750
16048
|
}
|
|
14751
16049
|
}
|
|
14752
16050
|
async function discoverOpenCodeFreeModels() {
|
|
14753
|
-
|
|
16051
|
+
const result = await discoverModelsByProvider("opencode", true);
|
|
16052
|
+
return { models: result.models, error: result.error };
|
|
14754
16053
|
}
|
|
14755
|
-
async function
|
|
14756
|
-
return
|
|
16054
|
+
async function discoverProviderModels(providerID) {
|
|
16055
|
+
return discoverModelsByProvider(providerID, false);
|
|
14757
16056
|
}
|
|
14758
16057
|
// src/cli/opencode-selection.ts
|
|
14759
16058
|
var scoreOpenCodePrimaryForCoding = (model) => {
|
|
@@ -14785,91 +16084,6 @@ function pickSupportOpenCodeModel(models, primaryModel) {
|
|
|
14785
16084
|
}, primaryModel);
|
|
14786
16085
|
return support;
|
|
14787
16086
|
}
|
|
14788
|
-
// src/cli/system.ts
|
|
14789
|
-
async function isOpenCodeInstalled() {
|
|
14790
|
-
try {
|
|
14791
|
-
const proc = Bun.spawn(["opencode", "--version"], {
|
|
14792
|
-
stdout: "pipe",
|
|
14793
|
-
stderr: "pipe"
|
|
14794
|
-
});
|
|
14795
|
-
await proc.exited;
|
|
14796
|
-
return proc.exitCode === 0;
|
|
14797
|
-
} catch {
|
|
14798
|
-
return false;
|
|
14799
|
-
}
|
|
14800
|
-
}
|
|
14801
|
-
async function getOpenCodeVersion() {
|
|
14802
|
-
try {
|
|
14803
|
-
const proc = Bun.spawn(["opencode", "--version"], {
|
|
14804
|
-
stdout: "pipe",
|
|
14805
|
-
stderr: "pipe"
|
|
14806
|
-
});
|
|
14807
|
-
const output = await new Response(proc.stdout).text();
|
|
14808
|
-
await proc.exited;
|
|
14809
|
-
return proc.exitCode === 0 ? output.trim() : null;
|
|
14810
|
-
} catch {
|
|
14811
|
-
return null;
|
|
14812
|
-
}
|
|
14813
|
-
}
|
|
14814
|
-
// src/cli/custom-skills.ts
|
|
14815
|
-
import {
|
|
14816
|
-
copyFileSync as copyFileSync2,
|
|
14817
|
-
existsSync as existsSync3,
|
|
14818
|
-
mkdirSync as mkdirSync2,
|
|
14819
|
-
readdirSync,
|
|
14820
|
-
statSync as statSync2
|
|
14821
|
-
} from "fs";
|
|
14822
|
-
import { homedir as homedir2 } from "os";
|
|
14823
|
-
import { dirname, join as join2 } from "path";
|
|
14824
|
-
import { fileURLToPath } from "url";
|
|
14825
|
-
var CUSTOM_SKILLS = [
|
|
14826
|
-
{
|
|
14827
|
-
name: "cartography",
|
|
14828
|
-
description: "Repository understanding and hierarchical codemap generation",
|
|
14829
|
-
allowedAgents: ["orchestrator"],
|
|
14830
|
-
sourcePath: "src/skills/cartography"
|
|
14831
|
-
}
|
|
14832
|
-
];
|
|
14833
|
-
function getCustomSkillsDir() {
|
|
14834
|
-
return join2(homedir2(), ".config", "opencode", "skills");
|
|
14835
|
-
}
|
|
14836
|
-
function copyDirRecursive(src, dest) {
|
|
14837
|
-
if (!existsSync3(dest)) {
|
|
14838
|
-
mkdirSync2(dest, { recursive: true });
|
|
14839
|
-
}
|
|
14840
|
-
const entries = readdirSync(src);
|
|
14841
|
-
for (const entry of entries) {
|
|
14842
|
-
const srcPath = join2(src, entry);
|
|
14843
|
-
const destPath = join2(dest, entry);
|
|
14844
|
-
const stat = statSync2(srcPath);
|
|
14845
|
-
if (stat.isDirectory()) {
|
|
14846
|
-
copyDirRecursive(srcPath, destPath);
|
|
14847
|
-
} else {
|
|
14848
|
-
const destDir = dirname(destPath);
|
|
14849
|
-
if (!existsSync3(destDir)) {
|
|
14850
|
-
mkdirSync2(destDir, { recursive: true });
|
|
14851
|
-
}
|
|
14852
|
-
copyFileSync2(srcPath, destPath);
|
|
14853
|
-
}
|
|
14854
|
-
}
|
|
14855
|
-
}
|
|
14856
|
-
function installCustomSkill(skill) {
|
|
14857
|
-
try {
|
|
14858
|
-
const packageRoot = fileURLToPath(new URL("../..", import.meta.url));
|
|
14859
|
-
const sourcePath = join2(packageRoot, skill.sourcePath);
|
|
14860
|
-
const targetPath = join2(getCustomSkillsDir(), skill.name);
|
|
14861
|
-
if (!existsSync3(sourcePath)) {
|
|
14862
|
-
console.error(`Custom skill source not found: ${sourcePath}`);
|
|
14863
|
-
return false;
|
|
14864
|
-
}
|
|
14865
|
-
copyDirRecursive(sourcePath, targetPath);
|
|
14866
|
-
return true;
|
|
14867
|
-
} catch (error48) {
|
|
14868
|
-
console.error(`Failed to install custom skill: ${skill.name}`, error48);
|
|
14869
|
-
return false;
|
|
14870
|
-
}
|
|
14871
|
-
}
|
|
14872
|
-
|
|
14873
16087
|
// src/cli/install.ts
|
|
14874
16088
|
var GREEN = "\x1B[32m";
|
|
14875
16089
|
var BLUE = "\x1B[34m";
|
|
@@ -14914,11 +16128,16 @@ async function checkOpenCodeInstalled() {
|
|
|
14914
16128
|
printError("OpenCode is not installed on this system.");
|
|
14915
16129
|
printInfo("Install it with:");
|
|
14916
16130
|
console.log(` ${BLUE}curl -fsSL https://opencode.ai/install | bash${RESET}`);
|
|
16131
|
+
console.log();
|
|
16132
|
+
printInfo("Or if already installed, add it to your PATH:");
|
|
16133
|
+
console.log(` ${BLUE}export PATH="$HOME/.local/bin:$PATH"${RESET}`);
|
|
16134
|
+
console.log(` ${BLUE}export PATH="$HOME/.opencode/bin:$PATH"${RESET}`);
|
|
14917
16135
|
return { ok: false };
|
|
14918
16136
|
}
|
|
14919
16137
|
const version2 = await getOpenCodeVersion();
|
|
14920
|
-
|
|
14921
|
-
|
|
16138
|
+
const path2 = getOpenCodePath();
|
|
16139
|
+
printSuccess(`OpenCode ${version2 ?? ""} detected${path2 ? ` (${DIM}${path2}${RESET})` : ""}`);
|
|
16140
|
+
return { ok: true, version: version2 ?? undefined, path: path2 ?? undefined };
|
|
14922
16141
|
}
|
|
14923
16142
|
function handleStepResult(result, successMsg) {
|
|
14924
16143
|
if (!result.success) {
|
|
@@ -14943,6 +16162,7 @@ function formatConfigSummary(config2) {
|
|
|
14943
16162
|
lines.push(` ${config2.hasAntigravity ? SYMBOLS.check : `${DIM}\u25CB${RESET}`} Antigravity (Google)`);
|
|
14944
16163
|
lines.push(` ${config2.hasChutes ? SYMBOLS.check : `${DIM}\u25CB${RESET}`} Chutes`);
|
|
14945
16164
|
lines.push(` ${SYMBOLS.check} Opencode Zen`);
|
|
16165
|
+
lines.push(` ${config2.balanceProviderUsage ? SYMBOLS.check : `${DIM}\u25CB${RESET}`} Balanced provider spend`);
|
|
14946
16166
|
if (config2.useOpenCodeFreeModels && config2.selectedOpenCodePrimaryModel) {
|
|
14947
16167
|
lines.push(` ${SYMBOLS.check} OpenCode Free Primary: ${BLUE}${config2.selectedOpenCodePrimaryModel}${RESET}`);
|
|
14948
16168
|
}
|
|
@@ -14988,9 +16208,15 @@ function argsToConfig(args) {
|
|
|
14988
16208
|
hasOpencodeZen: true,
|
|
14989
16209
|
useOpenCodeFreeModels: args.opencodeFree === "yes",
|
|
14990
16210
|
preferredOpenCodeModel: args.opencodeFreeModel && args.opencodeFreeModel !== "auto" ? args.opencodeFreeModel : undefined,
|
|
16211
|
+
artificialAnalysisApiKey: args.aaKey,
|
|
16212
|
+
openRouterApiKey: args.openrouterKey,
|
|
16213
|
+
balanceProviderUsage: args.balancedSpend === "yes",
|
|
14991
16214
|
hasTmux: args.tmux === "yes",
|
|
14992
16215
|
installSkills: args.skills === "yes",
|
|
14993
|
-
installCustomSkills: args.skills === "yes"
|
|
16216
|
+
installCustomSkills: args.skills === "yes",
|
|
16217
|
+
setupMode: "quick",
|
|
16218
|
+
dryRun: args.dryRun,
|
|
16219
|
+
modelsOnly: args.modelsOnly
|
|
14994
16220
|
};
|
|
14995
16221
|
}
|
|
14996
16222
|
async function askModelSelection(rl, models, defaultModel, prompt) {
|
|
@@ -15022,20 +16248,310 @@ async function askYesNo(rl, prompt, defaultValue = "no") {
|
|
|
15022
16248
|
return "no";
|
|
15023
16249
|
return defaultValue;
|
|
15024
16250
|
}
|
|
15025
|
-
async function
|
|
16251
|
+
async function askOptionalApiKey(rl, prompt, fromEnv) {
|
|
16252
|
+
const hint = fromEnv ? "[optional, Enter keeps env value]" : "[optional]";
|
|
16253
|
+
const answer = (await rl.question(`${BLUE}${prompt}${RESET} ${DIM}${hint}${RESET}: `)).trim();
|
|
16254
|
+
if (!answer)
|
|
16255
|
+
return fromEnv;
|
|
16256
|
+
return answer;
|
|
16257
|
+
}
|
|
16258
|
+
async function askSetupMode(rl) {
|
|
16259
|
+
console.log(`${BOLD}Choose setup mode:${RESET}`);
|
|
16260
|
+
console.log(` ${DIM}1.${RESET} Quick setup - you choose providers, we auto-pick models`);
|
|
16261
|
+
console.log(` ${DIM}2.${RESET} Manual setup - you choose providers and models per agent`);
|
|
16262
|
+
console.log();
|
|
16263
|
+
const answer = (await rl.question(`${BLUE}Selection${RESET} ${DIM}[default: 1]${RESET}: `)).trim().toLowerCase();
|
|
16264
|
+
if (answer === "2" || answer === "manual")
|
|
16265
|
+
return "manual";
|
|
16266
|
+
return "quick";
|
|
16267
|
+
}
|
|
16268
|
+
async function askModelByNumber(rl, models, prompt, allowEmpty = false) {
|
|
16269
|
+
let showAll = false;
|
|
16270
|
+
while (true) {
|
|
16271
|
+
console.log(`${BOLD}${prompt}${RESET}`);
|
|
16272
|
+
console.log(`${DIM}Available models:${RESET}`);
|
|
16273
|
+
const modelsToShow = showAll ? models : models.slice(0, 5);
|
|
16274
|
+
const remainingCount = models.length - modelsToShow.length;
|
|
16275
|
+
for (const [index, model] of modelsToShow.entries()) {
|
|
16276
|
+
const displayIndex = showAll ? index + 1 : index + 1;
|
|
16277
|
+
const name = model.name ? ` ${DIM}(${model.name})${RESET}` : "";
|
|
16278
|
+
console.log(` ${DIM}${displayIndex}.${RESET} ${BLUE}${model.model}${RESET}${name}`);
|
|
16279
|
+
}
|
|
16280
|
+
if (!showAll && remainingCount > 0) {
|
|
16281
|
+
console.log(`${DIM} ... and ${remainingCount} more${RESET}`);
|
|
16282
|
+
console.log(`${DIM} (type "all" to show the full list)${RESET}`);
|
|
16283
|
+
}
|
|
16284
|
+
console.log(`${DIM} (or type any model ID directly)${RESET}`);
|
|
16285
|
+
console.log();
|
|
16286
|
+
const answer = (await rl.question(`${BLUE}Selection${RESET}: `)).trim().toLowerCase();
|
|
16287
|
+
if (!answer) {
|
|
16288
|
+
if (allowEmpty)
|
|
16289
|
+
return;
|
|
16290
|
+
return models[0]?.model;
|
|
16291
|
+
}
|
|
16292
|
+
if (answer === "all") {
|
|
16293
|
+
showAll = true;
|
|
16294
|
+
console.log();
|
|
16295
|
+
continue;
|
|
16296
|
+
}
|
|
16297
|
+
const asNumber = Number.parseInt(answer, 10);
|
|
16298
|
+
if (Number.isFinite(asNumber) && asNumber >= 1 && asNumber <= models.length) {
|
|
16299
|
+
return models[asNumber - 1]?.model;
|
|
16300
|
+
}
|
|
16301
|
+
const byId = models.find((m) => m.model.toLowerCase() === answer);
|
|
16302
|
+
if (byId)
|
|
16303
|
+
return byId.model;
|
|
16304
|
+
if (answer.includes("/"))
|
|
16305
|
+
return answer;
|
|
16306
|
+
printWarning(`Invalid selection: "${answer}". Using first available model.`);
|
|
16307
|
+
return models[0]?.model;
|
|
16308
|
+
}
|
|
16309
|
+
}
|
|
16310
|
+
async function configureAgentManually(rl, agentName, allModels) {
|
|
16311
|
+
console.log();
|
|
16312
|
+
console.log(`${BOLD}Configure ${agentName}:${RESET}`);
|
|
16313
|
+
console.log();
|
|
16314
|
+
const selectedModels = new Set;
|
|
16315
|
+
const primary = await askModelByNumber(rl, allModels, "Primary model") ?? allModels[0]?.model ?? "opencode/big-pickle";
|
|
16316
|
+
selectedModels.add(primary);
|
|
16317
|
+
const availableForFallback1 = allModels.filter((m) => !selectedModels.has(m.model));
|
|
16318
|
+
const fallback1 = availableForFallback1.length > 0 ? await askModelByNumber(rl, availableForFallback1, "Fallback 1 (optional, press Enter to skip)", true) ?? primary : primary;
|
|
16319
|
+
if (fallback1 !== primary)
|
|
16320
|
+
selectedModels.add(fallback1);
|
|
16321
|
+
const availableForFallback2 = allModels.filter((m) => !selectedModels.has(m.model));
|
|
16322
|
+
const fallback2 = availableForFallback2.length > 0 ? await askModelByNumber(rl, availableForFallback2, "Fallback 2 (optional, press Enter to skip)", true) ?? fallback1 : fallback1;
|
|
16323
|
+
if (fallback2 !== fallback1)
|
|
16324
|
+
selectedModels.add(fallback2);
|
|
16325
|
+
const availableForFallback3 = allModels.filter((m) => !selectedModels.has(m.model));
|
|
16326
|
+
const fallback3 = availableForFallback3.length > 0 ? await askModelByNumber(rl, availableForFallback3, "Fallback 3 (optional, press Enter to skip)", true) ?? fallback2 : fallback2;
|
|
16327
|
+
return {
|
|
16328
|
+
primary,
|
|
16329
|
+
fallback1,
|
|
16330
|
+
fallback2,
|
|
16331
|
+
fallback3
|
|
16332
|
+
};
|
|
16333
|
+
}
|
|
16334
|
+
async function runManualSetupMode(rl, detected, modelsOnly = false) {
|
|
16335
|
+
console.log();
|
|
16336
|
+
console.log(`${BOLD}Manual Setup Mode${RESET}`);
|
|
16337
|
+
console.log("=".repeat(20));
|
|
16338
|
+
console.log();
|
|
16339
|
+
const existingAaKey = getEnv("ARTIFICIAL_ANALYSIS_API_KEY");
|
|
16340
|
+
const existingOpenRouterKey = getEnv("OPENROUTER_API_KEY");
|
|
16341
|
+
const artificialAnalysisApiKey = await askOptionalApiKey(rl, "Artificial Analysis API key for better ranking signals", existingAaKey);
|
|
16342
|
+
if (existingAaKey && !artificialAnalysisApiKey) {
|
|
16343
|
+
printInfo("Using existing ARTIFICIAL_ANALYSIS_API_KEY from environment.");
|
|
16344
|
+
}
|
|
16345
|
+
console.log();
|
|
16346
|
+
const openRouterApiKey = await askOptionalApiKey(rl, "OpenRouter API key for pricing/metadata signals", existingOpenRouterKey);
|
|
16347
|
+
if (existingOpenRouterKey && !openRouterApiKey) {
|
|
16348
|
+
printInfo("Using existing OPENROUTER_API_KEY from environment.");
|
|
16349
|
+
}
|
|
16350
|
+
console.log();
|
|
16351
|
+
const useOpenCodeFree = await askYesNo(rl, "Use Opencode Free models (opencode/*)?", "yes");
|
|
16352
|
+
console.log();
|
|
16353
|
+
let availableOpenCodeFreeModels;
|
|
16354
|
+
let availableChutesModels;
|
|
16355
|
+
if (useOpenCodeFree === "yes") {
|
|
16356
|
+
printInfo("Refreshing models with: opencode models --refresh --verbose");
|
|
16357
|
+
const discovery = await discoverOpenCodeFreeModels();
|
|
16358
|
+
if (discovery.models.length === 0) {
|
|
16359
|
+
printWarning(discovery.error ?? "No OpenCode free models found. Continuing without OpenCode free-model assignment.");
|
|
16360
|
+
} else {
|
|
16361
|
+
availableOpenCodeFreeModels = discovery.models;
|
|
16362
|
+
printSuccess(`Found ${discovery.models.length} OpenCode free models`);
|
|
16363
|
+
}
|
|
16364
|
+
console.log();
|
|
16365
|
+
}
|
|
16366
|
+
const kimi = await askYesNo(rl, "Enable Kimi provider?", detected.hasKimi ? "yes" : "no");
|
|
16367
|
+
console.log();
|
|
16368
|
+
const openai = await askYesNo(rl, "Enable OpenAI provider?", detected.hasOpenAI ? "yes" : "no");
|
|
16369
|
+
console.log();
|
|
16370
|
+
const anthropic = await askYesNo(rl, "Enable Anthropic provider?", detected.hasAnthropic ? "yes" : "no");
|
|
16371
|
+
console.log();
|
|
16372
|
+
const copilot = await askYesNo(rl, "Enable GitHub Copilot provider?", detected.hasCopilot ? "yes" : "no");
|
|
16373
|
+
console.log();
|
|
16374
|
+
const zaiPlan = await askYesNo(rl, "Enable ZAI Coding Plan provider?", detected.hasZaiPlan ? "yes" : "no");
|
|
16375
|
+
console.log();
|
|
16376
|
+
const antigravity = await askYesNo(rl, "Enable Antigravity (Google) provider?", detected.hasAntigravity ? "yes" : "no");
|
|
16377
|
+
console.log();
|
|
16378
|
+
const chutes = await askYesNo(rl, "Enable Chutes provider?", detected.hasChutes ? "yes" : "no");
|
|
16379
|
+
console.log();
|
|
16380
|
+
if (chutes === "yes") {
|
|
16381
|
+
printInfo("Refreshing Chutes model list with: opencode models --refresh --verbose");
|
|
16382
|
+
const discovery = await discoverProviderModels("chutes");
|
|
16383
|
+
if (discovery.models.length === 0) {
|
|
16384
|
+
printWarning(discovery.error ?? "No Chutes models found. Continuing without Chutes dynamic assignment.");
|
|
16385
|
+
} else {
|
|
16386
|
+
availableChutesModels = discovery.models;
|
|
16387
|
+
printSuccess(`Found ${discovery.models.length} Chutes models`);
|
|
16388
|
+
}
|
|
16389
|
+
console.log();
|
|
16390
|
+
}
|
|
16391
|
+
const availableModels = [];
|
|
16392
|
+
if (useOpenCodeFree === "yes" && availableOpenCodeFreeModels) {
|
|
16393
|
+
for (const model of availableOpenCodeFreeModels) {
|
|
16394
|
+
availableModels.push({ model: model.model, name: model.name });
|
|
16395
|
+
}
|
|
16396
|
+
}
|
|
16397
|
+
if (kimi === "yes") {
|
|
16398
|
+
availableModels.push({ model: "kimi-for-coding/k2p5", name: "Kimi K2.5" });
|
|
16399
|
+
}
|
|
16400
|
+
if (openai === "yes") {
|
|
16401
|
+
availableModels.push({
|
|
16402
|
+
model: "openai/gpt-5.3-codex",
|
|
16403
|
+
name: "GPT-5.3 Codex"
|
|
16404
|
+
});
|
|
16405
|
+
availableModels.push({
|
|
16406
|
+
model: "openai/gpt-5.1-codex-mini",
|
|
16407
|
+
name: "GPT-5.1 Codex Mini"
|
|
16408
|
+
});
|
|
16409
|
+
}
|
|
16410
|
+
if (anthropic === "yes") {
|
|
16411
|
+
availableModels.push({
|
|
16412
|
+
model: "anthropic/claude-opus-4-6",
|
|
16413
|
+
name: "Claude Opus 4.6"
|
|
16414
|
+
});
|
|
16415
|
+
availableModels.push({
|
|
16416
|
+
model: "anthropic/claude-sonnet-4-5",
|
|
16417
|
+
name: "Claude Sonnet 4.5"
|
|
16418
|
+
});
|
|
16419
|
+
availableModels.push({
|
|
16420
|
+
model: "anthropic/claude-haiku-4-5",
|
|
16421
|
+
name: "Claude Haiku 4.5"
|
|
16422
|
+
});
|
|
16423
|
+
}
|
|
16424
|
+
if (copilot === "yes") {
|
|
16425
|
+
availableModels.push({
|
|
16426
|
+
model: "github-copilot/grok-code-fast-1",
|
|
16427
|
+
name: "Grok Code Fast"
|
|
16428
|
+
});
|
|
16429
|
+
}
|
|
16430
|
+
if (zaiPlan === "yes") {
|
|
16431
|
+
availableModels.push({ model: "zai-coding-plan/glm-4.7", name: "GLM 4.7" });
|
|
16432
|
+
}
|
|
16433
|
+
if (antigravity === "yes") {
|
|
16434
|
+
availableModels.push({
|
|
16435
|
+
model: "google/antigravity-gemini-3.1-pro",
|
|
16436
|
+
name: "Gemini 3.1 Pro"
|
|
16437
|
+
});
|
|
16438
|
+
availableModels.push({
|
|
16439
|
+
model: "google/antigravity-gemini-3-flash",
|
|
16440
|
+
name: "Gemini 3 Flash"
|
|
16441
|
+
});
|
|
16442
|
+
}
|
|
16443
|
+
if (chutes === "yes" && availableChutesModels) {
|
|
16444
|
+
for (const model of availableChutesModels) {
|
|
16445
|
+
availableModels.push({ model: model.model, name: model.name });
|
|
16446
|
+
}
|
|
16447
|
+
}
|
|
16448
|
+
availableModels.push({
|
|
16449
|
+
model: "opencode/big-pickle",
|
|
16450
|
+
name: "OpenCode Big Pickle"
|
|
16451
|
+
});
|
|
16452
|
+
if (availableModels.length === 0) {
|
|
16453
|
+
printWarning("No models available. Please enable at least one provider.");
|
|
16454
|
+
availableModels.push({
|
|
16455
|
+
model: "opencode/big-pickle",
|
|
16456
|
+
name: "OpenCode Big Pickle"
|
|
16457
|
+
});
|
|
16458
|
+
}
|
|
16459
|
+
const manualAgentConfigs = {};
|
|
16460
|
+
const agentNames = [
|
|
16461
|
+
"orchestrator",
|
|
16462
|
+
"oracle",
|
|
16463
|
+
"designer",
|
|
16464
|
+
"explorer",
|
|
16465
|
+
"librarian",
|
|
16466
|
+
"fixer"
|
|
16467
|
+
];
|
|
16468
|
+
for (const agentName of agentNames) {
|
|
16469
|
+
manualAgentConfigs[agentName] = await configureAgentManually(rl, agentName, availableModels);
|
|
16470
|
+
}
|
|
16471
|
+
const balancedSpend = await askYesNo(rl, "Do you have subscriptions or pay per API? If yes, we will distribute assignments evenly across selected providers so your subscriptions last longer.", "no");
|
|
16472
|
+
console.log();
|
|
16473
|
+
let skills = "no";
|
|
16474
|
+
let customSkills = "no";
|
|
16475
|
+
if (!modelsOnly) {
|
|
16476
|
+
console.log(`${BOLD}Recommended Skills:${RESET}`);
|
|
16477
|
+
for (const skill of RECOMMENDED_SKILLS) {
|
|
16478
|
+
console.log(` ${SYMBOLS.bullet} ${BOLD}${skill.name}${RESET}: ${skill.description}`);
|
|
16479
|
+
}
|
|
16480
|
+
console.log();
|
|
16481
|
+
skills = await askYesNo(rl, "Install recommended skills?", "yes");
|
|
16482
|
+
console.log();
|
|
16483
|
+
console.log(`${BOLD}Custom Skills:${RESET}`);
|
|
16484
|
+
for (const skill of CUSTOM_SKILLS) {
|
|
16485
|
+
console.log(` ${SYMBOLS.bullet} ${BOLD}${skill.name}${RESET}: ${skill.description}`);
|
|
16486
|
+
}
|
|
16487
|
+
console.log();
|
|
16488
|
+
customSkills = await askYesNo(rl, "Install custom skills?", "yes");
|
|
16489
|
+
console.log();
|
|
16490
|
+
} else {
|
|
16491
|
+
printInfo("Models-only mode: skipping plugin/auth setup and skills prompts.");
|
|
16492
|
+
console.log();
|
|
16493
|
+
}
|
|
16494
|
+
return {
|
|
16495
|
+
hasKimi: kimi === "yes",
|
|
16496
|
+
hasOpenAI: openai === "yes",
|
|
16497
|
+
hasAnthropic: anthropic === "yes",
|
|
16498
|
+
hasCopilot: copilot === "yes",
|
|
16499
|
+
hasZaiPlan: zaiPlan === "yes",
|
|
16500
|
+
hasAntigravity: antigravity === "yes",
|
|
16501
|
+
hasChutes: chutes === "yes",
|
|
16502
|
+
hasOpencodeZen: true,
|
|
16503
|
+
useOpenCodeFreeModels: useOpenCodeFree === "yes" && availableOpenCodeFreeModels !== undefined,
|
|
16504
|
+
availableOpenCodeFreeModels,
|
|
16505
|
+
availableChutesModels,
|
|
16506
|
+
artificialAnalysisApiKey,
|
|
16507
|
+
openRouterApiKey,
|
|
16508
|
+
balanceProviderUsage: balancedSpend === "yes",
|
|
16509
|
+
hasTmux: false,
|
|
16510
|
+
installSkills: skills === "yes",
|
|
16511
|
+
installCustomSkills: customSkills === "yes",
|
|
16512
|
+
setupMode: "manual",
|
|
16513
|
+
manualAgentConfigs,
|
|
16514
|
+
modelsOnly
|
|
16515
|
+
};
|
|
16516
|
+
}
|
|
16517
|
+
async function runInteractiveMode(detected, modelsOnly = false) {
|
|
15026
16518
|
const rl = readline.createInterface({
|
|
15027
16519
|
input: process.stdin,
|
|
15028
16520
|
output: process.stdout
|
|
15029
16521
|
});
|
|
15030
|
-
const totalQuestions = 8;
|
|
15031
16522
|
try {
|
|
16523
|
+
console.log();
|
|
16524
|
+
console.log(`${BOLD}oh-my-opencode-slim Setup${RESET}`);
|
|
16525
|
+
console.log("=".repeat(25));
|
|
16526
|
+
console.log();
|
|
16527
|
+
const setupMode = await askSetupMode(rl);
|
|
16528
|
+
if (setupMode === "manual") {
|
|
16529
|
+
const config2 = await runManualSetupMode(rl, detected, modelsOnly);
|
|
16530
|
+
rl.close();
|
|
16531
|
+
return config2;
|
|
16532
|
+
}
|
|
16533
|
+
const totalQuestions = 11;
|
|
16534
|
+
const existingAaKey = getEnv("ARTIFICIAL_ANALYSIS_API_KEY");
|
|
16535
|
+
const existingOpenRouterKey = getEnv("OPENROUTER_API_KEY");
|
|
15032
16536
|
console.log(`${BOLD}Question 1/${totalQuestions}:${RESET}`);
|
|
15033
|
-
const
|
|
16537
|
+
const artificialAnalysisApiKey = await askOptionalApiKey(rl, "Artificial Analysis API key for better ranking signals", existingAaKey);
|
|
16538
|
+
if (existingAaKey && !artificialAnalysisApiKey) {
|
|
16539
|
+
printInfo("Using existing ARTIFICIAL_ANALYSIS_API_KEY from environment.");
|
|
16540
|
+
}
|
|
16541
|
+
console.log();
|
|
16542
|
+
console.log(`${BOLD}Question 2/${totalQuestions}:${RESET}`);
|
|
16543
|
+
const openRouterApiKey = await askOptionalApiKey(rl, "OpenRouter API key for pricing/metadata signals", existingOpenRouterKey);
|
|
16544
|
+
if (existingOpenRouterKey && !openRouterApiKey) {
|
|
16545
|
+
printInfo("Using existing OPENROUTER_API_KEY from environment.");
|
|
16546
|
+
}
|
|
16547
|
+
console.log();
|
|
16548
|
+
console.log(`${BOLD}Question 3/${totalQuestions}:${RESET}`);
|
|
16549
|
+
const useOpenCodeFree = await askYesNo(rl, "Use Opencode Free models (opencode/*)?", "yes");
|
|
15034
16550
|
console.log();
|
|
15035
16551
|
let availableOpenCodeFreeModels;
|
|
15036
16552
|
let selectedOpenCodePrimaryModel;
|
|
15037
16553
|
let selectedOpenCodeSecondaryModel;
|
|
15038
|
-
let
|
|
16554
|
+
let availableChutesModels;
|
|
15039
16555
|
let selectedChutesPrimaryModel;
|
|
15040
16556
|
let selectedChutesSecondaryModel;
|
|
15041
16557
|
if (useOpenCodeFree === "yes") {
|
|
@@ -15047,70 +16563,85 @@ async function runInteractiveMode(detected) {
|
|
|
15047
16563
|
availableOpenCodeFreeModels = discovery.models;
|
|
15048
16564
|
const recommendedPrimary = pickBestCodingOpenCodeModel(discovery.models)?.model ?? discovery.models[0]?.model;
|
|
15049
16565
|
if (recommendedPrimary) {
|
|
16566
|
+
printInfo("This step configures only OpenCode Free primary/support models.");
|
|
15050
16567
|
console.log(`${BOLD}OpenCode Free Models:${RESET}`);
|
|
15051
16568
|
selectedOpenCodePrimaryModel = await askModelSelection(rl, discovery.models, recommendedPrimary, "Choose primary model for orchestrator/oracle");
|
|
15052
16569
|
}
|
|
15053
16570
|
if (selectedOpenCodePrimaryModel) {
|
|
15054
16571
|
const recommendedSecondary = pickSupportOpenCodeModel(discovery.models, selectedOpenCodePrimaryModel)?.model ?? selectedOpenCodePrimaryModel;
|
|
15055
|
-
|
|
16572
|
+
const openCodeSupportList = discovery.models.filter((model) => model.model !== selectedOpenCodePrimaryModel);
|
|
16573
|
+
const openCodeSupportDefault = recommendedSecondary === selectedOpenCodePrimaryModel ? openCodeSupportList[0]?.model ?? recommendedSecondary : recommendedSecondary;
|
|
16574
|
+
selectedOpenCodeSecondaryModel = await askModelSelection(rl, openCodeSupportList, openCodeSupportDefault, "Choose support model for explorer/librarian/fixer");
|
|
15056
16575
|
}
|
|
15057
16576
|
console.log();
|
|
15058
16577
|
}
|
|
15059
16578
|
}
|
|
15060
|
-
console.log(`${BOLD}Question 2/${totalQuestions}:${RESET}`);
|
|
15061
|
-
const kimi = await askYesNo(rl, "Do you want to use Kimi For Coding?", detected.hasKimi ? "yes" : "no");
|
|
15062
|
-
console.log();
|
|
15063
|
-
console.log(`${BOLD}Question 3/${totalQuestions}:${RESET}`);
|
|
15064
|
-
const openai = await askYesNo(rl, "Do you have access to OpenAI API?", detected.hasOpenAI ? "yes" : "no");
|
|
15065
|
-
console.log();
|
|
15066
16579
|
console.log(`${BOLD}Question 4/${totalQuestions}:${RESET}`);
|
|
15067
|
-
const
|
|
16580
|
+
const kimi = await askYesNo(rl, "Enable Kimi provider?", detected.hasKimi ? "yes" : "no");
|
|
15068
16581
|
console.log();
|
|
15069
16582
|
console.log(`${BOLD}Question 5/${totalQuestions}:${RESET}`);
|
|
15070
|
-
const
|
|
16583
|
+
const openai = await askYesNo(rl, "Enable OpenAI provider?", detected.hasOpenAI ? "yes" : "no");
|
|
15071
16584
|
console.log();
|
|
15072
16585
|
console.log(`${BOLD}Question 6/${totalQuestions}:${RESET}`);
|
|
15073
|
-
const
|
|
16586
|
+
const anthropic = await askYesNo(rl, "Enable Anthropic provider?", detected.hasAnthropic ? "yes" : "no");
|
|
15074
16587
|
console.log();
|
|
15075
16588
|
console.log(`${BOLD}Question 7/${totalQuestions}:${RESET}`);
|
|
15076
|
-
const
|
|
16589
|
+
const copilot = await askYesNo(rl, "Enable GitHub Copilot provider?", detected.hasCopilot ? "yes" : "no");
|
|
15077
16590
|
console.log();
|
|
15078
16591
|
console.log(`${BOLD}Question 8/${totalQuestions}:${RESET}`);
|
|
15079
|
-
const
|
|
16592
|
+
const zaiPlan = await askYesNo(rl, "Enable ZAI Coding Plan provider?", detected.hasZaiPlan ? "yes" : "no");
|
|
16593
|
+
console.log();
|
|
16594
|
+
console.log(`${BOLD}Question 9/${totalQuestions}:${RESET}`);
|
|
16595
|
+
const antigravity = await askYesNo(rl, "Enable Antigravity (Google) provider?", detected.hasAntigravity ? "yes" : "no");
|
|
16596
|
+
console.log();
|
|
16597
|
+
console.log(`${BOLD}Question 10/${totalQuestions}:${RESET}`);
|
|
16598
|
+
const chutes = await askYesNo(rl, "Enable Chutes provider?", detected.hasChutes ? "yes" : "no");
|
|
15080
16599
|
console.log();
|
|
15081
16600
|
if (chutes === "yes") {
|
|
15082
16601
|
printInfo("Refreshing Chutes model list with: opencode models --refresh --verbose");
|
|
15083
|
-
const discovery = await
|
|
16602
|
+
const discovery = await discoverProviderModels("chutes");
|
|
15084
16603
|
if (discovery.models.length === 0) {
|
|
15085
|
-
printWarning(discovery.error ?? "No
|
|
16604
|
+
printWarning(discovery.error ?? "No Chutes models found. Continuing without Chutes dynamic assignment.");
|
|
15086
16605
|
} else {
|
|
15087
|
-
|
|
16606
|
+
availableChutesModels = discovery.models;
|
|
15088
16607
|
const recommendedPrimary = pickBestCodingChutesModel(discovery.models)?.model ?? discovery.models[0]?.model;
|
|
15089
16608
|
if (recommendedPrimary) {
|
|
15090
|
-
console.log(`${BOLD}Chutes
|
|
16609
|
+
console.log(`${BOLD}Chutes Models:${RESET}`);
|
|
15091
16610
|
selectedChutesPrimaryModel = await askModelSelection(rl, discovery.models, recommendedPrimary, "Choose Chutes primary model for orchestrator/oracle/designer");
|
|
15092
16611
|
}
|
|
15093
16612
|
if (selectedChutesPrimaryModel) {
|
|
15094
16613
|
const recommendedSecondary = pickSupportChutesModel(discovery.models, selectedChutesPrimaryModel)?.model ?? selectedChutesPrimaryModel;
|
|
15095
|
-
|
|
16614
|
+
const chutesSupportList = discovery.models.filter((model) => model.model !== selectedChutesPrimaryModel);
|
|
16615
|
+
const chutesSupportDefault = recommendedSecondary === selectedChutesPrimaryModel ? chutesSupportList[0]?.model ?? recommendedSecondary : recommendedSecondary;
|
|
16616
|
+
selectedChutesSecondaryModel = await askModelSelection(rl, chutesSupportList, chutesSupportDefault, "Choose Chutes support model for explorer/librarian/fixer");
|
|
15096
16617
|
}
|
|
15097
16618
|
console.log();
|
|
15098
16619
|
}
|
|
15099
16620
|
}
|
|
15100
|
-
console.log(`${BOLD}
|
|
15101
|
-
|
|
15102
|
-
console.log(` ${SYMBOLS.bullet} ${BOLD}${skill.name}${RESET}: ${skill.description}`);
|
|
15103
|
-
}
|
|
16621
|
+
console.log(`${BOLD}Question 11/${totalQuestions}:${RESET}`);
|
|
16622
|
+
const balancedSpend = await askYesNo(rl, "Do you have subscriptions or pay per API? If yes, we will distribute assignments evenly across selected providers so your subscriptions last longer.", "no");
|
|
15104
16623
|
console.log();
|
|
15105
|
-
|
|
15106
|
-
|
|
15107
|
-
|
|
15108
|
-
|
|
15109
|
-
|
|
16624
|
+
let skills = "no";
|
|
16625
|
+
let customSkills = "no";
|
|
16626
|
+
if (!modelsOnly) {
|
|
16627
|
+
console.log(`${BOLD}Recommended Skills:${RESET}`);
|
|
16628
|
+
for (const skill of RECOMMENDED_SKILLS) {
|
|
16629
|
+
console.log(` ${SYMBOLS.bullet} ${BOLD}${skill.name}${RESET}: ${skill.description}`);
|
|
16630
|
+
}
|
|
16631
|
+
console.log();
|
|
16632
|
+
skills = await askYesNo(rl, "Install recommended skills?", "yes");
|
|
16633
|
+
console.log();
|
|
16634
|
+
console.log(`${BOLD}Custom Skills:${RESET}`);
|
|
16635
|
+
for (const skill of CUSTOM_SKILLS) {
|
|
16636
|
+
console.log(` ${SYMBOLS.bullet} ${BOLD}${skill.name}${RESET}: ${skill.description}`);
|
|
16637
|
+
}
|
|
16638
|
+
console.log();
|
|
16639
|
+
customSkills = await askYesNo(rl, "Install custom skills?", "yes");
|
|
16640
|
+
console.log();
|
|
16641
|
+
} else {
|
|
16642
|
+
printInfo("Models-only mode: skipping plugin/auth setup and skills prompts.");
|
|
16643
|
+
console.log();
|
|
15110
16644
|
}
|
|
15111
|
-
console.log();
|
|
15112
|
-
const customSkills = await askYesNo(rl, "Install custom skills?", "yes");
|
|
15113
|
-
console.log();
|
|
15114
16645
|
return {
|
|
15115
16646
|
hasKimi: kimi === "yes",
|
|
15116
16647
|
hasOpenAI: openai === "yes",
|
|
@@ -15126,10 +16657,15 @@ async function runInteractiveMode(detected) {
|
|
|
15126
16657
|
availableOpenCodeFreeModels,
|
|
15127
16658
|
selectedChutesPrimaryModel,
|
|
15128
16659
|
selectedChutesSecondaryModel,
|
|
15129
|
-
|
|
16660
|
+
availableChutesModels,
|
|
16661
|
+
artificialAnalysisApiKey,
|
|
16662
|
+
openRouterApiKey,
|
|
16663
|
+
balanceProviderUsage: balancedSpend === "yes",
|
|
15130
16664
|
hasTmux: false,
|
|
15131
16665
|
installSkills: skills === "yes",
|
|
15132
|
-
installCustomSkills: customSkills === "yes"
|
|
16666
|
+
installCustomSkills: customSkills === "yes",
|
|
16667
|
+
setupMode: "quick",
|
|
16668
|
+
modelsOnly
|
|
15133
16669
|
};
|
|
15134
16670
|
} finally {
|
|
15135
16671
|
rl.close();
|
|
@@ -15143,24 +16679,32 @@ async function runInstall(config2) {
|
|
|
15143
16679
|
const isUpdate = detected.isInstalled;
|
|
15144
16680
|
printHeader(isUpdate);
|
|
15145
16681
|
const hasAnyEnabledProvider = resolvedConfig.hasKimi || resolvedConfig.hasOpenAI || resolvedConfig.hasAnthropic || resolvedConfig.hasCopilot || resolvedConfig.hasZaiPlan || resolvedConfig.hasAntigravity || resolvedConfig.hasChutes || resolvedConfig.useOpenCodeFreeModels;
|
|
15146
|
-
|
|
16682
|
+
const modelsOnly = resolvedConfig.modelsOnly === true;
|
|
16683
|
+
let totalSteps = modelsOnly ? 2 : 4;
|
|
15147
16684
|
if (resolvedConfig.useOpenCodeFreeModels)
|
|
15148
16685
|
totalSteps += 1;
|
|
15149
|
-
if (resolvedConfig.hasAntigravity)
|
|
16686
|
+
if (!modelsOnly && resolvedConfig.hasAntigravity)
|
|
15150
16687
|
totalSteps += 2;
|
|
15151
|
-
if (resolvedConfig.hasChutes)
|
|
16688
|
+
if (!modelsOnly && resolvedConfig.hasChutes)
|
|
15152
16689
|
totalSteps += 1;
|
|
15153
16690
|
if (hasAnyEnabledProvider)
|
|
15154
16691
|
totalSteps += 1;
|
|
15155
|
-
if (resolvedConfig.installSkills)
|
|
16692
|
+
if (!modelsOnly && resolvedConfig.installSkills)
|
|
15156
16693
|
totalSteps += 1;
|
|
15157
|
-
if (resolvedConfig.installCustomSkills)
|
|
16694
|
+
if (!modelsOnly && resolvedConfig.installCustomSkills)
|
|
15158
16695
|
totalSteps += 1;
|
|
15159
16696
|
let step = 1;
|
|
16697
|
+
if (modelsOnly) {
|
|
16698
|
+
printInfo("Models-only mode: updating model assignments without reinstalling plugins/skills.");
|
|
16699
|
+
}
|
|
15160
16700
|
printStep(step++, totalSteps, "Checking OpenCode installation...");
|
|
15161
|
-
|
|
15162
|
-
|
|
15163
|
-
|
|
16701
|
+
if (resolvedConfig.dryRun) {
|
|
16702
|
+
printInfo("Dry run mode - skipping OpenCode check");
|
|
16703
|
+
} else {
|
|
16704
|
+
const { ok } = await checkOpenCodeInstalled();
|
|
16705
|
+
if (!ok)
|
|
16706
|
+
return 1;
|
|
16707
|
+
}
|
|
15164
16708
|
if (resolvedConfig.useOpenCodeFreeModels && (resolvedConfig.availableOpenCodeFreeModels?.length ?? 0) === 0) {
|
|
15165
16709
|
printStep(step++, totalSteps, "Refreshing OpenCode free models (opencode/*)...");
|
|
15166
16710
|
const discovery = await discoverOpenCodeFreeModels();
|
|
@@ -15181,43 +16725,61 @@ async function runInstall(config2) {
|
|
|
15181
16725
|
printStep(step++, totalSteps, "Using previously refreshed OpenCode free model list...");
|
|
15182
16726
|
printSuccess(`OpenCode free models ready (${availableModels.length} models found)`);
|
|
15183
16727
|
}
|
|
15184
|
-
if (resolvedConfig.hasChutes && (resolvedConfig.
|
|
15185
|
-
printStep(step++, totalSteps, "Refreshing Chutes
|
|
15186
|
-
const discovery = await
|
|
16728
|
+
if (resolvedConfig.hasChutes && (resolvedConfig.availableChutesModels?.length ?? 0) === 0) {
|
|
16729
|
+
printStep(step++, totalSteps, "Refreshing Chutes models (chutes/*)...");
|
|
16730
|
+
const discovery = await discoverProviderModels("chutes");
|
|
15187
16731
|
if (discovery.models.length === 0) {
|
|
15188
|
-
printWarning(discovery.error ?? "No
|
|
16732
|
+
printWarning(discovery.error ?? "No Chutes models found. Continuing with fallback Chutes mapping.");
|
|
15189
16733
|
} else {
|
|
15190
|
-
resolvedConfig.
|
|
16734
|
+
resolvedConfig.availableChutesModels = discovery.models;
|
|
15191
16735
|
resolvedConfig.selectedChutesPrimaryModel = resolvedConfig.selectedChutesPrimaryModel ?? pickBestCodingChutesModel(discovery.models)?.model ?? discovery.models[0]?.model;
|
|
15192
16736
|
resolvedConfig.selectedChutesSecondaryModel = resolvedConfig.selectedChutesSecondaryModel ?? pickSupportChutesModel(discovery.models, resolvedConfig.selectedChutesPrimaryModel)?.model ?? resolvedConfig.selectedChutesPrimaryModel;
|
|
15193
16737
|
printSuccess(`Chutes models ready (${discovery.models.length} models found)`);
|
|
15194
16738
|
}
|
|
15195
|
-
} else if (resolvedConfig.hasChutes && (resolvedConfig.
|
|
15196
|
-
const availableChutes = resolvedConfig.
|
|
16739
|
+
} else if (resolvedConfig.hasChutes && (resolvedConfig.availableChutesModels?.length ?? 0) > 0) {
|
|
16740
|
+
const availableChutes = resolvedConfig.availableChutesModels ?? [];
|
|
15197
16741
|
resolvedConfig.selectedChutesPrimaryModel = resolvedConfig.selectedChutesPrimaryModel ?? pickBestCodingChutesModel(availableChutes)?.model;
|
|
15198
16742
|
resolvedConfig.selectedChutesSecondaryModel = resolvedConfig.selectedChutesSecondaryModel ?? pickSupportChutesModel(availableChutes, resolvedConfig.selectedChutesPrimaryModel)?.model ?? resolvedConfig.selectedChutesPrimaryModel;
|
|
15199
|
-
printStep(step++, totalSteps, "Using previously refreshed Chutes
|
|
16743
|
+
printStep(step++, totalSteps, "Using previously refreshed Chutes model list...");
|
|
15200
16744
|
printSuccess(`Chutes models ready (${availableChutes.length} models found)`);
|
|
15201
16745
|
}
|
|
15202
|
-
|
|
15203
|
-
|
|
15204
|
-
|
|
15205
|
-
|
|
15206
|
-
|
|
16746
|
+
if (!modelsOnly) {
|
|
16747
|
+
printStep(step++, totalSteps, "Adding oh-my-opencode-slim plugin...");
|
|
16748
|
+
if (resolvedConfig.dryRun) {
|
|
16749
|
+
printInfo("Dry run mode - skipping plugin installation");
|
|
16750
|
+
} else {
|
|
16751
|
+
const pluginResult = await addPluginToOpenCodeConfig();
|
|
16752
|
+
if (!handleStepResult(pluginResult, "Plugin added"))
|
|
16753
|
+
return 1;
|
|
16754
|
+
}
|
|
16755
|
+
}
|
|
16756
|
+
if (!modelsOnly && resolvedConfig.hasAntigravity) {
|
|
15207
16757
|
printStep(step++, totalSteps, "Adding Antigravity plugin...");
|
|
15208
|
-
|
|
15209
|
-
|
|
15210
|
-
|
|
16758
|
+
if (resolvedConfig.dryRun) {
|
|
16759
|
+
printInfo("Dry run mode - skipping Antigravity plugin");
|
|
16760
|
+
} else {
|
|
16761
|
+
const antigravityPluginResult = addAntigravityPlugin();
|
|
16762
|
+
if (!handleStepResult(antigravityPluginResult, "Antigravity plugin added"))
|
|
16763
|
+
return 1;
|
|
16764
|
+
}
|
|
15211
16765
|
printStep(step++, totalSteps, "Configuring Google Provider...");
|
|
15212
|
-
|
|
15213
|
-
|
|
15214
|
-
|
|
16766
|
+
if (resolvedConfig.dryRun) {
|
|
16767
|
+
printInfo("Dry run mode - skipping Google Provider setup");
|
|
16768
|
+
} else {
|
|
16769
|
+
const googleProviderResult = addGoogleProvider();
|
|
16770
|
+
if (!handleStepResult(googleProviderResult, "Google Provider configured"))
|
|
16771
|
+
return 1;
|
|
16772
|
+
}
|
|
15215
16773
|
}
|
|
15216
|
-
if (resolvedConfig.hasChutes) {
|
|
15217
|
-
printStep(step++, totalSteps, "
|
|
15218
|
-
|
|
15219
|
-
|
|
15220
|
-
|
|
16774
|
+
if (!modelsOnly && resolvedConfig.hasChutes) {
|
|
16775
|
+
printStep(step++, totalSteps, "Enabling Chutes auth flow...");
|
|
16776
|
+
if (resolvedConfig.dryRun) {
|
|
16777
|
+
printInfo("Dry run mode - skipping Chutes auth flow");
|
|
16778
|
+
} else {
|
|
16779
|
+
const chutesProviderResult = addChutesProvider();
|
|
16780
|
+
if (!handleStepResult(chutesProviderResult, "Chutes auth flow ready"))
|
|
16781
|
+
return 1;
|
|
16782
|
+
}
|
|
15221
16783
|
}
|
|
15222
16784
|
if (hasAnyEnabledProvider) {
|
|
15223
16785
|
printStep(step++, totalSteps, "Resolving dynamic model assignments...");
|
|
@@ -15225,7 +16787,14 @@ async function runInstall(config2) {
|
|
|
15225
16787
|
if (catalogDiscovery.models.length === 0) {
|
|
15226
16788
|
printWarning(catalogDiscovery.error ?? "Unable to discover model catalog. Falling back to static mappings.");
|
|
15227
16789
|
} else {
|
|
15228
|
-
const
|
|
16790
|
+
const { signals, warnings } = await fetchExternalModelSignals({
|
|
16791
|
+
artificialAnalysisApiKey: resolvedConfig.artificialAnalysisApiKey,
|
|
16792
|
+
openRouterApiKey: resolvedConfig.openRouterApiKey
|
|
16793
|
+
});
|
|
16794
|
+
for (const warning of warnings) {
|
|
16795
|
+
printInfo(warning);
|
|
16796
|
+
}
|
|
16797
|
+
const dynamicPlan = buildDynamicModelPlan(catalogDiscovery.models, resolvedConfig, signals);
|
|
15229
16798
|
if (!dynamicPlan) {
|
|
15230
16799
|
printWarning("Dynamic planner found no suitable models. Using static mappings.");
|
|
15231
16800
|
} else {
|
|
@@ -15234,41 +16803,69 @@ async function runInstall(config2) {
|
|
|
15234
16803
|
}
|
|
15235
16804
|
}
|
|
15236
16805
|
}
|
|
15237
|
-
|
|
15238
|
-
|
|
15239
|
-
|
|
15240
|
-
|
|
16806
|
+
if (!modelsOnly) {
|
|
16807
|
+
printStep(step++, totalSteps, "Disabling OpenCode default agents...");
|
|
16808
|
+
if (resolvedConfig.dryRun) {
|
|
16809
|
+
printInfo("Dry run mode - skipping agent disabling");
|
|
16810
|
+
} else {
|
|
16811
|
+
const agentResult = disableDefaultAgents();
|
|
16812
|
+
if (!handleStepResult(agentResult, "Default agents disabled"))
|
|
16813
|
+
return 1;
|
|
16814
|
+
}
|
|
16815
|
+
}
|
|
15241
16816
|
printStep(step++, totalSteps, "Writing oh-my-opencode-slim configuration...");
|
|
15242
|
-
|
|
15243
|
-
|
|
15244
|
-
|
|
15245
|
-
|
|
16817
|
+
if (resolvedConfig.dryRun) {
|
|
16818
|
+
const liteConfig = generateLiteConfig(resolvedConfig);
|
|
16819
|
+
printInfo("Dry run mode - configuration that would be written:");
|
|
16820
|
+
console.log(`
|
|
16821
|
+
${JSON.stringify(liteConfig, null, 2)}
|
|
16822
|
+
`);
|
|
16823
|
+
} else {
|
|
16824
|
+
const liteResult = writeLiteConfig(resolvedConfig);
|
|
16825
|
+
if (!handleStepResult(liteResult, "Config written"))
|
|
16826
|
+
return 1;
|
|
16827
|
+
}
|
|
16828
|
+
if (!modelsOnly && resolvedConfig.installSkills) {
|
|
15246
16829
|
printStep(step++, totalSteps, "Installing recommended skills...");
|
|
15247
|
-
|
|
15248
|
-
|
|
15249
|
-
|
|
15250
|
-
|
|
15251
|
-
|
|
15252
|
-
|
|
15253
|
-
|
|
15254
|
-
|
|
16830
|
+
if (resolvedConfig.dryRun) {
|
|
16831
|
+
printInfo("Dry run mode - would install skills:");
|
|
16832
|
+
for (const skill of RECOMMENDED_SKILLS) {
|
|
16833
|
+
printInfo(` - ${skill.name}`);
|
|
16834
|
+
}
|
|
16835
|
+
} else {
|
|
16836
|
+
let skillsInstalled = 0;
|
|
16837
|
+
for (const skill of RECOMMENDED_SKILLS) {
|
|
16838
|
+
printInfo(`Installing ${skill.name}...`);
|
|
16839
|
+
if (installSkill(skill)) {
|
|
16840
|
+
printSuccess(`Installed: ${skill.name}`);
|
|
16841
|
+
skillsInstalled++;
|
|
16842
|
+
} else {
|
|
16843
|
+
printWarning(`Failed to install: ${skill.name}`);
|
|
16844
|
+
}
|
|
15255
16845
|
}
|
|
16846
|
+
printSuccess(`${skillsInstalled}/${RECOMMENDED_SKILLS.length} skills installed`);
|
|
15256
16847
|
}
|
|
15257
|
-
printSuccess(`${skillsInstalled}/${RECOMMENDED_SKILLS.length} skills installed`);
|
|
15258
16848
|
}
|
|
15259
|
-
if (resolvedConfig.installCustomSkills) {
|
|
16849
|
+
if (!modelsOnly && resolvedConfig.installCustomSkills) {
|
|
15260
16850
|
printStep(step++, totalSteps, "Installing custom skills...");
|
|
15261
|
-
|
|
15262
|
-
|
|
15263
|
-
|
|
15264
|
-
|
|
15265
|
-
printSuccess(`Installed: ${skill.name}`);
|
|
15266
|
-
customSkillsInstalled++;
|
|
15267
|
-
} else {
|
|
15268
|
-
printWarning(`Failed to install: ${skill.name}`);
|
|
16851
|
+
if (resolvedConfig.dryRun) {
|
|
16852
|
+
printInfo("Dry run mode - would install custom skills:");
|
|
16853
|
+
for (const skill of CUSTOM_SKILLS) {
|
|
16854
|
+
printInfo(` - ${skill.name}`);
|
|
15269
16855
|
}
|
|
16856
|
+
} else {
|
|
16857
|
+
let customSkillsInstalled = 0;
|
|
16858
|
+
for (const skill of CUSTOM_SKILLS) {
|
|
16859
|
+
printInfo(`Installing ${skill.name}...`);
|
|
16860
|
+
if (installCustomSkill(skill)) {
|
|
16861
|
+
printSuccess(`Installed: ${skill.name}`);
|
|
16862
|
+
customSkillsInstalled++;
|
|
16863
|
+
} else {
|
|
16864
|
+
printWarning(`Failed to install: ${skill.name}`);
|
|
16865
|
+
}
|
|
16866
|
+
}
|
|
16867
|
+
printSuccess(`${customSkillsInstalled}/${CUSTOM_SKILLS.length} custom skills installed`);
|
|
15270
16868
|
}
|
|
15271
|
-
printSuccess(`${customSkillsInstalled}/${CUSTOM_SKILLS.length} custom skills installed`);
|
|
15272
16869
|
}
|
|
15273
16870
|
console.log();
|
|
15274
16871
|
console.log(formatConfigSummary(resolvedConfig));
|
|
@@ -15307,7 +16904,7 @@ async function runInstall(config2) {
|
|
|
15307
16904
|
}
|
|
15308
16905
|
if (resolvedConfig.hasChutes) {
|
|
15309
16906
|
console.log();
|
|
15310
|
-
console.log(` Then
|
|
16907
|
+
console.log(` Then select ${BOLD}chutes${RESET} provider.`);
|
|
15311
16908
|
}
|
|
15312
16909
|
console.log();
|
|
15313
16910
|
}
|
|
@@ -15340,20 +16937,27 @@ async function install(args) {
|
|
|
15340
16937
|
console.log(` ${SYMBOLS.bullet} --${flagName}=<yes|no>`);
|
|
15341
16938
|
}
|
|
15342
16939
|
console.log();
|
|
15343
|
-
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> --tmux=<yes|no>");
|
|
16940
|
+
printInfo("Usage: bunx oh-my-opencode-slim install --no-tui --kimi=<yes|no> --openai=<yes|no> --anthropic=<yes|no> --copilot=<yes|no> --zai-plan=<yes|no> --antigravity=<yes|no> --chutes=<yes|no> --balanced-spend=<yes|no> --tmux=<yes|no>");
|
|
15344
16941
|
console.log();
|
|
15345
16942
|
return 1;
|
|
15346
16943
|
}
|
|
15347
|
-
|
|
16944
|
+
const nonInteractiveConfig = argsToConfig(args);
|
|
16945
|
+
return runInstall(nonInteractiveConfig);
|
|
15348
16946
|
}
|
|
15349
16947
|
const detected = detectCurrentConfig();
|
|
15350
16948
|
printHeader(detected.isInstalled);
|
|
15351
16949
|
printStep(1, 1, "Checking OpenCode installation...");
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
|
|
16950
|
+
if (args.dryRun) {
|
|
16951
|
+
printInfo("Dry run mode - skipping OpenCode check");
|
|
16952
|
+
} else {
|
|
16953
|
+
const { ok } = await checkOpenCodeInstalled();
|
|
16954
|
+
if (!ok)
|
|
16955
|
+
return 1;
|
|
16956
|
+
}
|
|
15355
16957
|
console.log();
|
|
15356
|
-
const config2 = await runInteractiveMode(detected);
|
|
16958
|
+
const config2 = await runInteractiveMode(detected, args.modelsOnly === true);
|
|
16959
|
+
config2.dryRun = args.dryRun;
|
|
16960
|
+
config2.modelsOnly = args.modelsOnly;
|
|
15357
16961
|
return runInstall(config2);
|
|
15358
16962
|
}
|
|
15359
16963
|
|
|
@@ -15385,8 +16989,18 @@ function parseArgs(args) {
|
|
|
15385
16989
|
result.skills = arg.split("=")[1];
|
|
15386
16990
|
} else if (arg.startsWith("--opencode-free=")) {
|
|
15387
16991
|
result.opencodeFree = arg.split("=")[1];
|
|
16992
|
+
} else if (arg.startsWith("--balanced-spend=")) {
|
|
16993
|
+
result.balancedSpend = arg.split("=")[1];
|
|
15388
16994
|
} else if (arg.startsWith("--opencode-free-model=")) {
|
|
15389
16995
|
result.opencodeFreeModel = arg.split("=")[1];
|
|
16996
|
+
} else if (arg.startsWith("--aa-key=")) {
|
|
16997
|
+
result.aaKey = arg.slice("--aa-key=".length);
|
|
16998
|
+
} else if (arg.startsWith("--openrouter-key=")) {
|
|
16999
|
+
result.openrouterKey = arg.slice("--openrouter-key=".length);
|
|
17000
|
+
} else if (arg === "--dry-run") {
|
|
17001
|
+
result.dryRun = true;
|
|
17002
|
+
} else if (arg === "--models-only") {
|
|
17003
|
+
result.modelsOnly = true;
|
|
15390
17004
|
} else if (arg === "-h" || arg === "--help") {
|
|
15391
17005
|
printHelp();
|
|
15392
17006
|
process.exit(0);
|
|
@@ -15399,6 +17013,7 @@ function printHelp() {
|
|
|
15399
17013
|
oh-my-opencode-slim installer
|
|
15400
17014
|
|
|
15401
17015
|
Usage: bunx oh-my-opencode-slim install [OPTIONS]
|
|
17016
|
+
bunx oh-my-opencode-slim models [OPTIONS]
|
|
15402
17017
|
|
|
15403
17018
|
Options:
|
|
15404
17019
|
--kimi=yes|no Kimi API access (yes/no)
|
|
@@ -15409,21 +17024,31 @@ Options:
|
|
|
15409
17024
|
--antigravity=yes|no Antigravity/Google models (yes/no)
|
|
15410
17025
|
--chutes=yes|no Chutes models (yes/no)
|
|
15411
17026
|
--opencode-free=yes|no Use OpenCode free models (opencode/*)
|
|
17027
|
+
--balanced-spend=yes|no Evenly spread usage across selected providers when score gaps are within tolerance
|
|
15412
17028
|
--opencode-free-model Preferred OpenCode model id or "auto"
|
|
17029
|
+
--aa-key Artificial Analysis API key (optional)
|
|
17030
|
+
--openrouter-key OpenRouter API key (optional)
|
|
15413
17031
|
--tmux=yes|no Enable tmux integration (yes/no)
|
|
15414
17032
|
--skills=yes|no Install recommended skills (yes/no)
|
|
15415
17033
|
--no-tui Non-interactive mode (requires all flags)
|
|
17034
|
+
--dry-run Simulate install without writing files or requiring OpenCode
|
|
17035
|
+
--models-only Update model assignments only (skip plugin/auth/skills)
|
|
15416
17036
|
-h, --help Show this help message
|
|
15417
17037
|
|
|
15418
17038
|
Examples:
|
|
15419
17039
|
bunx oh-my-opencode-slim install
|
|
15420
|
-
bunx oh-my-opencode-slim
|
|
17040
|
+
bunx oh-my-opencode-slim models
|
|
17041
|
+
bunx oh-my-opencode-slim install --no-tui --kimi=yes --openai=yes --anthropic=yes --copilot=no --zai-plan=no --antigravity=yes --chutes=no --opencode-free=yes --balanced-spend=yes --opencode-free-model=auto --aa-key=YOUR_AA_KEY --openrouter-key=YOUR_OR_KEY --tmux=no --skills=yes
|
|
15421
17042
|
`);
|
|
15422
17043
|
}
|
|
15423
17044
|
async function main() {
|
|
15424
17045
|
const args = process.argv.slice(2);
|
|
15425
|
-
if (args.length === 0 || args[0] === "install") {
|
|
15426
|
-
const
|
|
17046
|
+
if (args.length === 0 || args[0] === "install" || args[0] === "models") {
|
|
17047
|
+
const hasSubcommand = args[0] === "install" || args[0] === "models";
|
|
17048
|
+
const installArgs = parseArgs(args.slice(hasSubcommand ? 1 : 0));
|
|
17049
|
+
if (args[0] === "models") {
|
|
17050
|
+
installArgs.modelsOnly = true;
|
|
17051
|
+
}
|
|
15427
17052
|
const exitCode = await install(installArgs);
|
|
15428
17053
|
process.exit(exitCode);
|
|
15429
17054
|
} else if (args[0] === "-h" || args[0] === "--help") {
|