@serkanalgur/opencode-nexus 1.2.6 → 1.3.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 CHANGED
@@ -8559,12 +8559,12 @@ class NexusConfigManager {
8559
8559
  }
8560
8560
  saveProjectConfig(basePath) {
8561
8561
  const projectPath = join(basePath, ".opencode", "nexus.jsonc");
8562
- const config = this.getNonDefaultConfig();
8562
+ const config = this.getSaveableConfig();
8563
8563
  this.writeJsoncFile(projectPath, config);
8564
8564
  }
8565
8565
  saveGlobalConfig() {
8566
8566
  const globalPath = join(homedir(), ".config", "opencode", "nexus.jsonc");
8567
- const config = this.getNonDefaultConfig();
8567
+ const config = this.getSaveableConfig();
8568
8568
  this.writeJsoncFile(globalPath, config);
8569
8569
  }
8570
8570
  initProjectConfig(basePath) {
@@ -8582,38 +8582,12 @@ class NexusConfigManager {
8582
8582
  this.saveGlobalConfig();
8583
8583
  }
8584
8584
  }
8585
- getNonDefaultConfig() {
8585
+ getSaveableConfig() {
8586
8586
  const current = this.getConfig();
8587
8587
  const result = {};
8588
- const models = {};
8589
- for (const role of this.getRoles()) {
8590
- if (current.models[role] !== DEFAULT_CONFIG.models[role]) {
8591
- models[role] = current.models[role];
8592
- }
8593
- }
8594
- if (Object.keys(models).length > 0) {
8595
- result.models = models;
8596
- }
8597
- const budget = {};
8598
- const budgetKeys = ["maxTotalCost", "maxCostPerTask", "maxCostPerAgent", "alertThreshold"];
8599
- for (const key of budgetKeys) {
8600
- if (current.budget[key] !== DEFAULT_CONFIG.budget[key]) {
8601
- budget[key] = current.budget[key];
8602
- }
8603
- }
8604
- if (Object.keys(budget).length > 0) {
8605
- result.budget = budget;
8606
- }
8607
- const selfHealing = {};
8608
- const shKeys = ["enabled", "maxRetries", "contextTransfer"];
8609
- for (const key of shKeys) {
8610
- if (current.selfHealing[key] !== DEFAULT_CONFIG.selfHealing[key]) {
8611
- selfHealing[key] = current.selfHealing[key];
8612
- }
8613
- }
8614
- if (Object.keys(selfHealing).length > 0) {
8615
- result.selfHealing = selfHealing;
8616
- }
8588
+ result.models = { ...current.models };
8589
+ result.budget = { ...current.budget };
8590
+ result.selfHealing = { ...current.selfHealing };
8617
8591
  return result;
8618
8592
  }
8619
8593
  resetToDefaults() {
@@ -9766,6 +9740,7 @@ class NexusOrchestrator {
9766
9740
  return this._healthMonitor;
9767
9741
  }
9768
9742
  notifications = null;
9743
+ modelCosts = new Map;
9769
9744
  constructor(config, messageStoreConfig, memoryStoreConfig) {
9770
9745
  this.config = this.mergeConfig(config);
9771
9746
  this.budget = this.config.budget;
@@ -9787,6 +9762,7 @@ class NexusOrchestrator {
9787
9762
  this.ctx = ctx;
9788
9763
  this.onStateChange = onStateChange ?? null;
9789
9764
  this.configManager.loadFromPath(process.cwd());
9765
+ await this.loadModelCosts();
9790
9766
  this.cleanupInterval = setInterval(() => this.cleanupStaleData(), 300000);
9791
9767
  this._healthMonitor = new HealthMonitor({
9792
9768
  checkInterval: this.config.agents.healthCheckInterval
@@ -9802,6 +9778,56 @@ class NexusOrchestrator {
9802
9778
  };
9803
9779
  await this.moduleRegistry.setupAll(moduleCtx);
9804
9780
  }
9781
+ async loadModelCosts() {
9782
+ try {
9783
+ if (!this.ctx)
9784
+ return;
9785
+ if (this.ctx.client?.model?.list) {
9786
+ const result = await this.ctx.client.model.list();
9787
+ const models = result?.data?.data ?? result?.data ?? [];
9788
+ if (Array.isArray(models)) {
9789
+ for (const model of models) {
9790
+ if (model.cost && Array.isArray(model.cost) && model.cost.length > 0) {
9791
+ const baseCost = model.cost[0];
9792
+ this.modelCosts.set(model.id, {
9793
+ input: baseCost.input || 0,
9794
+ output: baseCost.output || 0,
9795
+ cacheRead: baseCost.cache?.read || 0,
9796
+ cacheWrite: baseCost.cache?.write || 0
9797
+ });
9798
+ }
9799
+ }
9800
+ }
9801
+ return;
9802
+ }
9803
+ const location = this.ctx.location ?? this.ctx.data?.location?.default();
9804
+ if (location && this.ctx.data?.location?.model) {
9805
+ await this.ctx.data.location.model.sync(location);
9806
+ const models = this.ctx.data.location.model.list(location) ?? [];
9807
+ for (const model of models) {
9808
+ if (model.cost && Array.isArray(model.cost) && model.cost.length > 0) {
9809
+ const baseCost = model.cost[0];
9810
+ this.modelCosts.set(model.id, {
9811
+ input: baseCost.input || 0,
9812
+ output: baseCost.output || 0,
9813
+ cacheRead: baseCost.cache?.read || 0,
9814
+ cacheWrite: baseCost.cache?.write || 0
9815
+ });
9816
+ }
9817
+ }
9818
+ }
9819
+ } catch {}
9820
+ }
9821
+ setModelCosts(costs) {
9822
+ for (const [model, cost] of Object.entries(costs)) {
9823
+ this.modelCosts.set(model, {
9824
+ input: cost.input,
9825
+ output: cost.output,
9826
+ cacheRead: cost.cacheRead || 0,
9827
+ cacheWrite: cost.cacheWrite || 0
9828
+ });
9829
+ }
9830
+ }
9805
9831
  initBroadcaster(opts) {
9806
9832
  this.broadcaster = new StateBroadcaster(this, opts);
9807
9833
  const previousOnStateChange = this.onStateChange;
@@ -10380,6 +10406,10 @@ Please continue from where the previous agent left off.`;
10380
10406
  return this.selectBestModel(role, complexity);
10381
10407
  }
10382
10408
  estimateModelCost(model) {
10409
+ const realCost = this.modelCosts.get(model);
10410
+ if (realCost) {
10411
+ return (realCost.input * 1000 + realCost.output * 1000) / 2;
10412
+ }
10383
10413
  const costs = {
10384
10414
  "claude-sonnet-4-6": 0.15,
10385
10415
  "claude-opus-4-7": 15,
@@ -10931,6 +10961,43 @@ var src_default = define({
10931
10961
  return { content: "Dashboard stopped" };
10932
10962
  }
10933
10963
  });
10964
+ editor.add({
10965
+ name: "model.costs",
10966
+ description: "Show real model pricing from OpenCode or set custom costs",
10967
+ input: {
10968
+ type: "object",
10969
+ properties: {
10970
+ model: { type: "string", description: "Model ID to show cost for (optional, shows all if omitted)" },
10971
+ setInput: { type: "number", description: "Set input cost per token for a model" },
10972
+ setOutput: { type: "number", description: "Set output cost per token for a model" }
10973
+ },
10974
+ additionalProperties: false
10975
+ },
10976
+ execute: async (input) => {
10977
+ const { model, setInput, setOutput } = input;
10978
+ if (model && setInput !== undefined && setOutput !== undefined) {
10979
+ orchestrator.setModelCosts({ [model]: { input: setInput, output: setOutput } });
10980
+ return { content: `Set ${model}: input=$${setInput}/token, output=$${setOutput}/token` };
10981
+ }
10982
+ if (model) {
10983
+ const cost = orchestrator.modelCosts.get(model);
10984
+ if (cost) {
10985
+ return { content: `${model}: input=$${cost.input}/token, output=$${cost.output}/token, cache_read=$${cost.cacheRead}/token, cache_write=$${cost.cacheWrite}/token` };
10986
+ }
10987
+ const estimate = orchestrator["estimateModelCost"](model);
10988
+ return { content: `${model}: no real pricing data (estimated $${estimate}/1K tokens)` };
10989
+ }
10990
+ if (orchestrator.modelCosts.size > 0) {
10991
+ const lines = ["\uD83D\uDCCA Model Pricing (from OpenCode):"];
10992
+ for (const [id, cost] of orchestrator.modelCosts) {
10993
+ lines.push(` ${id}: $${cost.input}/token in, $${cost.output}/token out`);
10994
+ }
10995
+ return { content: lines.join(`
10996
+ `) };
10997
+ }
10998
+ return { content: "No real pricing data loaded. Using hardcoded estimates." };
10999
+ }
11000
+ });
10934
11001
  editor.add({
10935
11002
  name: "spawn",
10936
11003
  description: "Spawn a sub-agent for a task",
package/dist/tui.js CHANGED
@@ -302,12 +302,12 @@ class NexusConfigManager {
302
302
  }
303
303
  saveProjectConfig(basePath) {
304
304
  const projectPath = join(basePath, ".opencode", "nexus.jsonc");
305
- const config = this.getNonDefaultConfig();
305
+ const config = this.getSaveableConfig();
306
306
  this.writeJsoncFile(projectPath, config);
307
307
  }
308
308
  saveGlobalConfig() {
309
309
  const globalPath = join(homedir(), ".config", "opencode", "nexus.jsonc");
310
- const config = this.getNonDefaultConfig();
310
+ const config = this.getSaveableConfig();
311
311
  this.writeJsoncFile(globalPath, config);
312
312
  }
313
313
  initProjectConfig(basePath) {
@@ -325,38 +325,12 @@ class NexusConfigManager {
325
325
  this.saveGlobalConfig();
326
326
  }
327
327
  }
328
- getNonDefaultConfig() {
328
+ getSaveableConfig() {
329
329
  const current = this.getConfig();
330
330
  const result = {};
331
- const models = {};
332
- for (const role of this.getRoles()) {
333
- if (current.models[role] !== DEFAULT_CONFIG.models[role]) {
334
- models[role] = current.models[role];
335
- }
336
- }
337
- if (Object.keys(models).length > 0) {
338
- result.models = models;
339
- }
340
- const budget = {};
341
- const budgetKeys = ["maxTotalCost", "maxCostPerTask", "maxCostPerAgent", "alertThreshold"];
342
- for (const key of budgetKeys) {
343
- if (current.budget[key] !== DEFAULT_CONFIG.budget[key]) {
344
- budget[key] = current.budget[key];
345
- }
346
- }
347
- if (Object.keys(budget).length > 0) {
348
- result.budget = budget;
349
- }
350
- const selfHealing = {};
351
- const shKeys = ["enabled", "maxRetries", "contextTransfer"];
352
- for (const key of shKeys) {
353
- if (current.selfHealing[key] !== DEFAULT_CONFIG.selfHealing[key]) {
354
- selfHealing[key] = current.selfHealing[key];
355
- }
356
- }
357
- if (Object.keys(selfHealing).length > 0) {
358
- result.selfHealing = selfHealing;
359
- }
331
+ result.models = { ...current.models };
332
+ result.budget = { ...current.budget };
333
+ result.selfHealing = { ...current.selfHealing };
360
334
  return result;
361
335
  }
362
336
  resetToDefaults() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serkanalgur/opencode-nexus",
3
- "version": "1.2.6",
3
+ "version": "1.3.0",
4
4
  "description": "Adaptive Multi-Agent Orchestration with Cost Intelligence for OpenCode V2",
5
5
  "keywords": [
6
6
  "opencode",