@cortexkit/opencode-magic-context 0.11.0 → 0.11.1

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":"event-resolvers.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/event-resolvers.ts"],"names":[],"mappings":"AAIA,KAAK,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEtD,wBAAgB,mBAAmB,CAC/B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,MAAM,GAAG,SAAS,GAC5B,MAAM,CAiBR;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAiB9F;AAED,KAAK,sBAAsB,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC;AAEvF,wBAAgB,uBAAuB,CACnC,MAAM,EAAE,sBAAsB,EAC9B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,QAAQ,EAAE,MAAM,GACjB,MAAM,CAuBR;AAED,wBAAgB,eAAe,CAC3B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,MAAM,GAAG,SAAS,GAC5B,MAAM,GAAG,SAAS,CAMpB;AAED,wBAAgB,gBAAgB,CAC5B,UAAU,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAC/D,MAAM,GAAG,SAAS,CAmBpB"}
1
+ {"version":3,"file":"event-resolvers.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/event-resolvers.ts"],"names":[],"mappings":"AAIA,KAAK,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEtD,wBAAgB,mBAAmB,CAC/B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,MAAM,GAAG,SAAS,GAC5B,MAAM,CAiBR;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAiB9F;AAED,KAAK,sBAAsB,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC;AA+BvF,wBAAgB,uBAAuB,CACnC,MAAM,EAAE,sBAAsB,EAC9B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,QAAQ,EAAE,MAAM,GACjB,MAAM,CAuBR;AAED,wBAAgB,eAAe,CAC3B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,MAAM,GAAG,SAAS,GAC5B,MAAM,GAAG,SAAS,CAMpB;AAED,wBAAgB,gBAAgB,CAC5B,UAAU,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAC/D,MAAM,GAAG,SAAS,CAmBpB"}
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/transform.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/transform.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -150985,6 +150985,15 @@ function getOpencodeConfigPath() {
150985
150985
  return json2;
150986
150986
  return null;
150987
150987
  }
150988
+ function resolveLimit(limit) {
150989
+ if (!limit)
150990
+ return;
150991
+ if (typeof limit.input === "number" && limit.input > 0)
150992
+ return limit.input;
150993
+ if (typeof limit.context === "number" && limit.context > 0)
150994
+ return limit.context;
150995
+ return;
150996
+ }
150988
150997
  function loadModelsDevLimitsFromFile() {
150989
150998
  const limits = new Map;
150990
150999
  const modelsJsonPath = getModelsJsonPath();
@@ -150998,13 +151007,13 @@ function loadModelsDevLimitsFromFile() {
150998
151007
  if (!provider2?.models || typeof provider2.models !== "object")
150999
151008
  continue;
151000
151009
  for (const [modelId, model] of Object.entries(provider2.models)) {
151001
- const context = model?.limit?.context;
151002
- if (typeof context === "number" && context > 0) {
151003
- limits.set(`${providerId}/${modelId}`, context);
151010
+ const effective = resolveLimit(model?.limit);
151011
+ if (typeof effective === "number" && effective > 0) {
151012
+ limits.set(`${providerId}/${modelId}`, effective);
151004
151013
  const modes = model?.experimental?.modes;
151005
151014
  if (modes && typeof modes === "object") {
151006
151015
  for (const mode of Object.keys(modes)) {
151007
- limits.set(`${providerId}/${modelId}-${mode}`, context);
151016
+ limits.set(`${providerId}/${modelId}-${mode}`, effective);
151008
151017
  }
151009
151018
  }
151010
151019
  }
@@ -151025,9 +151034,9 @@ function loadModelsDevLimitsFromFile() {
151025
151034
  if (!provider2?.models || typeof provider2.models !== "object")
151026
151035
  continue;
151027
151036
  for (const [modelId, model] of Object.entries(provider2.models)) {
151028
- const context = model?.limit?.context;
151029
- if (typeof context === "number" && context > 0) {
151030
- limits.set(`${providerId}/${modelId}`, context);
151037
+ const effective = resolveLimit(model?.limit);
151038
+ if (typeof effective === "number" && effective > 0) {
151039
+ limits.set(`${providerId}/${modelId}`, effective);
151031
151040
  }
151032
151041
  }
151033
151042
  }
@@ -151054,9 +151063,9 @@ async function refreshModelLimitsFromApi(client) {
151054
151063
  if (!p?.id || !p.models || typeof p.models !== "object")
151055
151064
  continue;
151056
151065
  for (const [modelId, model] of Object.entries(p.models)) {
151057
- const context = model?.limit?.context;
151058
- if (typeof context === "number" && context > 0) {
151059
- map2.set(`${p.id}/${modelId}`, context);
151066
+ const effective = resolveLimit(model?.limit);
151067
+ if (typeof effective === "number" && effective > 0) {
151068
+ map2.set(`${p.id}/${modelId}`, effective);
151060
151069
  }
151061
151070
  }
151062
151071
  }
@@ -163053,20 +163062,34 @@ function resolveCacheTtl(cacheTtl, modelKey) {
163053
163062
  }
163054
163063
  return cacheTtl.default ?? "5m";
163055
163064
  }
163065
+ function* modelKeyLookupOrder(modelKey) {
163066
+ const slash = modelKey.indexOf("/");
163067
+ const provider2 = slash >= 0 ? modelKey.slice(0, slash) : "";
163068
+ let modelId = slash >= 0 ? modelKey.slice(slash + 1) : modelKey;
163069
+ while (modelId.length > 0) {
163070
+ if (provider2)
163071
+ yield `${provider2}/${modelId}`;
163072
+ yield modelId;
163073
+ const lastDash = modelId.lastIndexOf("-");
163074
+ if (lastDash <= 0)
163075
+ break;
163076
+ modelId = modelId.slice(0, lastDash);
163077
+ }
163078
+ }
163056
163079
  function resolveExecuteThreshold(config2, modelKey, fallback) {
163057
163080
  const MAX_EXECUTE_THRESHOLD = 80;
163058
163081
  let resolved;
163059
163082
  if (typeof config2 === "number") {
163060
163083
  resolved = config2;
163061
- } else if (modelKey && typeof config2[modelKey] === "number") {
163062
- resolved = config2[modelKey];
163063
163084
  } else if (modelKey) {
163064
- const bareModelId = modelKey.split("/").slice(1).join("/");
163065
- if (bareModelId && typeof config2[bareModelId] === "number") {
163066
- resolved = config2[bareModelId];
163067
- } else {
163068
- resolved = config2.default ?? fallback;
163085
+ let matched;
163086
+ for (const candidate of modelKeyLookupOrder(modelKey)) {
163087
+ if (typeof config2[candidate] === "number") {
163088
+ matched = config2[candidate];
163089
+ break;
163090
+ }
163069
163091
  }
163092
+ resolved = matched ?? config2.default ?? fallback;
163070
163093
  } else {
163071
163094
  resolved = config2.default ?? fallback;
163072
163095
  }
@@ -166020,7 +166043,9 @@ function createTransform(deps) {
166020
166043
  const lastAssistantModel = findLastAssistantModel(messages);
166021
166044
  if (lastAssistantModel) {
166022
166045
  const knownModel = deps.liveModelBySession.get(sessionId);
166023
- if (knownModel && (knownModel.providerID !== lastAssistantModel.providerID || knownModel.modelID !== lastAssistantModel.modelID)) {
166046
+ if (!knownModel) {
166047
+ deps.liveModelBySession.set(sessionId, lastAssistantModel);
166048
+ } else if (knownModel.providerID !== lastAssistantModel.providerID || knownModel.modelID !== lastAssistantModel.modelID) {
166024
166049
  sessionLog(sessionId, `transform: model change detected (${knownModel.providerID}/${knownModel.modelID} -> ${lastAssistantModel.providerID}/${lastAssistantModel.modelID}), clearing stale context state`);
166025
166050
  deps.liveModelBySession.set(sessionId, lastAssistantModel);
166026
166051
  updateSessionMeta(db, sessionId, {
@@ -1 +1 @@
1
- {"version":3,"file":"models-dev-cache.d.ts","sourceRoot":"","sources":["../../src/shared/models-dev-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAMH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AA2J9D;;;;;;;;;;GAUG;AACH,wBAAsB,yBAAyB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCrF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAchG;AAED,4CAA4C;AAC5C,wBAAgB,mBAAmB,IAAI,IAAI,CAK1C;AAED,oDAAoD;AACpD,wBAAgB,sBAAsB,IAAI;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACrB,CAOA"}
1
+ {"version":3,"file":"models-dev-cache.d.ts","sourceRoot":"","sources":["../../src/shared/models-dev-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAMH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AA+K9D;;;;;;;;;;GAUG;AACH,wBAAsB,yBAAyB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCrF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAchG;AAED,4CAA4C;AAC5C,wBAAgB,mBAAmB,IAAI,IAAI,CAK1C;AAED,oDAAoD;AACpD,wBAAgB,sBAAsB,IAAI;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACrB,CAOA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-magic-context",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Magic Context — cross-session memory and context management",
6
6
  "main": "dist/index.js",
@@ -19,11 +19,17 @@ describe("models-dev-cache", () => {
19
19
  OPENCODE_MODELS_PATH: process.env.OPENCODE_MODELS_PATH,
20
20
  OPENCODE_MODELS_URL: process.env.OPENCODE_MODELS_URL,
21
21
  XDG_CACHE_HOME: process.env.XDG_CACHE_HOME,
22
+ OPENCODE_CONFIG_DIR: process.env.OPENCODE_CONFIG_DIR,
22
23
  };
23
- // Isolate from user environment.
24
+ // Isolate from user environment — including user's ~/.config/opencode/opencode.jsonc
25
+ // which may have custom provider limits that would override models.json entries.
24
26
  delete process.env.OPENCODE_MODELS_PATH;
25
27
  delete process.env.OPENCODE_MODELS_URL;
26
28
  process.env.XDG_CACHE_HOME = tempDir;
29
+ // Point at an empty directory so no opencode.json{c} is read unless the test writes one.
30
+ const emptyConfigDir = join(tempDir, "config", "opencode");
31
+ mkdirSync(emptyConfigDir, { recursive: true });
32
+ process.env.OPENCODE_CONFIG_DIR = emptyConfigDir;
27
33
  clearModelsDevCache();
28
34
  });
29
35
 
@@ -61,6 +67,113 @@ describe("models-dev-cache", () => {
61
67
  expect(getModelsDevContextLimit("unknown", "unknown")).toBeUndefined();
62
68
  });
63
69
 
70
+ test("prefers limit.input over limit.context when both are present", () => {
71
+ //#given — GitHub Copilot shape: input is max prompt, context is total window.
72
+ // Matches real-world github-copilot/gpt-5.3-codex which has
73
+ // limit.context = 400000 (total), limit.input = 272000 (max prompt).
74
+ // Our pressure math must use the input cap; sending a 400K prompt gets rejected.
75
+ // OpenCode's own session/overflow.ts follows the same rule.
76
+ const opencodeDir = join(tempDir, "opencode");
77
+ mkdirSync(opencodeDir, { recursive: true });
78
+ writeFileSync(
79
+ join(opencodeDir, "models.json"),
80
+ JSON.stringify({
81
+ "github-copilot": {
82
+ models: {
83
+ "gpt-5.3-codex": { limit: { context: 400000, input: 272000 } },
84
+ "claude-opus-4.6": { limit: { context: 144000, input: 128000 } },
85
+ // Context-only model (no input) falls back to context.
86
+ "legacy-only-context": { limit: { context: 100000 } },
87
+ },
88
+ },
89
+ }),
90
+ );
91
+
92
+ //#then
93
+ expect(getModelsDevContextLimit("github-copilot", "gpt-5.3-codex")).toBe(272000);
94
+ expect(getModelsDevContextLimit("github-copilot", "claude-opus-4.6")).toBe(128000);
95
+ expect(getModelsDevContextLimit("github-copilot", "legacy-only-context")).toBe(100000);
96
+ });
97
+
98
+ test("derived experimental.modes inherit the effective (input) limit", () => {
99
+ //#given — parent has input < context; derived modes should inherit input, not context
100
+ const opencodeDir = join(tempDir, "opencode");
101
+ mkdirSync(opencodeDir, { recursive: true });
102
+ writeFileSync(
103
+ join(opencodeDir, "models.json"),
104
+ JSON.stringify({
105
+ openai: {
106
+ models: {
107
+ "gpt-5.4": {
108
+ limit: { context: 1050000, input: 922000 },
109
+ experimental: { modes: { fast: {}, mini: {} } },
110
+ },
111
+ },
112
+ },
113
+ }),
114
+ );
115
+
116
+ //#then
117
+ expect(getModelsDevContextLimit("openai", "gpt-5.4")).toBe(922000);
118
+ expect(getModelsDevContextLimit("openai", "gpt-5.4-fast")).toBe(922000);
119
+ expect(getModelsDevContextLimit("openai", "gpt-5.4-mini")).toBe(922000);
120
+ });
121
+
122
+ test("custom opencode.json provider overlay uses limit.input preferentially", () => {
123
+ //#given — user defines a proxy provider in opencode.json with input < context
124
+ const opencodeDir = join(tempDir, "opencode");
125
+ mkdirSync(opencodeDir, { recursive: true });
126
+ const configDir = join(tempDir, "config", "opencode");
127
+ mkdirSync(configDir, { recursive: true });
128
+ writeFileSync(
129
+ join(configDir, "opencode.json"),
130
+ JSON.stringify({
131
+ provider: {
132
+ "my-proxy": {
133
+ models: {
134
+ "split-model": { limit: { context: 400000, input: 200000 } },
135
+ },
136
+ },
137
+ },
138
+ }),
139
+ );
140
+ process.env.OPENCODE_CONFIG_DIR = configDir;
141
+ clearModelsDevCache();
142
+
143
+ //#then
144
+ expect(getModelsDevContextLimit("my-proxy", "split-model")).toBe(200000);
145
+
146
+ // Cleanup: restore env (afterEach also handles this, but we added a new var)
147
+ delete process.env.OPENCODE_CONFIG_DIR;
148
+ });
149
+
150
+ test("API cache uses limit.input preferentially", async () => {
151
+ //#given — API response shape mirrors file layer
152
+ const mockClient = {
153
+ config: {
154
+ providers: async () => ({
155
+ data: {
156
+ providers: [
157
+ {
158
+ id: "github-copilot",
159
+ models: {
160
+ "gpt-5.3-codex": {
161
+ limit: { context: 400000, input: 272000 },
162
+ },
163
+ },
164
+ },
165
+ ],
166
+ },
167
+ }),
168
+ },
169
+ };
170
+ // @ts-expect-error mock narrow shape
171
+ await refreshModelLimitsFromApi(mockClient);
172
+
173
+ //#then
174
+ expect(getModelsDevContextLimit("github-copilot", "gpt-5.3-codex")).toBe(272000);
175
+ });
176
+
64
177
  test("expands experimental.modes into derived model IDs with parent context", () => {
65
178
  const opencodeDir = join(tempDir, "opencode");
66
179
  mkdirSync(opencodeDir, { recursive: true });
@@ -86,6 +86,24 @@ function getOpencodeConfigPath(): string | null {
86
86
  return null;
87
87
  }
88
88
 
89
+ /**
90
+ * Resolve the effective pressure limit for a model's `limit` object.
91
+ *
92
+ * Prefers `limit.input` (max prompt tokens the provider will accept) over
93
+ * `limit.context` (total window including output). For GitHub Copilot and
94
+ * several proxy providers, `context` is the marketing number (input + output
95
+ * combined), and sending a prompt sized against `context` gets rejected.
96
+ * OpenCode's own `session/overflow.ts` uses `input ?? context` for the same
97
+ * reason — the denominator that drives overflow/pressure must be the number
98
+ * the provider actually enforces on input.
99
+ */
100
+ function resolveLimit(limit: { context?: number; input?: number } | undefined): number | undefined {
101
+ if (!limit) return undefined;
102
+ if (typeof limit.input === "number" && limit.input > 0) return limit.input;
103
+ if (typeof limit.context === "number" && limit.context > 0) return limit.context;
104
+ return undefined;
105
+ }
106
+
89
107
  function loadModelsDevLimitsFromFile(): Map<string, number> {
90
108
  const limits = new Map<string, number>();
91
109
 
@@ -102,7 +120,7 @@ function loadModelsDevLimitsFromFile(): Map<string, number> {
102
120
  models?: Record<
103
121
  string,
104
122
  {
105
- limit?: { context?: number };
123
+ limit?: { context?: number; input?: number };
106
124
  experimental?: { modes?: Record<string, unknown> };
107
125
  }
108
126
  >;
@@ -112,15 +130,15 @@ function loadModelsDevLimitsFromFile(): Map<string, number> {
112
130
  for (const [providerId, provider] of Object.entries(data)) {
113
131
  if (!provider?.models || typeof provider.models !== "object") continue;
114
132
  for (const [modelId, model] of Object.entries(provider.models)) {
115
- const context = model?.limit?.context;
116
- if (typeof context === "number" && context > 0) {
117
- limits.set(`${providerId}/${modelId}`, context);
133
+ const effective = resolveLimit(model?.limit);
134
+ if (typeof effective === "number" && effective > 0) {
135
+ limits.set(`${providerId}/${modelId}`, effective);
118
136
  // OpenCode creates derived model IDs from experimental.modes
119
137
  // e.g. gpt-5.4 + modes.fast → gpt-5.4-fast (inherits parent limit).
120
138
  const modes = model?.experimental?.modes;
121
139
  if (modes && typeof modes === "object") {
122
140
  for (const mode of Object.keys(modes)) {
123
- limits.set(`${providerId}/${modelId}-${mode}`, context);
141
+ limits.set(`${providerId}/${modelId}-${mode}`, effective);
124
142
  }
125
143
  }
126
144
  }
@@ -136,7 +154,7 @@ function loadModelsDevLimitsFromFile(): Map<string, number> {
136
154
  }
137
155
 
138
156
  // 2. Overlay custom provider models from OpenCode config (higher priority).
139
- // Users define custom/proxy models via provider.<id>.models.<name>.limit.context
157
+ // Users define custom/proxy models via provider.<id>.models.<name>.limit.{input,context}
140
158
  // in opencode.json(c). These override models.dev entries for the same key.
141
159
  try {
142
160
  const configPath = getOpencodeConfigPath();
@@ -149,7 +167,9 @@ function loadModelsDevLimitsFromFile(): Map<string, number> {
149
167
  const config = JSON.parse(raw) as {
150
168
  provider?: Record<
151
169
  string,
152
- { models?: Record<string, { limit?: { context?: number } }> }
170
+ {
171
+ models?: Record<string, { limit?: { context?: number; input?: number } }>;
172
+ }
153
173
  >;
154
174
  };
155
175
 
@@ -157,9 +177,9 @@ function loadModelsDevLimitsFromFile(): Map<string, number> {
157
177
  for (const [providerId, provider] of Object.entries(config.provider)) {
158
178
  if (!provider?.models || typeof provider.models !== "object") continue;
159
179
  for (const [modelId, model] of Object.entries(provider.models)) {
160
- const context = model?.limit?.context;
161
- if (typeof context === "number" && context > 0) {
162
- limits.set(`${providerId}/${modelId}`, context);
180
+ const effective = resolveLimit(model?.limit);
181
+ if (typeof effective === "number" && effective > 0) {
182
+ limits.set(`${providerId}/${modelId}`, effective);
163
183
  }
164
184
  }
165
185
  }
@@ -206,13 +226,13 @@ export async function refreshModelLimitsFromApi(client: OpencodeClient): Promise
206
226
  for (const entry of providers) {
207
227
  const p = entry as {
208
228
  id?: string;
209
- models?: Record<string, { limit?: { context?: number } }>;
229
+ models?: Record<string, { limit?: { context?: number; input?: number } }>;
210
230
  };
211
231
  if (!p?.id || !p.models || typeof p.models !== "object") continue;
212
232
  for (const [modelId, model] of Object.entries(p.models)) {
213
- const context = model?.limit?.context;
214
- if (typeof context === "number" && context > 0) {
215
- map.set(`${p.id}/${modelId}`, context);
233
+ const effective = resolveLimit(model?.limit);
234
+ if (typeof effective === "number" && effective > 0) {
235
+ map.set(`${p.id}/${modelId}`, effective);
216
236
  }
217
237
  }
218
238
  }