@cortexkit/opencode-magic-context 0.27.1 → 0.27.2
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 +1 -0
- package/dist/config/migrate-config-location.d.ts +8 -0
- package/dist/config/migrate-config-location.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/index.js +25 -3
- package/dist/shared/models-dev-cache.d.ts +23 -0
- package/dist/shared/models-dev-cache.d.ts.map +1 -1
- package/dist/shared/tui-config.d.ts.map +1 -1
- package/package.json +4 -1
- package/src/shared/models-dev-cache.test.ts +82 -0
- package/src/shared/models-dev-cache.ts +35 -0
- package/src/shared/tui-config.test.ts +43 -0
- package/src/shared/tui-config.ts +8 -1
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
<a href="#-recall">Recall</a> ·
|
|
29
29
|
<a href="https://docs.cortexkit.io/magic-context">Docs</a> ·
|
|
30
30
|
<a href="./CONFIGURATION.md">Configuration</a> ·
|
|
31
|
+
<a href="https://github.com/cortexkit/magic-context/releases?q=dashboard&expanded=true">Dashboard</a> ·
|
|
31
32
|
<a href="https://discord.gg/DSa65w8wuf">💬 Discord</a>
|
|
32
33
|
</p>
|
|
33
34
|
|
|
@@ -58,6 +58,14 @@ export declare function resolveCortexKitProjectConfigPath(directory: string): st
|
|
|
58
58
|
* `.jsonc` and a `.json` candidate; whichever exists migrates, target is always
|
|
59
59
|
* `.jsonc`. The bare-root project source (`<root>/magic-context.*`) is unique to
|
|
60
60
|
* Magic Context (AFT never had it) — omitting it would orphan repo-root configs.
|
|
61
|
+
*
|
|
62
|
+
* Project sources are filtered against the user-scope path set: when a session's
|
|
63
|
+
* project directory IS the user config home (e.g. opencode opened in
|
|
64
|
+
* `~/.config/cortexkit`), the bare-root project source `<root>/magic-context.jsonc`
|
|
65
|
+
* resolves to the USER config path. Without this guard the project migration
|
|
66
|
+
* would "migrate" the user's own config into `<root>/.cortexkit/` and rename the
|
|
67
|
+
* original aside, leaving the user on schema defaults (the config-eats-itself
|
|
68
|
+
* bug). A project migration must never touch a user-scope file.
|
|
61
69
|
*/
|
|
62
70
|
export declare function resolveLegacyConfigSources(directory: string): {
|
|
63
71
|
user: LegacyConfigSource[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate-config-location.d.ts","sourceRoot":"","sources":["../../src/config/migrate-config-location.ts"],"names":[],"mappings":"AAeA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,WAAW,kBAAkB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IAClC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,0BAA0B;IACvC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAC7C,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AAoBD,iFAAiF;AACjF,wBAAgB,2BAA2B,IAAI,MAAM,CAEpD;AAED,+EAA+E;AAC/E,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAExE;AAED,2DAA2D;AAC3D,wBAAgB,8BAA8B,IAAI,MAAM,CAEvD;AAED,2DAA2D;AAC3D,wBAAgB,iCAAiC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3E;
|
|
1
|
+
{"version":3,"file":"migrate-config-location.d.ts","sourceRoot":"","sources":["../../src/config/migrate-config-location.ts"],"names":[],"mappings":"AAeA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,WAAW,kBAAkB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IAClC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,0BAA0B;IACvC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAC7C,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AAoBD,iFAAiF;AACjF,wBAAgB,2BAA2B,IAAI,MAAM,CAEpD;AAED,+EAA+E;AAC/E,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAExE;AAED,2DAA2D;AAC3D,wBAAgB,8BAA8B,IAAI,MAAM,CAEvD;AAED,2DAA2D;AAC3D,wBAAgB,iCAAiC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3E;AA4BD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG;IAC3D,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC3B,OAAO,EAAE,kBAAkB,EAAE,CAAC;CACjC,CAsBA;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,IAAI,CAAC;AAE9C;;;;;;;;;GASG;AACH,wBAAgB,oCAAoC,CAChD,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,GACvB;IAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAAC,OAAO,EAAE,kBAAkB,EAAE,CAAA;CAAE,CA0B/D;AAmSD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,0BAA0B,GAAG,yBAAyB,CA0F7F;AAED;;;;;GAKG;AACH,wBAAgB,kCAAkC,CAC9C,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,qBAAqB,GAC/B,MAAM,EAAE,CA6BV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-handler.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/event-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAyBvF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAKlE,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"event-handler.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/event-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAyBvF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAKlE,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,oCAAoC,CAAC;AAwBpF,OAAO,EAAE,KAAK,kBAAkB,EAAsB,MAAM,6BAA6B,CAAC;AAM1F,KAAK,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEtD,UAAU,iBAAiB;IACvB,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAMD,MAAM,WAAW,gBAAgB;IAC7B,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAChD,iBAAiB,EAAE,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC;IAC9D,yBAAyB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,MAAM,EAAE;QACJ,cAAc,EAAE,MAAM,CAAC;QACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,4BAA4B,CAAC,EAAE,MAAM,GAAG;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;SAAE,CAAC;QACxF,wBAAwB,CAAC,EAAE;YAAE,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;SAAE,CAAC;QACxF,SAAS,EAAE,cAAc,CAAC;QAC1B,sBAAsB,CAAC,EAAE;YAAE,OAAO,EAAE,OAAO,CAAC;YAAC,YAAY,EAAE,MAAM,CAAA;SAAE,CAAC;KACvE,CAAC;IACF,MAAM,EAAE,MAAM,CAAC;IAIf,EAAE,EAAE,OAAO,qBAAqB,EAAE,QAAQ,CAAC;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2FAA2F;IAC3F,sBAAsB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,oBAAoB,EAAE,aAAa,CAAC,CAAC;IACjF,qBAAqB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,kBAAkB,CAAC;IAClE;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACvC;AAqID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,IACvC,OAAO;IAAE,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,KAAG,OAAO,CAAC,IAAI,CAAC,CAmgBzF"}
|
package/dist/index.js
CHANGED
|
@@ -176602,6 +176602,14 @@ async function refreshModelLimitsFromApi(client, options) {
|
|
|
176602
176602
|
}
|
|
176603
176603
|
}
|
|
176604
176604
|
}
|
|
176605
|
+
async function refreshModelLimitsAfterAuthOnce(client) {
|
|
176606
|
+
if (authRewarmDone)
|
|
176607
|
+
return;
|
|
176608
|
+
authRewarmDone = true;
|
|
176609
|
+
const ok = await refreshModelLimitsOnce(client);
|
|
176610
|
+
if (!ok)
|
|
176611
|
+
authRewarmDone = false;
|
|
176612
|
+
}
|
|
176605
176613
|
async function refreshModelLimitsOnce(client) {
|
|
176606
176614
|
try {
|
|
176607
176615
|
const result = await client.config.providers();
|
|
@@ -176655,7 +176663,7 @@ function lookupLimitWithTagFallback(cache, providerID, modelID) {
|
|
|
176655
176663
|
}
|
|
176656
176664
|
return;
|
|
176657
176665
|
}
|
|
176658
|
-
var MIN_SANE_LIMIT = 20000, MAX_SANE_LIMIT = 3000000, apiCache = null, apiLoadedAt = 0, persistSeedLoaded = false;
|
|
176666
|
+
var MIN_SANE_LIMIT = 20000, MAX_SANE_LIMIT = 3000000, apiCache = null, apiLoadedAt = 0, persistSeedLoaded = false, authRewarmDone = false;
|
|
176659
176667
|
var init_models_dev_cache = __esm(() => {
|
|
176660
176668
|
init_data_path();
|
|
176661
176669
|
init_logger();
|
|
@@ -186475,7 +186483,7 @@ function resolveTuiConfigPath() {
|
|
|
186475
186483
|
return jsoncPath;
|
|
186476
186484
|
if (existsSync19(jsonPath))
|
|
186477
186485
|
return jsonPath;
|
|
186478
|
-
return
|
|
186486
|
+
return jsoncPath;
|
|
186479
186487
|
}
|
|
186480
186488
|
function ensureTuiPluginEntry() {
|
|
186481
186489
|
try {
|
|
@@ -186811,7 +186819,18 @@ function legacySourcesForBase(basePath, label) {
|
|
|
186811
186819
|
{ path: `${basePath}.json`, label: `${label} magic-context.json` }
|
|
186812
186820
|
];
|
|
186813
186821
|
}
|
|
186822
|
+
function userScopeConfigPaths() {
|
|
186823
|
+
return new Set([
|
|
186824
|
+
`${cortexKitUserConfigBasePath()}.jsonc`,
|
|
186825
|
+
`${cortexKitUserConfigBasePath()}.json`,
|
|
186826
|
+
join2(configHome(), "opencode", `${CONFIG_FILE_BASENAME}.jsonc`),
|
|
186827
|
+
join2(configHome(), "opencode", `${CONFIG_FILE_BASENAME}.json`),
|
|
186828
|
+
join2(homeDir(), ".pi", "agent", `${CONFIG_FILE_BASENAME}.jsonc`),
|
|
186829
|
+
join2(homeDir(), ".pi", "agent", `${CONFIG_FILE_BASENAME}.json`)
|
|
186830
|
+
]);
|
|
186831
|
+
}
|
|
186814
186832
|
function resolveLegacyConfigSources(directory) {
|
|
186833
|
+
const userPaths = userScopeConfigPaths();
|
|
186815
186834
|
return {
|
|
186816
186835
|
user: [
|
|
186817
186836
|
...legacySourcesForBase(join2(configHome(), "opencode", CONFIG_FILE_BASENAME), "OpenCode user"),
|
|
@@ -186821,7 +186840,7 @@ function resolveLegacyConfigSources(directory) {
|
|
|
186821
186840
|
...legacySourcesForBase(join2(directory, CONFIG_FILE_BASENAME), "project root"),
|
|
186822
186841
|
...legacySourcesForBase(join2(directory, ".opencode", CONFIG_FILE_BASENAME), "OpenCode project"),
|
|
186823
186842
|
...legacySourcesForBase(join2(directory, ".pi", CONFIG_FILE_BASENAME), "Pi project")
|
|
186824
|
-
]
|
|
186843
|
+
].filter((source) => !userPaths.has(source.path))
|
|
186825
186844
|
};
|
|
186826
186845
|
}
|
|
186827
186846
|
function resolveLegacyConfigSourcesForHarness(directory, harness) {
|
|
@@ -202317,6 +202336,9 @@ function createEventHandler2(deps) {
|
|
|
202317
202336
|
}
|
|
202318
202337
|
if (hasUsageTokens) {
|
|
202319
202338
|
const totalInputTokens = (info.tokens?.input ?? 0) + (info.tokens?.cache?.read ?? 0) + (info.tokens?.cache?.write ?? 0);
|
|
202339
|
+
if (deps.client) {
|
|
202340
|
+
await refreshModelLimitsAfterAuthOnce(deps.client);
|
|
202341
|
+
}
|
|
202320
202342
|
let contextLimit = resolveContextLimit(info.providerID, info.modelID, {
|
|
202321
202343
|
db: deps.db,
|
|
202322
202344
|
sessionID: info.sessionID
|
|
@@ -59,6 +59,29 @@ export declare function refreshModelLimitsFromApi(client: OpencodeClientLike, op
|
|
|
59
59
|
retries?: number;
|
|
60
60
|
retryDelayMs?: number;
|
|
61
61
|
}): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Re-warm the limit cache ONCE per process, after auth is provably live.
|
|
64
|
+
*
|
|
65
|
+
* The startup warm (index.ts) can run before the user's provider auth is
|
|
66
|
+
* loaded. When it does, an auth-conditional limit patch hasn't applied yet, so
|
|
67
|
+
* `config.providers()` returns the RAW catalog limit (e.g. OpenAI gpt-5.5 OAuth
|
|
68
|
+
* is downshifted to a 272k input cap by OpenCode's Codex auth plugin only when
|
|
69
|
+
* `ctx.auth.type === "oauth"`; before auth loads it reports the raw 922k). That
|
|
70
|
+
* too-high value gets cached AND persisted as last-known-good, survives
|
|
71
|
+
* restarts, and the existing recovery only re-resolves a too-LOW limit
|
|
72
|
+
* (overflow / `percentage > 100`), so a too-HIGH one never self-corrects: the
|
|
73
|
+
* sidebar shows huge headroom while the backend rejects at the real cap (#179).
|
|
74
|
+
*
|
|
75
|
+
* The first `message.updated` carrying usage tokens proves a request succeeded,
|
|
76
|
+
* so auth + providers are fully resolved. Re-warming there overwrites any stale
|
|
77
|
+
* pre-auth limit with the live auth-adjusted one. Idempotent and cheap: a single
|
|
78
|
+
* `config.providers()` round-trip, then a no-op for the rest of the process. The
|
|
79
|
+
* latch is set before the await so concurrent `message.updated` events don't
|
|
80
|
+
* stack duplicate warms; a failed warm resets it so a later message retries.
|
|
81
|
+
*/
|
|
82
|
+
export declare function refreshModelLimitsAfterAuthOnce(client: OpencodeClientLike): Promise<void>;
|
|
83
|
+
/** Test-only: reset the after-auth re-warm latch between cases. */
|
|
84
|
+
export declare function resetAuthRewarmLatchForTest(): void;
|
|
62
85
|
/**
|
|
63
86
|
* Resolve a model's prompt limit from OpenCode's SDK (`config.providers()`),
|
|
64
87
|
* the single source of truth: it already merges models.dev + compiled-in
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models-dev-cache.d.ts","sourceRoot":"","sources":["../../src/shared/models-dev-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAQH,UAAU,kBAAkB;IACxB,MAAM,EAAE;QACJ,SAAS,EAAE,MAAM,OAAO,CAAC;YAAE,IAAI,CAAC,EAAE;gBAAE,SAAS,CAAC,EAAE,OAAO,CAAA;aAAE,CAAA;SAAE,CAAC,CAAC;KAChE,CAAC;CACL;AASD,eAAO,MAAM,cAAc,QAAS,CAAC;AACrC,eAAO,MAAM,cAAc,UAAY,CAAC;AAExC;;kFAEkF;AAClF,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,IAAI,MAAM,CAEtE;AA+HD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,yBAAyB,CAC3C,MAAM,EAAE,kBAAkB,EAC1B,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GACtD,OAAO,CAAC,IAAI,CAAC,CAUf;AA+DD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI1F;AA6BD,gFAAgF;AAChF,wBAAgB,mBAAmB,IAAI,IAAI,CAI1C;AAED,oDAAoD;AACpD,wBAAgB,sBAAsB,IAAI;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACpB,CAMA"}
|
|
1
|
+
{"version":3,"file":"models-dev-cache.d.ts","sourceRoot":"","sources":["../../src/shared/models-dev-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAQH,UAAU,kBAAkB;IACxB,MAAM,EAAE;QACJ,SAAS,EAAE,MAAM,OAAO,CAAC;YAAE,IAAI,CAAC,EAAE;gBAAE,SAAS,CAAC,EAAE,OAAO,CAAA;aAAE,CAAA;SAAE,CAAC,CAAC;KAChE,CAAC;CACL;AASD,eAAO,MAAM,cAAc,QAAS,CAAC;AACrC,eAAO,MAAM,cAAc,UAAY,CAAC;AAExC;;kFAEkF;AAClF,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,IAAI,MAAM,CAEtE;AA+HD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,yBAAyB,CAC3C,MAAM,EAAE,kBAAkB,EAC1B,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GACtD,OAAO,CAAC,IAAI,CAAC,CAUf;AAKD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,+BAA+B,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAK/F;AAED,mEAAmE;AACnE,wBAAgB,2BAA2B,IAAI,IAAI,CAElD;AA+DD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI1F;AA6BD,gFAAgF;AAChF,wBAAgB,mBAAmB,IAAI,IAAI,CAI1C;AAED,oDAAoD;AACpD,wBAAgB,sBAAsB,IAAI;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACpB,CAMA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tui-config.d.ts","sourceRoot":"","sources":["../../src/shared/tui-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"tui-config.d.ts","sourceRoot":"","sources":["../../src/shared/tui-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAyEH;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAqD9C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/opencode-magic-context",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin for Magic Context — cross-session memory and context management",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -44,9 +44,12 @@
|
|
|
44
44
|
"@jitl/quickjs-singlefile-cjs-release-asyncify": "0.32.0",
|
|
45
45
|
"@opencode-ai/plugin": "^1.15.13",
|
|
46
46
|
"@opencode-ai/sdk": "^1.15.13",
|
|
47
|
+
"@opentui/core": "^0.4.2",
|
|
48
|
+
"@opentui/solid": "^0.4.2",
|
|
47
49
|
"ai-tokenizer": "^1.0.6",
|
|
48
50
|
"comment-json": "^4.2.5",
|
|
49
51
|
"quickjs-emscripten": "^0.32.0",
|
|
52
|
+
"solid-js": "1.9.12",
|
|
50
53
|
"zod": "^4.1.8"
|
|
51
54
|
},
|
|
52
55
|
"devDependencies": {
|
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
clearModelsDevCache,
|
|
7
7
|
getModelsDevCacheState,
|
|
8
8
|
getSdkContextLimit,
|
|
9
|
+
refreshModelLimitsAfterAuthOnce,
|
|
9
10
|
refreshModelLimitsFromApi,
|
|
11
|
+
resetAuthRewarmLatchForTest,
|
|
10
12
|
} from "./models-dev-cache";
|
|
11
13
|
|
|
12
14
|
/**
|
|
@@ -198,6 +200,86 @@ describe("models-dev-cache (SDK-only)", () => {
|
|
|
198
200
|
});
|
|
199
201
|
});
|
|
200
202
|
|
|
203
|
+
describe("after-auth re-warm (once per process)", () => {
|
|
204
|
+
// The startup warm can run before provider auth is loaded, caching a raw
|
|
205
|
+
// pre-downshift limit (gpt-5.5 922k) that then survives restarts and is
|
|
206
|
+
// never corrected by the too-low-only recovery. The first usage event
|
|
207
|
+
// proves auth is live; one re-warm there overwrites the stale value.
|
|
208
|
+
function makeCountingClient(input: number) {
|
|
209
|
+
let calls = 0;
|
|
210
|
+
return {
|
|
211
|
+
client: {
|
|
212
|
+
config: {
|
|
213
|
+
providers: async () => {
|
|
214
|
+
calls++;
|
|
215
|
+
return {
|
|
216
|
+
data: {
|
|
217
|
+
providers: [
|
|
218
|
+
{
|
|
219
|
+
id: "openai",
|
|
220
|
+
models: { "gpt-5.5": { limit: { input } } },
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
calls: () => calls,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
test("re-warm overwrites a stale pre-auth limit and runs only once per process", async () => {
|
|
233
|
+
resetAuthRewarmLatchForTest();
|
|
234
|
+
// Pre-auth startup warm cached the raw 922k.
|
|
235
|
+
await refreshModelLimitsFromApi(
|
|
236
|
+
makeClient([{ id: "openai", models: { "gpt-5.5": { limit: { input: 922000 } } } }]),
|
|
237
|
+
);
|
|
238
|
+
expect(getSdkContextLimit("openai", "gpt-5.5")).toBe(922000);
|
|
239
|
+
|
|
240
|
+
// First usage: auth is live, providers() now reports the 272k cap.
|
|
241
|
+
const { client, calls } = makeCountingClient(272000);
|
|
242
|
+
await refreshModelLimitsAfterAuthOnce(client);
|
|
243
|
+
expect(getSdkContextLimit("openai", "gpt-5.5")).toBe(272000);
|
|
244
|
+
expect(calls()).toBe(1);
|
|
245
|
+
|
|
246
|
+
// Subsequent usage events are a no-op (latch held).
|
|
247
|
+
await refreshModelLimitsAfterAuthOnce(client);
|
|
248
|
+
await refreshModelLimitsAfterAuthOnce(client);
|
|
249
|
+
expect(calls()).toBe(1);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("a failed re-warm resets the latch so a later usage event retries", async () => {
|
|
253
|
+
resetAuthRewarmLatchForTest();
|
|
254
|
+
let calls = 0;
|
|
255
|
+
const flaky = {
|
|
256
|
+
config: {
|
|
257
|
+
providers: async () => {
|
|
258
|
+
calls++;
|
|
259
|
+
// First attempt: empty payload (auth still settling) → no warm.
|
|
260
|
+
if (calls === 1) return { data: { providers: [] } };
|
|
261
|
+
return {
|
|
262
|
+
data: {
|
|
263
|
+
providers: [
|
|
264
|
+
{
|
|
265
|
+
id: "openai",
|
|
266
|
+
models: { "gpt-5.5": { limit: { input: 272000 } } },
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
await refreshModelLimitsAfterAuthOnce(flaky);
|
|
275
|
+
expect(getSdkContextLimit("openai", "gpt-5.5")).toBeUndefined();
|
|
276
|
+
// Latch was reset on failure, so the next event retries and succeeds.
|
|
277
|
+
await refreshModelLimitsAfterAuthOnce(flaky);
|
|
278
|
+
expect(getSdkContextLimit("openai", "gpt-5.5")).toBe(272000);
|
|
279
|
+
expect(calls).toBe(2);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
201
283
|
describe("startup retry", () => {
|
|
202
284
|
test("retries when the provider payload is empty, then succeeds", async () => {
|
|
203
285
|
let calls = 0;
|
|
@@ -210,6 +210,41 @@ export async function refreshModelLimitsFromApi(
|
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
// Once-per-process latch for the after-auth re-warm below.
|
|
214
|
+
let authRewarmDone = false;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Re-warm the limit cache ONCE per process, after auth is provably live.
|
|
218
|
+
*
|
|
219
|
+
* The startup warm (index.ts) can run before the user's provider auth is
|
|
220
|
+
* loaded. When it does, an auth-conditional limit patch hasn't applied yet, so
|
|
221
|
+
* `config.providers()` returns the RAW catalog limit (e.g. OpenAI gpt-5.5 OAuth
|
|
222
|
+
* is downshifted to a 272k input cap by OpenCode's Codex auth plugin only when
|
|
223
|
+
* `ctx.auth.type === "oauth"`; before auth loads it reports the raw 922k). That
|
|
224
|
+
* too-high value gets cached AND persisted as last-known-good, survives
|
|
225
|
+
* restarts, and the existing recovery only re-resolves a too-LOW limit
|
|
226
|
+
* (overflow / `percentage > 100`), so a too-HIGH one never self-corrects: the
|
|
227
|
+
* sidebar shows huge headroom while the backend rejects at the real cap (#179).
|
|
228
|
+
*
|
|
229
|
+
* The first `message.updated` carrying usage tokens proves a request succeeded,
|
|
230
|
+
* so auth + providers are fully resolved. Re-warming there overwrites any stale
|
|
231
|
+
* pre-auth limit with the live auth-adjusted one. Idempotent and cheap: a single
|
|
232
|
+
* `config.providers()` round-trip, then a no-op for the rest of the process. The
|
|
233
|
+
* latch is set before the await so concurrent `message.updated` events don't
|
|
234
|
+
* stack duplicate warms; a failed warm resets it so a later message retries.
|
|
235
|
+
*/
|
|
236
|
+
export async function refreshModelLimitsAfterAuthOnce(client: OpencodeClientLike): Promise<void> {
|
|
237
|
+
if (authRewarmDone) return;
|
|
238
|
+
authRewarmDone = true;
|
|
239
|
+
const ok = await refreshModelLimitsOnce(client);
|
|
240
|
+
if (!ok) authRewarmDone = false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Test-only: reset the after-auth re-warm latch between cases. */
|
|
244
|
+
export function resetAuthRewarmLatchForTest(): void {
|
|
245
|
+
authRewarmDone = false;
|
|
246
|
+
}
|
|
247
|
+
|
|
213
248
|
/** Single SDK fetch + cache rebuild. Returns true when providers were loaded. */
|
|
214
249
|
async function refreshModelLimitsOnce(client: OpencodeClientLike): Promise<boolean> {
|
|
215
250
|
try {
|
|
@@ -60,4 +60,47 @@ describe("ensureTuiPluginEntry", () => {
|
|
|
60
60
|
expect(entry[0]).toBe("@cortexkit/opencode-magic-context@latest");
|
|
61
61
|
expect(entry[1]).toEqual({ enabled: true });
|
|
62
62
|
});
|
|
63
|
+
|
|
64
|
+
it("creates tui.jsonc (not tui.json) on a fresh install", async () => {
|
|
65
|
+
const root = mkdtempSync(join(tmpdir(), "mc-tui-fresh-"));
|
|
66
|
+
roots.push(root);
|
|
67
|
+
process.env.OPENCODE_CONFIG_DIR = root;
|
|
68
|
+
|
|
69
|
+
const { ensureTuiPluginEntry } = await import("./tui-config");
|
|
70
|
+
expect(ensureTuiPluginEntry()).toBe(true);
|
|
71
|
+
|
|
72
|
+
// The new file must be tui.jsonc so a tui.json stub never ends up
|
|
73
|
+
// sitting next to a tui.jsonc the user writes later (#176).
|
|
74
|
+
expect(existsSync(join(root, "tui.jsonc"))).toBe(true);
|
|
75
|
+
expect(existsSync(join(root, "tui.json"))).toBe(false);
|
|
76
|
+
const parsed = JSON.parse(readFileSync(join(root, "tui.jsonc"), "utf-8")) as {
|
|
77
|
+
plugin: unknown[];
|
|
78
|
+
};
|
|
79
|
+
expect(parsed.plugin).toContain("@cortexkit/opencode-magic-context@latest");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("writes into the existing tui.jsonc when both files exist", async () => {
|
|
83
|
+
const root = mkdtempSync(join(tmpdir(), "mc-tui-both-"));
|
|
84
|
+
roots.push(root);
|
|
85
|
+
process.env.OPENCODE_CONFIG_DIR = root;
|
|
86
|
+
// A real user config in tui.jsonc plus a leftover empty tui.json.
|
|
87
|
+
writeFileSync(
|
|
88
|
+
join(root, "tui.jsonc"),
|
|
89
|
+
`${JSON.stringify({ keybinds: { x: "y" } }, null, 2)}\n`,
|
|
90
|
+
);
|
|
91
|
+
writeFileSync(join(root, "tui.json"), "{}\n");
|
|
92
|
+
|
|
93
|
+
const { ensureTuiPluginEntry } = await import("./tui-config");
|
|
94
|
+
expect(ensureTuiPluginEntry()).toBe(true);
|
|
95
|
+
|
|
96
|
+
// The plugin entry must land in tui.jsonc (higher precedence), and the
|
|
97
|
+
// user's keybinds must survive; tui.json must be left untouched.
|
|
98
|
+
const jsonc = JSON.parse(readFileSync(join(root, "tui.jsonc"), "utf-8")) as {
|
|
99
|
+
plugin: unknown[];
|
|
100
|
+
keybinds: Record<string, string>;
|
|
101
|
+
};
|
|
102
|
+
expect(jsonc.plugin).toContain("@cortexkit/opencode-magic-context@latest");
|
|
103
|
+
expect(jsonc.keybinds).toEqual({ x: "y" });
|
|
104
|
+
expect(readFileSync(join(root, "tui.json"), "utf-8")).toBe("{}\n");
|
|
105
|
+
});
|
|
63
106
|
});
|
package/src/shared/tui-config.ts
CHANGED
|
@@ -62,9 +62,16 @@ function resolveTuiConfigPath(): string {
|
|
|
62
62
|
const jsoncPath = join(configDir, "tui.jsonc");
|
|
63
63
|
const jsonPath = join(configDir, "tui.json");
|
|
64
64
|
|
|
65
|
+
// OpenCode loads BOTH tui.json and tui.jsonc and merges them (tui.json first,
|
|
66
|
+
// tui.jsonc second, so .jsonc wins overlapping keys; plugin origins are
|
|
67
|
+
// deduped). So an existing tui.jsonc is the higher-precedence, user-facing
|
|
68
|
+
// file — write into it when present. Otherwise update an existing tui.json.
|
|
69
|
+
// For a fresh install create tui.jsonc, not tui.json: it lets the user add
|
|
70
|
+
// comments later and avoids leaving a second, lower-precedence config file
|
|
71
|
+
// alongside a tui.jsonc they create afterward (#176).
|
|
65
72
|
if (existsSync(jsoncPath)) return jsoncPath;
|
|
66
73
|
if (existsSync(jsonPath)) return jsonPath;
|
|
67
|
-
return
|
|
74
|
+
return jsoncPath;
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
/**
|