oh-my-claudecode-opencode 0.6.11 → 0.6.12
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/bin/omco-setup.js +10 -0
- package/bin/omco-setup.ts +10 -0
- package/dist/index.js +204 -120
- package/dist/tools/model-resolution-service.d.ts +8 -0
- package/package.json +1 -1
package/bin/omco-setup.js
CHANGED
|
@@ -87,6 +87,16 @@ async function main() {
|
|
|
87
87
|
};
|
|
88
88
|
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + `
|
|
89
89
|
`);
|
|
90
|
+
try {
|
|
91
|
+
const written = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
92
|
+
if (written.model_mapping?.tierDefaults) {
|
|
93
|
+
console.log(`
|
|
94
|
+
✅ Config verification passed.`);
|
|
95
|
+
}
|
|
96
|
+
} catch {
|
|
97
|
+
console.error(`
|
|
98
|
+
⚠️ Warning: Could not verify written config.`);
|
|
99
|
+
}
|
|
90
100
|
console.log(`
|
|
91
101
|
✅ Configured tier mapping for ${provider}:
|
|
92
102
|
`);
|
package/bin/omco-setup.ts
CHANGED
|
@@ -105,6 +105,16 @@ async function main() {
|
|
|
105
105
|
// Write config
|
|
106
106
|
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n");
|
|
107
107
|
|
|
108
|
+
// Verify config can be read back
|
|
109
|
+
try {
|
|
110
|
+
const written = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
111
|
+
if (written.model_mapping?.tierDefaults) {
|
|
112
|
+
console.log(`\n✅ Config verification passed.`);
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
console.error(`\n⚠️ Warning: Could not verify written config.`);
|
|
116
|
+
}
|
|
117
|
+
|
|
108
118
|
console.log(`\n✅ Configured tier mapping for ${provider}:\n`);
|
|
109
119
|
console.log(` haiku → ${tiers.haiku}`);
|
|
110
120
|
console.log(` sonnet → ${tiers.sonnet}`);
|
package/dist/index.js
CHANGED
|
@@ -20674,16 +20674,16 @@ function loadConfig(directory) {
|
|
|
20674
20674
|
}
|
|
20675
20675
|
return {
|
|
20676
20676
|
agents: {
|
|
20677
|
-
omc: {
|
|
20678
|
-
architect: {
|
|
20679
|
-
researcher: {
|
|
20680
|
-
explore: {
|
|
20681
|
-
frontendEngineer: {
|
|
20682
|
-
documentWriter: {
|
|
20683
|
-
multimodalLooker: {
|
|
20684
|
-
critic: {
|
|
20685
|
-
analyst: {
|
|
20686
|
-
planner: {
|
|
20677
|
+
omc: { tier: "opus", enabled: true },
|
|
20678
|
+
architect: { tier: "opus", enabled: true },
|
|
20679
|
+
researcher: { tier: "sonnet", enabled: true },
|
|
20680
|
+
explore: { tier: "haiku", enabled: true },
|
|
20681
|
+
frontendEngineer: { tier: "sonnet", enabled: true },
|
|
20682
|
+
documentWriter: { tier: "haiku", enabled: true },
|
|
20683
|
+
multimodalLooker: { tier: "sonnet", enabled: true },
|
|
20684
|
+
critic: { tier: "opus", enabled: true },
|
|
20685
|
+
analyst: { tier: "opus", enabled: true },
|
|
20686
|
+
planner: { tier: "opus", enabled: true }
|
|
20687
20687
|
},
|
|
20688
20688
|
features: {
|
|
20689
20689
|
parallelExecution: true,
|
|
@@ -20714,11 +20714,6 @@ function loadConfig(directory) {
|
|
|
20714
20714
|
defaultTier: "MEDIUM",
|
|
20715
20715
|
escalationEnabled: true,
|
|
20716
20716
|
maxEscalations: 2,
|
|
20717
|
-
tierModels: {
|
|
20718
|
-
LOW: "github-copilot/claude-haiku-4",
|
|
20719
|
-
MEDIUM: "github-copilot/claude-sonnet-4",
|
|
20720
|
-
HIGH: "github-copilot/claude-opus-4"
|
|
20721
|
-
},
|
|
20722
20717
|
agentOverrides: {
|
|
20723
20718
|
architect: { tier: "HIGH", reason: "Advisory agent requires deep reasoning" },
|
|
20724
20719
|
planner: { tier: "HIGH", reason: "Strategic planning requires deep reasoning" },
|
|
@@ -21873,6 +21868,29 @@ function createBackgroundManager(ctx, config2, modelService) {
|
|
|
21873
21868
|
}
|
|
21874
21869
|
return count;
|
|
21875
21870
|
};
|
|
21871
|
+
const detectConfiguredProvider = async () => {
|
|
21872
|
+
try {
|
|
21873
|
+
const providersResp = await ctx.client.provider.list({
|
|
21874
|
+
query: { directory: ctx.directory }
|
|
21875
|
+
});
|
|
21876
|
+
const providers = providersResp.data;
|
|
21877
|
+
for (const provider of providers || []) {
|
|
21878
|
+
if (provider.models && provider.models.length > 0) {
|
|
21879
|
+
const modelConfig = {
|
|
21880
|
+
providerID: provider.id,
|
|
21881
|
+
modelID: provider.models[0].id
|
|
21882
|
+
};
|
|
21883
|
+
log(`Detected configured provider as fallback`, { providerID: modelConfig.providerID, modelID: modelConfig.modelID });
|
|
21884
|
+
return modelConfig;
|
|
21885
|
+
}
|
|
21886
|
+
}
|
|
21887
|
+
log(`No configured providers found`);
|
|
21888
|
+
return;
|
|
21889
|
+
} catch (err) {
|
|
21890
|
+
log(`Failed to detect configured provider`, { error: String(err) });
|
|
21891
|
+
return;
|
|
21892
|
+
}
|
|
21893
|
+
};
|
|
21876
21894
|
const getParentSessionModel = async (parentSessionID) => {
|
|
21877
21895
|
if (modelCache.has(parentSessionID)) {
|
|
21878
21896
|
return modelCache.get(parentSessionID);
|
|
@@ -21894,11 +21912,17 @@ function createBackgroundManager(ctx, config2, modelService) {
|
|
|
21894
21912
|
return model;
|
|
21895
21913
|
}
|
|
21896
21914
|
log(`Parent session has no assistant messages with model info`, { parentSessionID });
|
|
21897
|
-
return;
|
|
21898
21915
|
} catch (err) {
|
|
21899
21916
|
log(`Failed to get parent session model`, { parentSessionID, error: String(err) });
|
|
21900
|
-
return;
|
|
21901
21917
|
}
|
|
21918
|
+
try {
|
|
21919
|
+
const configuredModel = await detectConfiguredProvider();
|
|
21920
|
+
if (configuredModel) {
|
|
21921
|
+
modelCache.set(parentSessionID, configuredModel);
|
|
21922
|
+
return configuredModel;
|
|
21923
|
+
}
|
|
21924
|
+
} catch {}
|
|
21925
|
+
return;
|
|
21902
21926
|
};
|
|
21903
21927
|
const createTask = async (parentSessionID, description, prompt, agent, model) => {
|
|
21904
21928
|
const runningCount = getRunningCount();
|
|
@@ -21906,109 +21930,127 @@ function createBackgroundManager(ctx, config2, modelService) {
|
|
|
21906
21930
|
throw new Error(`Max concurrent tasks (${defaultConcurrency}) reached. Wait for some to complete.`);
|
|
21907
21931
|
}
|
|
21908
21932
|
const taskId = generateTaskId();
|
|
21909
|
-
|
|
21910
|
-
|
|
21911
|
-
|
|
21912
|
-
|
|
21913
|
-
|
|
21914
|
-
|
|
21915
|
-
|
|
21916
|
-
|
|
21917
|
-
|
|
21918
|
-
|
|
21919
|
-
const resolvedModel = modelService ? modelService.resolveModelForAgent(agent, parentModel) : parentModel;
|
|
21920
|
-
if (resolvedModel && resolvedModel !== parentModel) {
|
|
21921
|
-
log(`[background-manager] Using tier-mapped model for ${agent}`, {
|
|
21922
|
-
providerID: resolvedModel.providerID,
|
|
21923
|
-
modelID: resolvedModel.modelID
|
|
21924
|
-
});
|
|
21925
|
-
}
|
|
21926
|
-
(async () => {
|
|
21927
|
-
try {
|
|
21928
|
-
const sessionResp = await ctx.client.session.create({
|
|
21929
|
-
body: {
|
|
21930
|
-
parentID: parentSessionID,
|
|
21931
|
-
title: `${agent}: ${description}`
|
|
21932
|
-
},
|
|
21933
|
-
query: { directory: ctx.directory }
|
|
21933
|
+
try {
|
|
21934
|
+
const parentModel = model || await getParentSessionModel(parentSessionID);
|
|
21935
|
+
const resolvedModel = modelService ? modelService.resolveModelForAgent(agent, parentModel) : parentModel;
|
|
21936
|
+
if (!resolvedModel) {
|
|
21937
|
+
throw new Error(`[OMCO] No model available for agent "${agent}". ` + `Configure tier mapping with 'npx omco-setup'.`);
|
|
21938
|
+
}
|
|
21939
|
+
if (resolvedModel !== parentModel) {
|
|
21940
|
+
log(`[background-manager] Using tier-mapped model for ${agent}`, {
|
|
21941
|
+
providerID: resolvedModel.providerID,
|
|
21942
|
+
modelID: resolvedModel.modelID
|
|
21934
21943
|
});
|
|
21935
|
-
|
|
21936
|
-
|
|
21937
|
-
|
|
21938
|
-
|
|
21939
|
-
|
|
21940
|
-
|
|
21941
|
-
|
|
21942
|
-
|
|
21944
|
+
}
|
|
21945
|
+
const task = {
|
|
21946
|
+
id: taskId,
|
|
21947
|
+
status: "running",
|
|
21948
|
+
description,
|
|
21949
|
+
parentSessionID,
|
|
21950
|
+
startedAt: Date.now()
|
|
21951
|
+
};
|
|
21952
|
+
tasks.set(taskId, task);
|
|
21953
|
+
log(`Background task created`, { taskId, description, agent });
|
|
21954
|
+
(async () => {
|
|
21955
|
+
try {
|
|
21956
|
+
const sessionResp = await ctx.client.session.create({
|
|
21957
|
+
body: {
|
|
21958
|
+
parentID: parentSessionID,
|
|
21959
|
+
title: `${agent}: ${description}`
|
|
21960
|
+
},
|
|
21961
|
+
query: { directory: ctx.directory }
|
|
21962
|
+
});
|
|
21963
|
+
const sessionID = sessionResp.data?.id ?? sessionResp.id;
|
|
21964
|
+
if (!sessionID)
|
|
21965
|
+
throw new Error("Failed to create session");
|
|
21966
|
+
task.sessionID = sessionID;
|
|
21967
|
+
const canonicalName = isAlias(agent) ? getCanonicalName(agent) : agent;
|
|
21968
|
+
const agentDef = getAgent(canonicalName);
|
|
21969
|
+
const systemPrompt = agentDef?.systemPrompt || "";
|
|
21970
|
+
const fullPrompt = systemPrompt ? `${systemPrompt}
|
|
21943
21971
|
|
|
21944
21972
|
${prompt}` : prompt;
|
|
21945
|
-
|
|
21946
|
-
|
|
21947
|
-
|
|
21948
|
-
|
|
21949
|
-
|
|
21950
|
-
|
|
21951
|
-
|
|
21952
|
-
|
|
21953
|
-
|
|
21954
|
-
|
|
21955
|
-
|
|
21956
|
-
|
|
21957
|
-
|
|
21958
|
-
|
|
21959
|
-
|
|
21960
|
-
|
|
21961
|
-
|
|
21962
|
-
|
|
21963
|
-
|
|
21964
|
-
|
|
21965
|
-
|
|
21966
|
-
|
|
21967
|
-
|
|
21968
|
-
|
|
21969
|
-
|
|
21970
|
-
|
|
21971
|
-
|
|
21972
|
-
|
|
21973
|
-
|
|
21974
|
-
|
|
21975
|
-
|
|
21976
|
-
|
|
21977
|
-
|
|
21978
|
-
|
|
21979
|
-
|
|
21973
|
+
const promptBody = {
|
|
21974
|
+
parts: [{ type: "text", text: fullPrompt }]
|
|
21975
|
+
};
|
|
21976
|
+
if (resolvedModel) {
|
|
21977
|
+
promptBody.model = resolvedModel;
|
|
21978
|
+
log(`Using model for subagent`, { taskId, ...resolvedModel });
|
|
21979
|
+
}
|
|
21980
|
+
let promptResp = await ctx.client.session.prompt({
|
|
21981
|
+
path: { id: sessionID },
|
|
21982
|
+
body: promptBody,
|
|
21983
|
+
query: { directory: ctx.directory }
|
|
21984
|
+
});
|
|
21985
|
+
let promptData = promptResp.data;
|
|
21986
|
+
if (promptResp.error) {
|
|
21987
|
+
throw new Error(`Prompt failed: ${JSON.stringify(promptResp.error)}`);
|
|
21988
|
+
}
|
|
21989
|
+
if (promptData?.info?.error) {
|
|
21990
|
+
const err = promptData.info.error;
|
|
21991
|
+
const isModelError = err.name === "ProviderModelNotFoundError" || err.name === "ProviderNotFoundError" || err.name?.includes("Model") || err.name?.includes("Provider");
|
|
21992
|
+
if (isModelError && parentModel && resolvedModel !== parentModel) {
|
|
21993
|
+
log(`[background-manager] Model error with tier-mapped model, retrying with parent session model`, {
|
|
21994
|
+
taskId,
|
|
21995
|
+
error: err.name,
|
|
21996
|
+
failedModel: resolvedModel,
|
|
21997
|
+
fallbackModel: parentModel
|
|
21998
|
+
});
|
|
21999
|
+
promptBody.model = parentModel;
|
|
22000
|
+
promptResp = await ctx.client.session.prompt({
|
|
22001
|
+
path: { id: sessionID },
|
|
22002
|
+
body: promptBody,
|
|
22003
|
+
query: { directory: ctx.directory }
|
|
22004
|
+
});
|
|
22005
|
+
promptData = promptResp.data;
|
|
22006
|
+
if (promptResp.error) {
|
|
22007
|
+
throw new Error(`Prompt failed after retry: ${JSON.stringify(promptResp.error)}`);
|
|
22008
|
+
}
|
|
21980
22009
|
}
|
|
21981
22010
|
}
|
|
21982
|
-
|
|
21983
|
-
|
|
21984
|
-
|
|
21985
|
-
|
|
21986
|
-
throw new Error(`[${err.name}] ${errMsg}`);
|
|
21987
|
-
}
|
|
21988
|
-
const result = promptData?.parts?.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
|
|
21989
|
-
`) || "";
|
|
21990
|
-
task.result = result;
|
|
21991
|
-
task.status = "completed";
|
|
21992
|
-
task.completedAt = Date.now();
|
|
21993
|
-
log(`Background task completed`, { taskId, duration: task.completedAt - task.startedAt });
|
|
21994
|
-
ctx.client.tui.showToast({
|
|
21995
|
-
body: {
|
|
21996
|
-
title: "Background Task Completed",
|
|
21997
|
-
message: `${description.substring(0, 40)}...`,
|
|
21998
|
-
variant: "success",
|
|
21999
|
-
duration: 3000
|
|
22011
|
+
if (promptData?.info?.error) {
|
|
22012
|
+
const err = promptData.info.error;
|
|
22013
|
+
const errMsg = err.data?.message || err.name || "Unknown error";
|
|
22014
|
+
throw new Error(`[${err.name}] ${errMsg}`);
|
|
22000
22015
|
}
|
|
22001
|
-
|
|
22002
|
-
|
|
22003
|
-
|
|
22004
|
-
|
|
22005
|
-
|
|
22006
|
-
|
|
22007
|
-
|
|
22008
|
-
|
|
22009
|
-
|
|
22010
|
-
|
|
22011
|
-
|
|
22016
|
+
const result = promptData?.parts?.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
|
|
22017
|
+
`) || "";
|
|
22018
|
+
task.result = result;
|
|
22019
|
+
task.status = "completed";
|
|
22020
|
+
task.completedAt = Date.now();
|
|
22021
|
+
log(`Background task completed`, { taskId, duration: task.completedAt - task.startedAt });
|
|
22022
|
+
ctx.client.tui.showToast({
|
|
22023
|
+
body: {
|
|
22024
|
+
title: "Background Task Completed",
|
|
22025
|
+
message: `${description.substring(0, 40)}...`,
|
|
22026
|
+
variant: "success",
|
|
22027
|
+
duration: 3000
|
|
22028
|
+
}
|
|
22029
|
+
}).catch((err) => {
|
|
22030
|
+
log(`Toast notification failed`, { taskId, error: String(err) });
|
|
22031
|
+
});
|
|
22032
|
+
} catch (err) {
|
|
22033
|
+
task.status = "failed";
|
|
22034
|
+
task.error = String(err);
|
|
22035
|
+
task.completedAt = Date.now();
|
|
22036
|
+
log(`Background task failed`, { taskId, error: task.error });
|
|
22037
|
+
}
|
|
22038
|
+
})();
|
|
22039
|
+
return task;
|
|
22040
|
+
} catch (err) {
|
|
22041
|
+
const failedTask = {
|
|
22042
|
+
id: taskId,
|
|
22043
|
+
status: "failed",
|
|
22044
|
+
description,
|
|
22045
|
+
parentSessionID,
|
|
22046
|
+
error: String(err),
|
|
22047
|
+
startedAt: Date.now(),
|
|
22048
|
+
completedAt: Date.now()
|
|
22049
|
+
};
|
|
22050
|
+
tasks.set(taskId, failedTask);
|
|
22051
|
+
log(`Background task failed during creation`, { taskId, error: String(err) });
|
|
22052
|
+
return failedTask;
|
|
22053
|
+
}
|
|
22012
22054
|
};
|
|
22013
22055
|
const getTask = (taskId) => {
|
|
22014
22056
|
return tasks.get(taskId);
|
|
@@ -34513,12 +34555,22 @@ Prompts MUST be in English. Use \`background_output\` for async results.`,
|
|
|
34513
34555
|
|
|
34514
34556
|
${prompt}`;
|
|
34515
34557
|
const parentModel = await manager.getParentSessionModel(context.sessionID);
|
|
34516
|
-
|
|
34517
|
-
if (
|
|
34518
|
-
|
|
34519
|
-
|
|
34520
|
-
|
|
34521
|
-
|
|
34558
|
+
let resolvedModel = parentModel;
|
|
34559
|
+
if (modelService) {
|
|
34560
|
+
try {
|
|
34561
|
+
resolvedModel = modelService.resolveModelForAgentOrThrow(subagent_type, parentModel);
|
|
34562
|
+
if (resolvedModel && resolvedModel !== parentModel) {
|
|
34563
|
+
log(`[call-omco-agent] Using tier-mapped model for ${subagent_type}`, {
|
|
34564
|
+
providerID: resolvedModel.providerID,
|
|
34565
|
+
modelID: resolvedModel.modelID
|
|
34566
|
+
});
|
|
34567
|
+
}
|
|
34568
|
+
} catch (err) {
|
|
34569
|
+
return JSON.stringify({
|
|
34570
|
+
status: "failed",
|
|
34571
|
+
error: err instanceof Error ? err.message : String(err)
|
|
34572
|
+
});
|
|
34573
|
+
}
|
|
34522
34574
|
}
|
|
34523
34575
|
if (run_in_background) {
|
|
34524
34576
|
const task = await manager.createTask(context.sessionID, description, enhancedPrompt, subagent_type, resolvedModel);
|
|
@@ -34735,11 +34787,43 @@ function createModelResolutionService(modelMappingConfig, agentOverrides) {
|
|
|
34735
34787
|
}
|
|
34736
34788
|
return fallbackModel;
|
|
34737
34789
|
};
|
|
34790
|
+
const resolveModelForAgentOrThrow = (agentName, fallbackModel) => {
|
|
34791
|
+
const result = resolveModelForAgent(agentName, fallbackModel);
|
|
34792
|
+
if (result)
|
|
34793
|
+
return result;
|
|
34794
|
+
const tierDefaults2 = resolver.getTierDefaults();
|
|
34795
|
+
const hasConfiguredTiers2 = Object.values(tierDefaults2).some((m) => m.includes("/"));
|
|
34796
|
+
let errorMessage = `[OMCO] Cannot resolve model for agent "${agentName}".`;
|
|
34797
|
+
if (!hasConfiguredTiers2) {
|
|
34798
|
+
errorMessage += `
|
|
34799
|
+
|
|
34800
|
+
No tier mapping configured. Run one of:
|
|
34801
|
+
` + ` 1. npx omco-setup (interactive setup)
|
|
34802
|
+
` + ` 2. Add tierDefaults to ~/.config/opencode/omco.json:
|
|
34803
|
+
` + ` {
|
|
34804
|
+
` + ` "model_mapping": {
|
|
34805
|
+
` + ` "tierDefaults": {
|
|
34806
|
+
` + ` "haiku": "openai/gpt-4o-mini",
|
|
34807
|
+
` + ` "sonnet": "openai/gpt-4o",
|
|
34808
|
+
` + ` "opus": "openai/o1"
|
|
34809
|
+
` + ` }
|
|
34810
|
+
` + ` }
|
|
34811
|
+
` + ` }`;
|
|
34812
|
+
} else {
|
|
34813
|
+
errorMessage += `
|
|
34814
|
+
|
|
34815
|
+
Tier mapping is configured but no fallback model available.
|
|
34816
|
+
` + `This usually means the parent session hasn't started yet.
|
|
34817
|
+
` + `Try sending a message first to establish the session model.`;
|
|
34818
|
+
}
|
|
34819
|
+
throw new Error(errorMessage);
|
|
34820
|
+
};
|
|
34738
34821
|
const isTierMappingConfigured = () => {
|
|
34739
34822
|
return hasConfiguredTiers;
|
|
34740
34823
|
};
|
|
34741
34824
|
return {
|
|
34742
34825
|
resolveModelForAgent,
|
|
34826
|
+
resolveModelForAgentOrThrow,
|
|
34743
34827
|
isTierMappingConfigured
|
|
34744
34828
|
};
|
|
34745
34829
|
}
|
|
@@ -27,6 +27,14 @@ export interface ModelResolutionService {
|
|
|
27
27
|
* @returns Resolved ModelConfig or undefined if should use fallback
|
|
28
28
|
*/
|
|
29
29
|
resolveModelForAgent(agentName: string, fallbackModel?: ModelConfig): ModelConfig | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Resolve model for an agent, always returning a result or throwing
|
|
32
|
+
* @param agentName - Name of the agent (canonical or alias)
|
|
33
|
+
* @param fallbackModel - Parent session model to use if resolution fails
|
|
34
|
+
* @returns Resolved ModelConfig (never undefined)
|
|
35
|
+
* @throws Error with actionable message if model cannot be resolved
|
|
36
|
+
*/
|
|
37
|
+
resolveModelForAgentOrThrow(agentName: string, fallbackModel?: ModelConfig): ModelConfig;
|
|
30
38
|
/**
|
|
31
39
|
* Check if tier mapping is configured (tierDefaults has provider/model format)
|
|
32
40
|
*/
|
package/package.json
CHANGED