@cortexkit/opencode-magic-context 0.8.8 → 0.8.10

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.
@@ -1 +1 @@
1
- {"version":3,"file":"embedding-local.d.ts","sourceRoot":"","sources":["../../../../src/features/magic-context/memory/embedding-local.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAyH9D,qBAAa,sBAAuB,YAAW,iBAAiB;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,WAAW,CAA8B;gBAErC,KAAK,SAAgC;IAK3C,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IA+E9B,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAyBjD,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,EAAE,CAAC;IA6B7D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAqB9B,QAAQ,IAAI,OAAO;CAGtB"}
1
+ {"version":3,"file":"embedding-local.d.ts","sourceRoot":"","sources":["../../../../src/features/magic-context/memory/embedding-local.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAyH9D,qBAAa,sBAAuB,YAAW,iBAAiB;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,WAAW,CAA8B;gBAErC,KAAK,SAAgC;IAK3C,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAqG9B,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAyBjD,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,EAAE,CAAC;IA6B7D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAqB9B,QAAQ,IAAI,OAAO;CAGtB"}
package/dist/index.js CHANGED
@@ -16166,6 +16166,8 @@ function cosineSimilarity(a, b) {
16166
16166
  }
16167
16167
 
16168
16168
  // src/features/magic-context/memory/embedding-local.ts
16169
+ import { mkdirSync } from "fs";
16170
+ import { join as join9 } from "path";
16169
16171
  async function withQuietConsole(fn) {
16170
16172
  const origWarn = console.warn;
16171
16173
  const origError = console.error;
@@ -16250,6 +16252,13 @@ class LocalEmbeddingProvider {
16250
16252
  if (LogLevel && "ERROR" in LogLevel) {
16251
16253
  env.logLevel = LogLevel.ERROR;
16252
16254
  }
16255
+ const modelCacheDir = join9(getOpenCodeStorageDir(), "plugin", "magic-context", "models");
16256
+ try {
16257
+ mkdirSync(modelCacheDir, { recursive: true });
16258
+ env.cacheDir = modelCacheDir;
16259
+ } catch {
16260
+ log("[magic-context] could not create model cache dir, using library default");
16261
+ }
16253
16262
  const createPipeline = transformersModule.pipeline;
16254
16263
  const MAX_ATTEMPTS = 3;
16255
16264
  let lastError;
@@ -16351,6 +16360,7 @@ class LocalEmbeddingProvider {
16351
16360
  }
16352
16361
  var init_embedding_local = __esm(() => {
16353
16362
  init_magic_context();
16363
+ init_data_path();
16354
16364
  init_logger();
16355
16365
  });
16356
16366
 
@@ -17401,11 +17411,11 @@ var init_migrations = __esm(() => {
17401
17411
 
17402
17412
  // src/features/magic-context/storage-db.ts
17403
17413
  import { Database as Database3 } from "bun:sqlite";
17404
- import { mkdirSync } from "fs";
17405
- import { join as join9 } from "path";
17414
+ import { mkdirSync as mkdirSync2 } from "fs";
17415
+ import { join as join10 } from "path";
17406
17416
  function resolveDatabasePath() {
17407
- const dbDir = join9(getOpenCodeStorageDir(), "plugin", "magic-context");
17408
- return { dbDir, dbPath: join9(dbDir, "context.db") };
17417
+ const dbDir = join10(getOpenCodeStorageDir(), "plugin", "magic-context");
17418
+ return { dbDir, dbPath: join10(dbDir, "context.db") };
17409
17419
  }
17410
17420
  function initializeDatabase(db) {
17411
17421
  db.run("PRAGMA journal_mode=WAL");
@@ -17720,7 +17730,7 @@ function openDatabase() {
17720
17730
  }
17721
17731
  return existing;
17722
17732
  }
17723
- mkdirSync(dbDir, { recursive: true });
17733
+ mkdirSync2(dbDir, { recursive: true });
17724
17734
  const db = new Database3(dbPath);
17725
17735
  initializeDatabase(db);
17726
17736
  runMigrations(db);
@@ -18559,7 +18569,7 @@ var init_send_session_notification = __esm(() => {
18559
18569
 
18560
18570
  // src/features/magic-context/compaction-marker.ts
18561
18571
  import { Database as Database4 } from "bun:sqlite";
18562
- import { join as join11 } from "path";
18572
+ import { join as join12 } from "path";
18563
18573
  function randomBase62(length) {
18564
18574
  const chars = [];
18565
18575
  for (let i = 0;i < length; i++) {
@@ -18579,7 +18589,7 @@ function generatePartId(timestampMs, counter = 0n) {
18579
18589
  return generateId("prt", timestampMs, counter);
18580
18590
  }
18581
18591
  function getOpenCodeDbPath3() {
18582
- return join11(getDataDir(), "opencode", "opencode.db");
18592
+ return join12(getDataDir(), "opencode", "opencode.db");
18583
18593
  }
18584
18594
  function getWritableOpenCodeDb() {
18585
18595
  const dbPath = getOpenCodeDbPath3();
@@ -19472,9 +19482,9 @@ var init_compartment_runner_validation = __esm(() => {
19472
19482
  });
19473
19483
 
19474
19484
  // src/hooks/magic-context/compartment-runner-historian.ts
19475
- import { mkdirSync as mkdirSync2, unlinkSync, writeFileSync } from "fs";
19485
+ import { mkdirSync as mkdirSync3, unlinkSync, writeFileSync } from "fs";
19476
19486
  import { tmpdir as tmpdir2 } from "os";
19477
- import { join as join12 } from "path";
19487
+ import { join as join13 } from "path";
19478
19488
  async function runValidatedHistorianPass(args) {
19479
19489
  const firstRun = await runHistorianPrompt({
19480
19490
  ...args,
@@ -19677,10 +19687,10 @@ function cleanupHistorianDump(sessionId, dumpPath) {
19677
19687
  }
19678
19688
  function dumpHistorianResponse(sessionId, label, text) {
19679
19689
  try {
19680
- mkdirSync2(HISTORIAN_RESPONSE_DUMP_DIR, { recursive: true });
19690
+ mkdirSync3(HISTORIAN_RESPONSE_DUMP_DIR, { recursive: true });
19681
19691
  const safeSessionId = sanitizeDumpName(sessionId);
19682
19692
  const safeLabel = sanitizeDumpName(label);
19683
- const dumpPath = join12(HISTORIAN_RESPONSE_DUMP_DIR, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
19693
+ const dumpPath = join13(HISTORIAN_RESPONSE_DUMP_DIR, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
19684
19694
  writeFileSync(dumpPath, text, "utf8");
19685
19695
  sessionLog(sessionId, "compartment agent: historian response dumped", {
19686
19696
  label,
@@ -19704,7 +19714,7 @@ var init_compartment_runner_historian = __esm(() => {
19704
19714
  init_shared();
19705
19715
  init_assistant_message_extractor();
19706
19716
  init_compartment_runner_validation();
19707
- HISTORIAN_RESPONSE_DUMP_DIR = join12(tmpdir2(), "magic-context-historian");
19717
+ HISTORIAN_RESPONSE_DUMP_DIR = join13(tmpdir2(), "magic-context-historian");
19708
19718
  });
19709
19719
 
19710
19720
  // src/hooks/magic-context/compartment-runner-state-xml.ts
@@ -28037,10 +28047,10 @@ var require_stringify = __commonJS((exports, module) => {
28037
28047
  replacer = null;
28038
28048
  indent = EMPTY;
28039
28049
  };
28040
- var join15 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
28050
+ var join16 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
28041
28051
  var join_content = (inside, value, gap) => {
28042
28052
  const comment = process_comments(value, PREFIX_BEFORE, gap + indent, true);
28043
- return join15(comment, inside, gap);
28053
+ return join16(comment, inside, gap);
28044
28054
  };
28045
28055
  var stringify_string = (holder, key, value) => {
28046
28056
  const raw = get_raw_string_literal(holder, key);
@@ -28062,13 +28072,13 @@ var require_stringify = __commonJS((exports, module) => {
28062
28072
  if (i !== 0) {
28063
28073
  inside += COMMA;
28064
28074
  }
28065
- const before = join15(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
28075
+ const before = join16(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
28066
28076
  inside += before || LF + deeper_gap;
28067
28077
  inside += stringify(i, value, deeper_gap) || STR_NULL;
28068
28078
  inside += process_comments(value, AFTER_VALUE(i), deeper_gap);
28069
28079
  after_comma = process_comments(value, AFTER(i), deeper_gap);
28070
28080
  }
28071
- inside += join15(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
28081
+ inside += join16(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
28072
28082
  return BRACKET_OPEN + join_content(inside, value, gap) + BRACKET_CLOSE;
28073
28083
  };
28074
28084
  var object_stringify = (value, gap) => {
@@ -28089,13 +28099,13 @@ var require_stringify = __commonJS((exports, module) => {
28089
28099
  inside += COMMA;
28090
28100
  }
28091
28101
  first = false;
28092
- const before = join15(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
28102
+ const before = join16(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
28093
28103
  inside += before || LF + deeper_gap;
28094
28104
  inside += quote(key) + process_comments(value, AFTER_PROP(key), deeper_gap) + COLON + process_comments(value, AFTER_COLON(key), deeper_gap) + SPACE + sv + process_comments(value, AFTER_VALUE(key), deeper_gap);
28095
28105
  after_comma = process_comments(value, AFTER(key), deeper_gap);
28096
28106
  };
28097
28107
  keys.forEach(iteratee);
28098
- inside += join15(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
28108
+ inside += join16(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
28099
28109
  return CURLY_BRACKET_OPEN + join_content(inside, value, gap) + CURLY_BRACKET_CLOSE;
28100
28110
  };
28101
28111
  function stringify(key, holder, gap) {
@@ -28192,12 +28202,12 @@ var exports_tui_config = {};
28192
28202
  __export(exports_tui_config, {
28193
28203
  ensureTuiPluginEntry: () => ensureTuiPluginEntry
28194
28204
  });
28195
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
28196
- import { dirname as dirname2, join as join15 } from "path";
28205
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
28206
+ import { dirname as dirname2, join as join16 } from "path";
28197
28207
  function resolveTuiConfigPath() {
28198
28208
  const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
28199
- const jsoncPath = join15(configDir, "tui.jsonc");
28200
- const jsonPath = join15(configDir, "tui.json");
28209
+ const jsoncPath = join16(configDir, "tui.jsonc");
28210
+ const jsonPath = join16(configDir, "tui.json");
28201
28211
  if (existsSync7(jsoncPath))
28202
28212
  return jsoncPath;
28203
28213
  if (existsSync7(jsonPath))
@@ -28218,7 +28228,7 @@ function ensureTuiPluginEntry() {
28218
28228
  }
28219
28229
  plugins.push(PLUGIN_ENTRY);
28220
28230
  config2.plugin = plugins;
28221
- mkdirSync4(dirname2(configPath), { recursive: true });
28231
+ mkdirSync5(dirname2(configPath), { recursive: true });
28222
28232
  writeFileSync3(configPath, `${import_comment_json.stringify(config2, null, 2)}
28223
28233
  `);
28224
28234
  log(`[magic-context] added TUI plugin entry to ${configPath}`);
@@ -30194,7 +30204,7 @@ function createCompactionHandler() {
30194
30204
  init_logger();
30195
30205
  import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
30196
30206
  import { homedir as homedir5, platform as platform2 } from "os";
30197
- import { join as join10 } from "path";
30207
+ import { join as join11 } from "path";
30198
30208
  var cachedLimits = null;
30199
30209
  var lastLoadAttempt = 0;
30200
30210
  var RELOAD_INTERVAL_MS = 5 * 60 * 1000;
@@ -30205,34 +30215,66 @@ function getModelsJsonPath() {
30205
30215
  if (xdgCache) {
30206
30216
  cacheBase = xdgCache;
30207
30217
  } else if (os3 === "win32") {
30208
- cacheBase = process.env.LOCALAPPDATA ?? join10(homedir5(), "AppData", "Local");
30218
+ cacheBase = process.env.LOCALAPPDATA ?? join11(homedir5(), "AppData", "Local");
30209
30219
  } else {
30210
- cacheBase = join10(homedir5(), ".cache");
30211
- }
30212
- return join10(cacheBase, "opencode", "models.json");
30220
+ cacheBase = join11(homedir5(), ".cache");
30221
+ }
30222
+ return join11(cacheBase, "opencode", "models.json");
30223
+ }
30224
+ function getOpencodeConfigPath() {
30225
+ const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
30226
+ const configDir = envDir ? envDir : platform2() === "win32" ? join11(homedir5(), ".config", "opencode") : join11(process.env.XDG_CONFIG_HOME || join11(homedir5(), ".config"), "opencode");
30227
+ const jsonc = join11(configDir, "opencode.jsonc");
30228
+ if (existsSync5(jsonc))
30229
+ return jsonc;
30230
+ const json2 = join11(configDir, "opencode.json");
30231
+ if (existsSync5(json2))
30232
+ return json2;
30233
+ return null;
30213
30234
  }
30214
30235
  function loadModelsDevLimits() {
30215
30236
  const limits = new Map;
30216
- const filePath = getModelsJsonPath();
30237
+ const modelsJsonPath = getModelsJsonPath();
30217
30238
  try {
30218
- if (!existsSync5(filePath)) {
30219
- return limits;
30220
- }
30221
- const raw = readFileSync4(filePath, "utf-8");
30222
- const data = JSON.parse(raw);
30223
- for (const [providerId, provider2] of Object.entries(data)) {
30224
- if (!provider2?.models || typeof provider2.models !== "object")
30225
- continue;
30226
- for (const [modelId, model] of Object.entries(provider2.models)) {
30227
- const context = model?.limit?.context;
30228
- if (typeof context === "number" && context > 0) {
30229
- limits.set(`${providerId}/${modelId}`, context);
30239
+ if (existsSync5(modelsJsonPath)) {
30240
+ const raw = readFileSync4(modelsJsonPath, "utf-8");
30241
+ const data = JSON.parse(raw);
30242
+ for (const [providerId, provider2] of Object.entries(data)) {
30243
+ if (!provider2?.models || typeof provider2.models !== "object")
30244
+ continue;
30245
+ for (const [modelId, model] of Object.entries(provider2.models)) {
30246
+ const context = model?.limit?.context;
30247
+ if (typeof context === "number" && context > 0) {
30248
+ limits.set(`${providerId}/${modelId}`, context);
30249
+ }
30230
30250
  }
30231
30251
  }
30232
30252
  }
30233
30253
  } catch (error48) {
30234
30254
  sessionLog("global", "models-dev-cache: failed to read models.json:", error48 instanceof Error ? error48.message : String(error48));
30235
30255
  }
30256
+ try {
30257
+ const configPath = getOpencodeConfigPath();
30258
+ if (configPath && existsSync5(configPath)) {
30259
+ let raw = readFileSync4(configPath, "utf-8");
30260
+ raw = raw.replace(/\/\/.*$/gm, "");
30261
+ const config2 = JSON.parse(raw);
30262
+ if (config2.provider && typeof config2.provider === "object") {
30263
+ for (const [providerId, provider2] of Object.entries(config2.provider)) {
30264
+ if (!provider2?.models || typeof provider2.models !== "object")
30265
+ continue;
30266
+ for (const [modelId, model] of Object.entries(provider2.models)) {
30267
+ const context = model?.limit?.context;
30268
+ if (typeof context === "number" && context > 0) {
30269
+ limits.set(`${providerId}/${modelId}`, context);
30270
+ }
30271
+ }
30272
+ }
30273
+ }
30274
+ }
30275
+ } catch (error48) {
30276
+ sessionLog("global", "models-dev-cache: failed to read opencode config for custom models:", error48 instanceof Error ? error48.message : String(error48));
30277
+ }
30236
30278
  return limits;
30237
30279
  }
30238
30280
  function getModelsDevContextLimit(providerID, modelID) {
@@ -33801,7 +33843,7 @@ init_send_session_notification();
33801
33843
 
33802
33844
  // src/hooks/magic-context/system-prompt-hash.ts
33803
33845
  import { existsSync as existsSync6, readFileSync as readFileSync5, realpathSync } from "fs";
33804
- import { join as join13, resolve as resolve2, sep } from "path";
33846
+ import { join as join14, resolve as resolve2, sep } from "path";
33805
33847
 
33806
33848
  // src/agents/magic-context-prompt.ts
33807
33849
  function getToolHistoryGuidance(dropToolStructure) {
@@ -34021,7 +34063,7 @@ var DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
34021
34063
  function readProjectDocs(directory) {
34022
34064
  const sections = [];
34023
34065
  for (const filename of DOC_FILES) {
34024
- const filePath = join13(directory, filename);
34066
+ const filePath = join14(directory, filename);
34025
34067
  try {
34026
34068
  if (existsSync6(filePath)) {
34027
34069
  const content = readFileSync5(filePath, "utf-8").trim();
@@ -36035,19 +36077,19 @@ init_model_requirements();
36035
36077
 
36036
36078
  // src/shared/rpc-server.ts
36037
36079
  init_logger();
36038
- import { mkdirSync as mkdirSync3, renameSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
36080
+ import { mkdirSync as mkdirSync4, renameSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
36039
36081
  import { createServer } from "http";
36040
36082
  import { dirname } from "path";
36041
36083
 
36042
36084
  // src/shared/rpc-utils.ts
36043
36085
  import { createHash } from "crypto";
36044
- import { join as join14 } from "path";
36086
+ import { join as join15 } from "path";
36045
36087
  function projectHash(directory) {
36046
36088
  const normalized = directory.replace(/\/+$/, "");
36047
36089
  return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
36048
36090
  }
36049
36091
  function rpcPortFilePath(storageDir, directory) {
36050
- return join14(storageDir, "rpc", projectHash(directory), "port");
36092
+ return join15(storageDir, "rpc", projectHash(directory), "port");
36051
36093
  }
36052
36094
 
36053
36095
  // src/shared/rpc-server.ts
@@ -36079,7 +36121,7 @@ class MagicContextRpcServer {
36079
36121
  this.server = server;
36080
36122
  try {
36081
36123
  const dir = dirname(this.portFilePath);
36082
- mkdirSync3(dir, { recursive: true });
36124
+ mkdirSync4(dir, { recursive: true });
36083
36125
  const tmpPath = `${this.portFilePath}.tmp`;
36084
36126
  writeFileSync2(tmpPath, String(this.port), "utf-8");
36085
36127
  renameSync(tmpPath, this.portFilePath);
@@ -1 +1 @@
1
- {"version":3,"file":"models-dev-cache.d.ts","sourceRoot":"","sources":["../../src/shared/models-dev-cache.ts"],"names":[],"mappings":"AAuEA;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAShG;AAED,8CAA8C;AAC9C,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C"}
1
+ {"version":3,"file":"models-dev-cache.d.ts","sourceRoot":"","sources":["../../src/shared/models-dev-cache.ts"],"names":[],"mappings":"AA0HA;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAShG;AAED,8CAA8C;AAC9C,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-magic-context",
3
- "version": "0.8.8",
3
+ "version": "0.8.10",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Magic Context — cross-session memory and context management",
6
6
  "main": "dist/index.js",
@@ -34,27 +34,42 @@ function getModelsJsonPath(): string {
34
34
  return join(cacheBase, "opencode", "models.json");
35
35
  }
36
36
 
37
+ function getOpencodeConfigPath(): string | null {
38
+ const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
39
+ const configDir = envDir
40
+ ? envDir
41
+ : platform() === "win32"
42
+ ? join(homedir(), ".config", "opencode")
43
+ : join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "opencode");
44
+
45
+ // Check jsonc first, then json (matches OpenCode's own lookup order)
46
+ const jsonc = join(configDir, "opencode.jsonc");
47
+ if (existsSync(jsonc)) return jsonc;
48
+ const json = join(configDir, "opencode.json");
49
+ if (existsSync(json)) return json;
50
+ return null;
51
+ }
52
+
37
53
  function loadModelsDevLimits(): Map<string, number> {
38
54
  const limits = new Map<string, number>();
39
- const filePath = getModelsJsonPath();
40
55
 
56
+ // 1. Load from OpenCode's models.dev cache (base layer — all known public models)
57
+ const modelsJsonPath = getModelsJsonPath();
41
58
  try {
42
- if (!existsSync(filePath)) {
43
- return limits;
44
- }
59
+ if (existsSync(modelsJsonPath)) {
60
+ const raw = readFileSync(modelsJsonPath, "utf-8");
61
+ const data = JSON.parse(raw) as Record<
62
+ string,
63
+ { models?: Record<string, { limit?: { context?: number } }> }
64
+ >;
45
65
 
46
- const raw = readFileSync(filePath, "utf-8");
47
- const data = JSON.parse(raw) as Record<
48
- string,
49
- { models?: Record<string, { limit?: { context?: number } }> }
50
- >;
51
-
52
- for (const [providerId, provider] of Object.entries(data)) {
53
- if (!provider?.models || typeof provider.models !== "object") continue;
54
- for (const [modelId, model] of Object.entries(provider.models)) {
55
- const context = model?.limit?.context;
56
- if (typeof context === "number" && context > 0) {
57
- limits.set(`${providerId}/${modelId}`, context);
66
+ for (const [providerId, provider] of Object.entries(data)) {
67
+ if (!provider?.models || typeof provider.models !== "object") continue;
68
+ for (const [modelId, model] of Object.entries(provider.models)) {
69
+ const context = model?.limit?.context;
70
+ if (typeof context === "number" && context > 0) {
71
+ limits.set(`${providerId}/${modelId}`, context);
72
+ }
58
73
  }
59
74
  }
60
75
  }
@@ -66,6 +81,42 @@ function loadModelsDevLimits(): Map<string, number> {
66
81
  );
67
82
  }
68
83
 
84
+ // 2. Overlay custom provider models from OpenCode config (higher priority).
85
+ // Users define custom/proxy models via provider.<id>.models.<name>.limit.context
86
+ // in opencode.json(c). These override models.dev entries for the same key.
87
+ try {
88
+ const configPath = getOpencodeConfigPath();
89
+ if (configPath && existsSync(configPath)) {
90
+ let raw = readFileSync(configPath, "utf-8");
91
+ // Strip JSONC comments (single-line only — sufficient for OpenCode configs)
92
+ raw = raw.replace(/\/\/.*$/gm, "");
93
+ const config = JSON.parse(raw) as {
94
+ provider?: Record<
95
+ string,
96
+ { models?: Record<string, { limit?: { context?: number } }> }
97
+ >;
98
+ };
99
+
100
+ if (config.provider && typeof config.provider === "object") {
101
+ for (const [providerId, provider] of Object.entries(config.provider)) {
102
+ if (!provider?.models || typeof provider.models !== "object") continue;
103
+ for (const [modelId, model] of Object.entries(provider.models)) {
104
+ const context = model?.limit?.context;
105
+ if (typeof context === "number" && context > 0) {
106
+ limits.set(`${providerId}/${modelId}`, context);
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ } catch (error) {
113
+ sessionLog(
114
+ "global",
115
+ "models-dev-cache: failed to read opencode config for custom models:",
116
+ error instanceof Error ? error.message : String(error),
117
+ );
118
+ }
119
+
69
120
  return limits;
70
121
  }
71
122