@serkanalgur/opencode-nexus 2.3.5 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +251 -114
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10085,6 +10085,21 @@ ${lines.join(`
|
|
|
10085
10085
|
}
|
|
10086
10086
|
|
|
10087
10087
|
// src/orchestrator.ts
|
|
10088
|
+
function assistantMessageText(message) {
|
|
10089
|
+
if (message.type !== "assistant")
|
|
10090
|
+
return "";
|
|
10091
|
+
return message.content.filter((part) => part.type === "text").map((part) => part.text).join("");
|
|
10092
|
+
}
|
|
10093
|
+
function lastAssistantText(messages) {
|
|
10094
|
+
for (let i = messages.length - 1;i >= 0; i--) {
|
|
10095
|
+
if (messages[i].type !== "assistant")
|
|
10096
|
+
continue;
|
|
10097
|
+
const text = assistantMessageText(messages[i]);
|
|
10098
|
+
if (text)
|
|
10099
|
+
return text;
|
|
10100
|
+
}
|
|
10101
|
+
return "";
|
|
10102
|
+
}
|
|
10088
10103
|
var DEFAULT_ESCALATION = {
|
|
10089
10104
|
maxRetries: 3,
|
|
10090
10105
|
retryDelay: 1000,
|
|
@@ -10136,7 +10151,7 @@ class NexusOrchestrator {
|
|
|
10136
10151
|
}
|
|
10137
10152
|
notifications = null;
|
|
10138
10153
|
modelCosts = new Map;
|
|
10139
|
-
|
|
10154
|
+
lastDegradedSpawn = null;
|
|
10140
10155
|
constructor(config, messageStoreConfig, memoryStoreConfig) {
|
|
10141
10156
|
this.config = this.mergeConfig(config);
|
|
10142
10157
|
this.budget = this.config.budget;
|
|
@@ -10161,7 +10176,7 @@ class NexusOrchestrator {
|
|
|
10161
10176
|
async initialize(ctx, onStateChange) {
|
|
10162
10177
|
this.ctx = ctx;
|
|
10163
10178
|
this.onStateChange = onStateChange ?? null;
|
|
10164
|
-
const projectDir = ctx.location
|
|
10179
|
+
const projectDir = ctx.location.directory;
|
|
10165
10180
|
this.configManager.loadFromPath(projectDir);
|
|
10166
10181
|
await this.loadModelCosts();
|
|
10167
10182
|
this.cleanupInterval = setInterval(() => this.cleanupStaleData(), 300000);
|
|
@@ -10181,41 +10196,22 @@ class NexusOrchestrator {
|
|
|
10181
10196
|
}
|
|
10182
10197
|
async loadModelCosts() {
|
|
10183
10198
|
try {
|
|
10184
|
-
if (!this.ctx)
|
|
10199
|
+
if (!this.ctx?.model)
|
|
10185
10200
|
return;
|
|
10186
|
-
|
|
10187
|
-
|
|
10188
|
-
const models = result?.data?.data ?? result?.data ?? [];
|
|
10189
|
-
if (Array.isArray(models)) {
|
|
10190
|
-
for (const model of models) {
|
|
10191
|
-
if (model.cost && Array.isArray(model.cost) && model.cost.length > 0) {
|
|
10192
|
-
const baseCost = model.cost[0];
|
|
10193
|
-
this.modelCosts.set(model.id, {
|
|
10194
|
-
input: baseCost.input || 0,
|
|
10195
|
-
output: baseCost.output || 0,
|
|
10196
|
-
cacheRead: baseCost.cache?.read || 0,
|
|
10197
|
-
cacheWrite: baseCost.cache?.write || 0
|
|
10198
|
-
});
|
|
10199
|
-
}
|
|
10200
|
-
}
|
|
10201
|
-
}
|
|
10201
|
+
const { data } = await this.ctx.model.list();
|
|
10202
|
+
if (!Array.isArray(data) || data.length === 0)
|
|
10202
10203
|
return;
|
|
10203
|
-
|
|
10204
|
-
const
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
const
|
|
10208
|
-
|
|
10209
|
-
|
|
10210
|
-
|
|
10211
|
-
|
|
10212
|
-
|
|
10213
|
-
|
|
10214
|
-
cacheRead: baseCost.cache?.read || 0,
|
|
10215
|
-
cacheWrite: baseCost.cache?.write || 0
|
|
10216
|
-
});
|
|
10217
|
-
}
|
|
10218
|
-
}
|
|
10204
|
+
const per1k = (v) => (v || 0) / 1000;
|
|
10205
|
+
for (const model of data) {
|
|
10206
|
+
if (!model?.cost || !Array.isArray(model.cost) || model.cost.length === 0)
|
|
10207
|
+
continue;
|
|
10208
|
+
const baseCost = model.cost[0];
|
|
10209
|
+
this.modelCosts.set(`${model.providerID}/${model.id}`, {
|
|
10210
|
+
input: per1k(baseCost.input),
|
|
10211
|
+
output: per1k(baseCost.output),
|
|
10212
|
+
cacheRead: per1k(baseCost.cache?.read),
|
|
10213
|
+
cacheWrite: per1k(baseCost.cache?.write)
|
|
10214
|
+
});
|
|
10219
10215
|
}
|
|
10220
10216
|
} catch {}
|
|
10221
10217
|
}
|
|
@@ -10564,15 +10560,14 @@ Please continue from where the previous agent left off.`;
|
|
|
10564
10560
|
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Task timed out")), timeout));
|
|
10565
10561
|
await Promise.race([waitPromise, timeoutPromise]);
|
|
10566
10562
|
const messages = await this.ctx.session.context({ sessionID: agent.sessionID });
|
|
10567
|
-
const
|
|
10568
|
-
const output = lastAssistantMsg?.content || "Task completed";
|
|
10563
|
+
const output = lastAssistantText(messages) || "Task completed (no output captured)";
|
|
10569
10564
|
const duration = Date.now() - startTime;
|
|
10570
10565
|
const result = {
|
|
10571
10566
|
success: true,
|
|
10572
|
-
output
|
|
10567
|
+
output,
|
|
10573
10568
|
duration,
|
|
10574
10569
|
tokensUsed: 0,
|
|
10575
|
-
cost: this.estimateModelCost(agent.model.model)
|
|
10570
|
+
cost: this.estimateModelCost(agent.model.model, agent.model.provider)
|
|
10576
10571
|
};
|
|
10577
10572
|
this.dag.markComplete(node.id, result);
|
|
10578
10573
|
this.todoEnforcer.completeTask(agent.id);
|
|
@@ -10608,7 +10603,7 @@ Please continue from where the previous agent left off.`;
|
|
|
10608
10603
|
if (priorPattern.length > 0) {
|
|
10609
10604
|
this.learning.recordSuccess(priorPattern[0].entry.id);
|
|
10610
10605
|
}
|
|
10611
|
-
if (
|
|
10606
|
+
if (output.length > 0) {
|
|
10612
10607
|
const securityIssues = this.securityScanner.scanContent(output, node.task.name);
|
|
10613
10608
|
if (securityIssues.length > 0) {
|
|
10614
10609
|
this.emit("security:issues-found", {
|
|
@@ -10769,7 +10764,57 @@ Please continue from where the previous agent left off.`;
|
|
|
10769
10764
|
};
|
|
10770
10765
|
this.notifyStateChange();
|
|
10771
10766
|
}
|
|
10772
|
-
async
|
|
10767
|
+
async createChildSession(params) {
|
|
10768
|
+
const { tool, agent, description, prompt, model, parent, callID } = params;
|
|
10769
|
+
if (!parent.sessionID) {
|
|
10770
|
+
throw new Error("Cannot spawn agent without a parent session ID");
|
|
10771
|
+
}
|
|
10772
|
+
if (!parent.agent) {
|
|
10773
|
+
throw new Error("Cannot spawn agent without the calling agent id (tool context has no 'agent')");
|
|
10774
|
+
}
|
|
10775
|
+
let childSessionID;
|
|
10776
|
+
let resolveChildSession = null;
|
|
10777
|
+
let watchdog;
|
|
10778
|
+
const childSessionReady = new Promise((resolve, reject) => {
|
|
10779
|
+
resolveChildSession = resolve;
|
|
10780
|
+
watchdog = setTimeout(() => reject(new Error(`subagent tool did not report a child session for ${agent}`)), 30000);
|
|
10781
|
+
});
|
|
10782
|
+
childSessionReady.catch(() => {});
|
|
10783
|
+
const reportProgress = (p) => {
|
|
10784
|
+
if (p?.sessionID && !childSessionID) {
|
|
10785
|
+
childSessionID = p.sessionID;
|
|
10786
|
+
resolveChildSession?.(p.sessionID);
|
|
10787
|
+
}
|
|
10788
|
+
return Promise.resolve();
|
|
10789
|
+
};
|
|
10790
|
+
const toolContext = {
|
|
10791
|
+
sessionID: parent.sessionID,
|
|
10792
|
+
agent: parent.agent,
|
|
10793
|
+
messageID: parent.messageID || `msg_${callID}`,
|
|
10794
|
+
id: `call_${callID}`,
|
|
10795
|
+
progress: reportProgress,
|
|
10796
|
+
signal: parent.signal ?? new AbortController().signal
|
|
10797
|
+
};
|
|
10798
|
+
const subagentCall = tool.execute({ agent, description, prompt, model, background: true }, toolContext);
|
|
10799
|
+
let childSessionIDResolved;
|
|
10800
|
+
try {
|
|
10801
|
+
childSessionIDResolved = await Promise.race([
|
|
10802
|
+
childSessionReady,
|
|
10803
|
+
subagentCall.then(() => {
|
|
10804
|
+
throw new Error(`subagent tool finished without reporting a child session for ${agent}`);
|
|
10805
|
+
}, (err) => {
|
|
10806
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10807
|
+
throw new Error(`subagent tool failed for ${agent}: ${message}`);
|
|
10808
|
+
})
|
|
10809
|
+
]);
|
|
10810
|
+
} finally {
|
|
10811
|
+
if (watchdog)
|
|
10812
|
+
clearTimeout(watchdog);
|
|
10813
|
+
}
|
|
10814
|
+
subagentCall.catch(() => {});
|
|
10815
|
+
return childSessionIDResolved;
|
|
10816
|
+
}
|
|
10817
|
+
async spawnAgent(config, options) {
|
|
10773
10818
|
if (!this.ctx) {
|
|
10774
10819
|
throw new Error("Orchestrator not initialized");
|
|
10775
10820
|
}
|
|
@@ -10793,26 +10838,58 @@ Please continue from where the previous agent left off.`;
|
|
|
10793
10838
|
const roleEmoji = this.configManager.getRoleEmoji(config.role);
|
|
10794
10839
|
const title = `${roleEmoji} ${this.configManager.getRoleDisplayName(config.role)} — ${modelConfig}`;
|
|
10795
10840
|
const agentTypeMap = {
|
|
10796
|
-
architect: "architect",
|
|
10797
|
-
coder: "
|
|
10798
|
-
reviewer: "
|
|
10799
|
-
tester: "
|
|
10800
|
-
explorer: "
|
|
10801
|
-
documenter: "
|
|
10841
|
+
architect: "nexus-architect",
|
|
10842
|
+
coder: "nexus-coder",
|
|
10843
|
+
reviewer: "nexus-reviewer",
|
|
10844
|
+
tester: "nexus-tester",
|
|
10845
|
+
explorer: "nexus-explorer",
|
|
10846
|
+
documenter: "nexus-documenter"
|
|
10802
10847
|
};
|
|
10803
|
-
const agentType = agentTypeMap[config.role] || "
|
|
10804
|
-
const
|
|
10805
|
-
|
|
10806
|
-
|
|
10807
|
-
|
|
10808
|
-
|
|
10809
|
-
|
|
10810
|
-
|
|
10811
|
-
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
10815
|
-
|
|
10848
|
+
const agentType = agentTypeMap[config.role] || "nexus-coder";
|
|
10849
|
+
const parent = options?.toolContext?.sessionID ? options.toolContext : undefined;
|
|
10850
|
+
const toolList = parent && typeof this.ctx.tool?.list === "function" ? await this.ctx.tool.list() : undefined;
|
|
10851
|
+
const subagentTool = Array.isArray(toolList) ? toolList.find((t) => t?.id === "subagent" && typeof t?.execute === "function") : undefined;
|
|
10852
|
+
const taskText = options?.task || config.task?.description || config.task?.name || "";
|
|
10853
|
+
let spawnPath = "session-create";
|
|
10854
|
+
let childSessionID;
|
|
10855
|
+
if (parent && subagentTool) {
|
|
10856
|
+
if (!taskText) {
|
|
10857
|
+
throw new Error("spawnAgent requires task text when using the subagent tool path");
|
|
10858
|
+
}
|
|
10859
|
+
spawnPath = "subagent-tool";
|
|
10860
|
+
childSessionID = await this.createChildSession({
|
|
10861
|
+
tool: subagentTool,
|
|
10862
|
+
agent: agentType,
|
|
10863
|
+
description: title,
|
|
10864
|
+
prompt: taskText,
|
|
10865
|
+
model: modelConfig,
|
|
10866
|
+
parent,
|
|
10867
|
+
callID: agentId
|
|
10868
|
+
});
|
|
10869
|
+
} else {
|
|
10870
|
+
const created = await this.ctx.session.create({
|
|
10871
|
+
title,
|
|
10872
|
+
agent: agentType,
|
|
10873
|
+
model: modelName ? { providerID: provider, id: modelName } : undefined,
|
|
10874
|
+
metadata: {
|
|
10875
|
+
nexusRole: config.role,
|
|
10876
|
+
nexusTask: config.task?.name || "direct-spawn",
|
|
10877
|
+
nexusAgentId: agentId,
|
|
10878
|
+
nexusModel: modelConfig
|
|
10879
|
+
}
|
|
10880
|
+
});
|
|
10881
|
+
childSessionID = created.id;
|
|
10882
|
+
}
|
|
10883
|
+
if (spawnPath === "session-create") {
|
|
10884
|
+
this.lastDegradedSpawn = {
|
|
10885
|
+
agentId,
|
|
10886
|
+
role: config.role,
|
|
10887
|
+
reason: parent ? "subagent-tool-unavailable" : "no-parent-context"
|
|
10888
|
+
};
|
|
10889
|
+
console.warn(`[nexus] spawn degraded for ${config.role}: child session ${childSessionID} was created via ` + `ctx.session.create (${this.lastDegradedSpawn.reason}) and is not linked to a parent session.`);
|
|
10890
|
+
} else {
|
|
10891
|
+
this.lastDegradedSpawn = null;
|
|
10892
|
+
}
|
|
10816
10893
|
const agent = {
|
|
10817
10894
|
id: agentId,
|
|
10818
10895
|
name: title,
|
|
@@ -10835,7 +10912,8 @@ Please continue from where the previous agent left off.`;
|
|
|
10835
10912
|
averageResponseTime: 0,
|
|
10836
10913
|
errorRate: 0
|
|
10837
10914
|
},
|
|
10838
|
-
sessionID:
|
|
10915
|
+
sessionID: childSessionID,
|
|
10916
|
+
spawnPath
|
|
10839
10917
|
};
|
|
10840
10918
|
this.agents.set(agentId, agent);
|
|
10841
10919
|
const taskDesc = config.task?.name || `Agent ${config.role} task`;
|
|
@@ -10890,10 +10968,29 @@ Please continue from where the previous agent left off.`;
|
|
|
10890
10968
|
selectModel(role, complexity) {
|
|
10891
10969
|
return this.selectBestModel(role, complexity);
|
|
10892
10970
|
}
|
|
10893
|
-
|
|
10894
|
-
const
|
|
10971
|
+
getModelCost(model, provider) {
|
|
10972
|
+
const exact = this.modelCosts.get(model);
|
|
10973
|
+
if (exact)
|
|
10974
|
+
return exact;
|
|
10975
|
+
if (provider) {
|
|
10976
|
+
const qualified = this.modelCosts.get(`${provider}/${model}`);
|
|
10977
|
+
if (qualified)
|
|
10978
|
+
return qualified;
|
|
10979
|
+
}
|
|
10980
|
+
const bare = model.slice(model.indexOf("/") + 1);
|
|
10981
|
+
let best;
|
|
10982
|
+
for (const [key, cost] of this.modelCosts) {
|
|
10983
|
+
if (key.slice(key.indexOf("/") + 1) !== bare)
|
|
10984
|
+
continue;
|
|
10985
|
+
if (!best || cost.input < best.input)
|
|
10986
|
+
best = cost;
|
|
10987
|
+
}
|
|
10988
|
+
return best;
|
|
10989
|
+
}
|
|
10990
|
+
estimateModelCost(model, provider) {
|
|
10991
|
+
const realCost = this.getModelCost(model, provider);
|
|
10895
10992
|
if (realCost) {
|
|
10896
|
-
return (realCost.input
|
|
10993
|
+
return (realCost.input + realCost.output) / 2;
|
|
10897
10994
|
}
|
|
10898
10995
|
const costs = {
|
|
10899
10996
|
"claude-sonnet-4-6": 0.15,
|
|
@@ -10933,7 +11030,7 @@ Please continue from where the previous agent left off.`;
|
|
|
10933
11030
|
scoreModel(modelId, role, complexity) {
|
|
10934
11031
|
const [provider, ...parts] = modelId.split("/");
|
|
10935
11032
|
const model = parts.join("/");
|
|
10936
|
-
const cost = this.estimateModelCost(model);
|
|
11033
|
+
const cost = this.estimateModelCost(model, provider);
|
|
10937
11034
|
const quality = this.estimateModelQuality(model);
|
|
10938
11035
|
const maxCost = 15;
|
|
10939
11036
|
const costScore = 1 - cost / maxCost;
|
|
@@ -10965,14 +11062,14 @@ Please continue from where the previous agent left off.`;
|
|
|
10965
11062
|
const scored = unique.map((m) => this.scoreModel(m, role, complexity));
|
|
10966
11063
|
const budgetRemaining = this.budget.maxTotalCost - this.totalSpent;
|
|
10967
11064
|
const affordable = scored.filter((s) => {
|
|
10968
|
-
const cost = this.estimateModelCost(s.model);
|
|
11065
|
+
const cost = this.estimateModelCost(s.model, s.provider);
|
|
10969
11066
|
return cost <= budgetRemaining || cost === 0;
|
|
10970
11067
|
});
|
|
10971
11068
|
const best = (affordable.length > 0 ? affordable : scored).sort((a, b) => b.overallScore - a.overallScore)[0];
|
|
10972
11069
|
return {
|
|
10973
11070
|
provider: best.provider,
|
|
10974
11071
|
model: best.model,
|
|
10975
|
-
estimatedCost: this.estimateModelCost(best.model),
|
|
11072
|
+
estimatedCost: this.estimateModelCost(best.model, best.provider),
|
|
10976
11073
|
estimatedQuality: best.qualityScore,
|
|
10977
11074
|
reasoning: best.reasoning
|
|
10978
11075
|
};
|
|
@@ -11466,6 +11563,28 @@ import { homedir as homedir2 } from "node:os";
|
|
|
11466
11563
|
var NEXUS_AGENT_CONTENT = `---
|
|
11467
11564
|
description: Nexus multi-agent orchestrator — decomposes tasks and delegates to specialized sub-agents
|
|
11468
11565
|
mode: primary
|
|
11566
|
+
permissions:
|
|
11567
|
+
- action: subagent
|
|
11568
|
+
resource: "nexus-*"
|
|
11569
|
+
effect: allow
|
|
11570
|
+
- action: subagent
|
|
11571
|
+
resource: "nexus-architect"
|
|
11572
|
+
effect: allow
|
|
11573
|
+
- action: subagent
|
|
11574
|
+
resource: "nexus-coder"
|
|
11575
|
+
effect: allow
|
|
11576
|
+
- action: subagent
|
|
11577
|
+
resource: "nexus-reviewer"
|
|
11578
|
+
effect: allow
|
|
11579
|
+
- action: subagent
|
|
11580
|
+
resource: "nexus-tester"
|
|
11581
|
+
effect: allow
|
|
11582
|
+
- action: subagent
|
|
11583
|
+
resource: "nexus-explorer"
|
|
11584
|
+
effect: allow
|
|
11585
|
+
- action: subagent
|
|
11586
|
+
resource: "nexus-documenter"
|
|
11587
|
+
effect: allow
|
|
11469
11588
|
---
|
|
11470
11589
|
|
|
11471
11590
|
# Nexus Orchestrator
|
|
@@ -11629,8 +11748,10 @@ mode: subagent
|
|
|
11629
11748
|
permissions:
|
|
11630
11749
|
- action: edit
|
|
11631
11750
|
resource: "*"
|
|
11751
|
+
effect: allow
|
|
11632
11752
|
- action: shell
|
|
11633
11753
|
resource: "*"
|
|
11754
|
+
effect: allow
|
|
11634
11755
|
---
|
|
11635
11756
|
|
|
11636
11757
|
# Nexus Architect Agent
|
|
@@ -11671,8 +11792,10 @@ mode: subagent
|
|
|
11671
11792
|
permissions:
|
|
11672
11793
|
- action: edit
|
|
11673
11794
|
resource: "*"
|
|
11795
|
+
effect: allow
|
|
11674
11796
|
- action: shell
|
|
11675
11797
|
resource: "*"
|
|
11798
|
+
effect: allow
|
|
11676
11799
|
---
|
|
11677
11800
|
|
|
11678
11801
|
# Nexus Coder Agent
|
|
@@ -11758,8 +11881,10 @@ mode: subagent
|
|
|
11758
11881
|
permissions:
|
|
11759
11882
|
- action: edit
|
|
11760
11883
|
resource: "*"
|
|
11884
|
+
effect: allow
|
|
11761
11885
|
- action: shell
|
|
11762
11886
|
resource: "*"
|
|
11887
|
+
effect: allow
|
|
11763
11888
|
---
|
|
11764
11889
|
|
|
11765
11890
|
# Nexus Tester Agent
|
|
@@ -11857,8 +11982,10 @@ mode: subagent
|
|
|
11857
11982
|
permissions:
|
|
11858
11983
|
- action: edit
|
|
11859
11984
|
resource: "*"
|
|
11985
|
+
effect: allow
|
|
11860
11986
|
- action: shell
|
|
11861
11987
|
resource: "*"
|
|
11988
|
+
effect: allow
|
|
11862
11989
|
---
|
|
11863
11990
|
|
|
11864
11991
|
# Nexus Documenter Agent
|
|
@@ -11936,6 +12063,25 @@ You are a technical writer who creates documentation that developers actually wa
|
|
|
11936
12063
|
totalCost: initialState.totalSpent,
|
|
11937
12064
|
budgetRemaining: initialState.budgetRemaining
|
|
11938
12065
|
})));
|
|
12066
|
+
const spawnAndDeliver = async (opts, toolCtx) => {
|
|
12067
|
+
const agent = await orchestrator.spawnAgent({ role: opts.role, model: opts.model }, {
|
|
12068
|
+
toolContext: {
|
|
12069
|
+
sessionID: toolCtx?.sessionID || "",
|
|
12070
|
+
agent: toolCtx?.agent,
|
|
12071
|
+
messageID: toolCtx?.messageID,
|
|
12072
|
+
callID: toolCtx?.id,
|
|
12073
|
+
signal: toolCtx?.signal
|
|
12074
|
+
},
|
|
12075
|
+
task: opts.task
|
|
12076
|
+
});
|
|
12077
|
+
if (agent.spawnPath !== "subagent-tool") {
|
|
12078
|
+
await ctx.session.prompt({
|
|
12079
|
+
sessionID: agent.sessionID,
|
|
12080
|
+
text: opts.task
|
|
12081
|
+
});
|
|
12082
|
+
}
|
|
12083
|
+
return agent;
|
|
12084
|
+
};
|
|
11939
12085
|
await ctx.tool.transform((editor) => {
|
|
11940
12086
|
editor.namespace({
|
|
11941
12087
|
name: "nexus",
|
|
@@ -12122,35 +12268,36 @@ You are a technical writer who creates documentation that developers actually wa
|
|
|
12122
12268
|
});
|
|
12123
12269
|
editor.add({
|
|
12124
12270
|
name: "model.costs",
|
|
12125
|
-
description: "Show real model pricing from OpenCode or set custom costs",
|
|
12271
|
+
description: "Show real model pricing from OpenCode, or set custom costs. All prices are USD per 1K tokens.",
|
|
12126
12272
|
input: {
|
|
12127
12273
|
type: "object",
|
|
12128
12274
|
properties: {
|
|
12129
|
-
model: { type: "string", description: "Model
|
|
12130
|
-
setInput: { type: "number", description: "Set input cost per
|
|
12131
|
-
setOutput: { type: "number", description: "Set output cost per
|
|
12275
|
+
model: { type: "string", description: "Model to show cost for, as 'provider/id' or a bare 'id' (optional, shows all if omitted)" },
|
|
12276
|
+
setInput: { type: "number", description: "Set input cost in USD per 1K tokens for a model (e.g. 0.003 for $3 per million tokens)" },
|
|
12277
|
+
setOutput: { type: "number", description: "Set output cost in USD per 1K tokens for a model (e.g. 0.015 for $15 per million tokens)" }
|
|
12132
12278
|
},
|
|
12133
12279
|
additionalProperties: false
|
|
12134
12280
|
},
|
|
12135
12281
|
options: { codemode: true },
|
|
12136
12282
|
execute: async (input) => {
|
|
12137
12283
|
const { model, setInput, setOutput } = input;
|
|
12284
|
+
const per1k = (v) => `$${v}/1K tokens`;
|
|
12138
12285
|
if (model && setInput !== undefined && setOutput !== undefined) {
|
|
12139
12286
|
orchestrator.setModelCosts({ [model]: { input: setInput, output: setOutput } });
|
|
12140
|
-
return { content: `Set ${model}: input
|
|
12287
|
+
return { content: `Set ${model}: input=${per1k(setInput)}, output=${per1k(setOutput)}` };
|
|
12141
12288
|
}
|
|
12142
12289
|
if (model) {
|
|
12143
|
-
const cost = orchestrator.
|
|
12290
|
+
const cost = orchestrator.getModelCost(model);
|
|
12144
12291
|
if (cost) {
|
|
12145
|
-
return { content: `${model}: input
|
|
12292
|
+
return { content: `${model}: input=${per1k(cost.input)}, output=${per1k(cost.output)}, cache_read=${per1k(cost.cacheRead)}, cache_write=${per1k(cost.cacheWrite)}` };
|
|
12146
12293
|
}
|
|
12147
12294
|
const estimate = orchestrator["estimateModelCost"](model);
|
|
12148
12295
|
return { content: `${model}: no real pricing data (estimated $${estimate}/1K tokens)` };
|
|
12149
12296
|
}
|
|
12150
12297
|
if (orchestrator.modelCosts.size > 0) {
|
|
12151
|
-
const lines = ["\uD83D\uDCCA Model Pricing (from OpenCode):"];
|
|
12298
|
+
const lines = ["\uD83D\uDCCA Model Pricing (from OpenCode, per 1K tokens):"];
|
|
12152
12299
|
for (const [id, cost] of orchestrator.modelCosts) {
|
|
12153
|
-
lines.push(` ${id}:
|
|
12300
|
+
lines.push(` ${id}: ${per1k(cost.input)} in, ${per1k(cost.output)} out`);
|
|
12154
12301
|
}
|
|
12155
12302
|
return { content: lines.join(`
|
|
12156
12303
|
`) };
|
|
@@ -12174,27 +12321,15 @@ You are a technical writer who creates documentation that developers actually wa
|
|
|
12174
12321
|
additionalProperties: false
|
|
12175
12322
|
},
|
|
12176
12323
|
options: { codemode: true },
|
|
12177
|
-
execute: async (input) => {
|
|
12324
|
+
execute: async (input, toolCtx) => {
|
|
12178
12325
|
const { role, task, model, wait, timeout } = input;
|
|
12179
12326
|
try {
|
|
12180
|
-
|
|
12181
|
-
try {
|
|
12182
|
-
const currentSession = ctx.session?.current?.();
|
|
12183
|
-
if (currentSession?.id) {
|
|
12184
|
-
orchestrator.parentSessionID = currentSession.id;
|
|
12185
|
-
}
|
|
12186
|
-
} catch {}
|
|
12187
|
-
}
|
|
12188
|
-
const agent = await orchestrator.spawnAgent({ role, model });
|
|
12189
|
-
await orchestrator.ctx.session.prompt({
|
|
12190
|
-
sessionID: agent.sessionID,
|
|
12191
|
-
text: task
|
|
12192
|
-
});
|
|
12327
|
+
const agent = await spawnAndDeliver({ role, task, model }, toolCtx);
|
|
12193
12328
|
agent.status = "working";
|
|
12194
12329
|
orchestrator.notifyStateChange();
|
|
12195
12330
|
if (wait) {
|
|
12196
12331
|
const waitTimeout = timeout || 120000;
|
|
12197
|
-
const waitPromise =
|
|
12332
|
+
const waitPromise = ctx.session.wait({ sessionID: agent.sessionID });
|
|
12198
12333
|
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`Timed out after ${waitTimeout}ms`)), waitTimeout));
|
|
12199
12334
|
try {
|
|
12200
12335
|
await Promise.race([waitPromise, timeoutPromise]);
|
|
@@ -12202,9 +12337,9 @@ You are a technical writer who creates documentation that developers actually wa
|
|
|
12202
12337
|
agent.status = "working";
|
|
12203
12338
|
orchestrator.notifyStateChange();
|
|
12204
12339
|
try {
|
|
12205
|
-
const messages = await
|
|
12206
|
-
const
|
|
12207
|
-
if (
|
|
12340
|
+
const messages = await ctx.session.context({ sessionID: agent.sessionID });
|
|
12341
|
+
const partialText = lastAssistantText(messages);
|
|
12342
|
+
if (partialText) {
|
|
12208
12343
|
agent.status = "completed";
|
|
12209
12344
|
await ctx.storage.set("orchestrator-state", JSON.parse(JSON.stringify(orchestrator.getState())));
|
|
12210
12345
|
const taskPreview = task.length > 80 ? task.substring(0, 77) + "..." : task;
|
|
@@ -12216,7 +12351,7 @@ You are a technical writer who creates documentation that developers actually wa
|
|
|
12216
12351
|
`\uD83D\uDCCE Session: ${agent.sessionID}`,
|
|
12217
12352
|
`
|
|
12218
12353
|
--- Partial Result ---`,
|
|
12219
|
-
|
|
12354
|
+
partialText
|
|
12220
12355
|
].join(`
|
|
12221
12356
|
`)
|
|
12222
12357
|
};
|
|
@@ -12234,9 +12369,8 @@ You are a technical writer who creates documentation that developers actually wa
|
|
|
12234
12369
|
};
|
|
12235
12370
|
}
|
|
12236
12371
|
try {
|
|
12237
|
-
const messages = await
|
|
12238
|
-
const
|
|
12239
|
-
const result = lastMsg?.content || "Task completed (no output captured)";
|
|
12372
|
+
const messages = await ctx.session.context({ sessionID: agent.sessionID });
|
|
12373
|
+
const result = lastAssistantText(messages) || "Task completed (no output captured)";
|
|
12240
12374
|
agent.status = "completed";
|
|
12241
12375
|
await ctx.storage.set("orchestrator-state", JSON.parse(JSON.stringify(orchestrator.getState())));
|
|
12242
12376
|
const taskPreview = task.length > 80 ? task.substring(0, 77) + "..." : task;
|
|
@@ -12311,24 +12445,19 @@ You are a technical writer who creates documentation that developers actually wa
|
|
|
12311
12445
|
additionalProperties: false
|
|
12312
12446
|
},
|
|
12313
12447
|
options: { codemode: true },
|
|
12314
|
-
execute: async (input) => {
|
|
12448
|
+
execute: async (input, toolCtx) => {
|
|
12315
12449
|
const { role, task, model, timeout } = input;
|
|
12316
12450
|
try {
|
|
12317
|
-
const agent = await
|
|
12318
|
-
await orchestrator.ctx.session.prompt({
|
|
12319
|
-
sessionID: agent.sessionID,
|
|
12320
|
-
text: task
|
|
12321
|
-
});
|
|
12451
|
+
const agent = await spawnAndDeliver({ role, task, model }, toolCtx);
|
|
12322
12452
|
agent.status = "working";
|
|
12323
12453
|
orchestrator.notifyStateChange();
|
|
12324
12454
|
const waitTimeout = timeout || 120000;
|
|
12325
|
-
const waitPromise =
|
|
12455
|
+
const waitPromise = ctx.session.wait({ sessionID: agent.sessionID });
|
|
12326
12456
|
const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve("timeout"), waitTimeout));
|
|
12327
12457
|
const outcome = await Promise.race([waitPromise.then(() => "completed"), timeoutPromise]);
|
|
12328
12458
|
try {
|
|
12329
|
-
const messages = await
|
|
12330
|
-
const
|
|
12331
|
-
const resultContent = lastMsg?.content || (outcome === "timeout" ? "Timed out — agent may still be running" : "Completed with no output");
|
|
12459
|
+
const messages = await ctx.session.context({ sessionID: agent.sessionID });
|
|
12460
|
+
const resultContent = lastAssistantText(messages) || (outcome === "timeout" ? "Timed out — agent may still be running" : "Completed with no output");
|
|
12332
12461
|
agent.status = outcome === "timeout" ? "working" : "completed";
|
|
12333
12462
|
orchestrator.notifyStateChange();
|
|
12334
12463
|
await ctx.storage.set("orchestrator-state", JSON.parse(JSON.stringify(orchestrator.getState())));
|
|
@@ -12630,12 +12759,20 @@ ${lines.join(`
|
|
|
12630
12759
|
const agents = orchestrator.getState().agents.filter((a) => a.status === "working" || a.status === "idle");
|
|
12631
12760
|
if (agents.length === 0)
|
|
12632
12761
|
return { content: "No running agents to move to background." };
|
|
12762
|
+
const background = ctx.session.background;
|
|
12763
|
+
if (!background) {
|
|
12764
|
+
return { content: "This OpenCode version does not expose session.background on the plugin context — nothing was detached." };
|
|
12765
|
+
}
|
|
12766
|
+
let detached = 0;
|
|
12633
12767
|
for (const agent of agents) {
|
|
12768
|
+
if (!agent.sessionID)
|
|
12769
|
+
continue;
|
|
12634
12770
|
try {
|
|
12635
|
-
await
|
|
12771
|
+
await background.call(ctx.session, { sessionID: agent.sessionID });
|
|
12772
|
+
detached++;
|
|
12636
12773
|
} catch {}
|
|
12637
12774
|
}
|
|
12638
|
-
return { content: `${
|
|
12775
|
+
return { content: `${detached} agent(s) moved to background. You can continue working while they run.` };
|
|
12639
12776
|
}
|
|
12640
12777
|
});
|
|
12641
12778
|
editor.add({
|
|
@@ -12652,11 +12789,11 @@ ${lines.join(`
|
|
|
12652
12789
|
execute: async (input) => {
|
|
12653
12790
|
const { sessionID } = input;
|
|
12654
12791
|
try {
|
|
12655
|
-
const messages = await
|
|
12656
|
-
const
|
|
12657
|
-
if (
|
|
12792
|
+
const messages = await ctx.session.context({ sessionID });
|
|
12793
|
+
const text = lastAssistantText(messages);
|
|
12794
|
+
if (text) {
|
|
12658
12795
|
return { content: `Session ${sessionID} result:
|
|
12659
|
-
${
|
|
12796
|
+
${text}` };
|
|
12660
12797
|
}
|
|
12661
12798
|
return { content: `Session ${sessionID} has no assistant messages yet.` };
|
|
12662
12799
|
} catch (error) {
|