@rama_nigg/open-cursor 2.3.19 → 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/README.md +50 -48
- package/dist/cli/discover.js +177 -8
- package/dist/cli/mcptool.js +234 -5649
- package/dist/cli/opencode-cursor.js +930 -50
- package/dist/index.js +899 -5952
- package/dist/plugin-entry.js +877 -5932
- package/package.json +4 -2
- package/src/auth.ts +3 -1
- package/src/cli/model-discovery.ts +3 -2
- package/src/cli/opencode-cursor.ts +402 -23
- package/src/client/simple.ts +6 -3
- package/src/mcp/config.ts +49 -0
- package/src/mcp/tool-bridge.ts +1 -1
- package/src/models/discovery.ts +3 -2
- package/src/models/pricing.ts +196 -0
- package/src/models/variants.ts +446 -0
- package/src/plugin-toggle.ts +7 -1
- package/src/plugin.ts +167 -36
- package/src/provider/boundary.ts +10 -0
- package/src/provider/tool-loop-guard.ts +8 -3
- package/src/proxy/formatter.ts +30 -12
- package/src/proxy/prompt-builder.ts +10 -1
- package/src/streaming/types.ts +5 -0
- package/src/tools/defaults.ts +166 -0
- package/src/tools/executors/cli.ts +1 -0
- package/src/usage.ts +112 -0
- package/src/utils/binary.ts +57 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import { getCursorModelCost, type OpenCodeModelCost } from "./pricing.js";
|
|
2
|
+
|
|
3
|
+
export type DiscoveredCursorModel = {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type CursorModelVariant = {
|
|
9
|
+
baseId: string;
|
|
10
|
+
variant: string | null;
|
|
11
|
+
cursorModelId: string;
|
|
12
|
+
name: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type CursorModelGroup = {
|
|
16
|
+
baseId: string;
|
|
17
|
+
name: string;
|
|
18
|
+
defaultCursorModelId: string;
|
|
19
|
+
variants: Record<string, string>;
|
|
20
|
+
members: CursorModelVariant[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type CursorModelGroups = {
|
|
24
|
+
groups: CursorModelGroup[];
|
|
25
|
+
direct: DiscoveredCursorModel[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type OpenCodeCursorModelEntry = {
|
|
29
|
+
name: string;
|
|
30
|
+
options?: {
|
|
31
|
+
cursorModel: string;
|
|
32
|
+
};
|
|
33
|
+
variants?: Record<string, { cursorModel: string; cost?: OpenCodeModelCost }>;
|
|
34
|
+
cost?: OpenCodeModelCost;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type CursorModelMergeOptions = {
|
|
38
|
+
variants: boolean;
|
|
39
|
+
compact: boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type CursorModelMergeResult = {
|
|
43
|
+
models: Record<string, unknown>;
|
|
44
|
+
syncedCount: number;
|
|
45
|
+
groupedCount: number;
|
|
46
|
+
removedCount: number;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const DEFAULT_VARIANT_ORDER = [
|
|
50
|
+
null,
|
|
51
|
+
"medium",
|
|
52
|
+
"high",
|
|
53
|
+
"low",
|
|
54
|
+
"none",
|
|
55
|
+
"xhigh",
|
|
56
|
+
"max",
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const VARIANT_DISPLAY_ORDER = [
|
|
60
|
+
"none",
|
|
61
|
+
"low",
|
|
62
|
+
"low-fast",
|
|
63
|
+
"fast",
|
|
64
|
+
"medium",
|
|
65
|
+
"medium-fast",
|
|
66
|
+
"medium-thinking",
|
|
67
|
+
"high",
|
|
68
|
+
"high-fast",
|
|
69
|
+
"high-thinking",
|
|
70
|
+
"high-thinking-fast",
|
|
71
|
+
"xhigh",
|
|
72
|
+
"xhigh-fast",
|
|
73
|
+
"max",
|
|
74
|
+
"max-thinking",
|
|
75
|
+
"max-thinking-fast",
|
|
76
|
+
"thinking",
|
|
77
|
+
"thinking-low",
|
|
78
|
+
"thinking-medium",
|
|
79
|
+
"thinking-high",
|
|
80
|
+
"thinking-high-fast",
|
|
81
|
+
"thinking-xhigh",
|
|
82
|
+
"thinking-max",
|
|
83
|
+
"extra-high",
|
|
84
|
+
"spark-preview",
|
|
85
|
+
"spark-preview-low",
|
|
86
|
+
"spark-preview-medium",
|
|
87
|
+
"spark-preview-high",
|
|
88
|
+
"spark-preview-xhigh",
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
function isSafeBaseId(baseId: string): boolean {
|
|
92
|
+
const parts = baseId.split("-").filter(Boolean);
|
|
93
|
+
if (parts.length < 2) return false;
|
|
94
|
+
if (baseId === "gpt-5") return false;
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Token-aligned hyphen-truncated prefixes, longest first, filtered through
|
|
99
|
+
// isSafeBaseId. Example: "gpt-5.3-codex-spark-preview-low" yields
|
|
100
|
+
// ["gpt-5.3-codex-spark-preview", "gpt-5.3-codex", "gpt-5.3"].
|
|
101
|
+
function generateBaseCandidates(modelId: string): string[] {
|
|
102
|
+
const tokens = modelId.split("-");
|
|
103
|
+
const candidates: string[] = [];
|
|
104
|
+
for (let i = tokens.length - 1; i >= 1; i--) {
|
|
105
|
+
const prefix = tokens.slice(0, i).join("-");
|
|
106
|
+
if (isSafeBaseId(prefix)) candidates.push(prefix);
|
|
107
|
+
}
|
|
108
|
+
return candidates;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
type CandidateStat = { count: number; diversity: number };
|
|
112
|
+
|
|
113
|
+
// childCount(B) = number of models that have B as a strict token-prefix
|
|
114
|
+
// (model starts with `${B}-`). diversity = distinct first tokens after the
|
|
115
|
+
// prefix; used to prefer bases that fan out across multiple sibling families.
|
|
116
|
+
function computeStats(
|
|
117
|
+
candidate: string,
|
|
118
|
+
modelIds: readonly string[],
|
|
119
|
+
): CandidateStat {
|
|
120
|
+
const prefix = `${candidate}-`;
|
|
121
|
+
const firstTokens = new Set<string>();
|
|
122
|
+
let count = 0;
|
|
123
|
+
for (const otherId of modelIds) {
|
|
124
|
+
if (!otherId.startsWith(prefix)) continue;
|
|
125
|
+
count++;
|
|
126
|
+
const firstToken = otherId.slice(prefix.length).split("-", 1)[0];
|
|
127
|
+
if (firstToken) firstTokens.add(firstToken);
|
|
128
|
+
}
|
|
129
|
+
return { count, diversity: firstTokens.size };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Selection priority for the chosen base of a model:
|
|
133
|
+
// A. Shortest explicit base with >= 2 strict children. An explicit
|
|
134
|
+
// candidate that already heads its own family wins outright. Shortest
|
|
135
|
+
// wins so spark-preview-low folds under gpt-5.3-codex when both
|
|
136
|
+
// gpt-5.3-codex and gpt-5.3-codex-spark-preview are in the set.
|
|
137
|
+
// B. Best implicit base (any candidate with >= 2 strict children). Pick
|
|
138
|
+
// highest first-token diversity, breaking ties by longer base. Keeps
|
|
139
|
+
// claude-4.6-opus (fans out into high/max) from being shadowed by
|
|
140
|
+
// claude-4.6-opus-high (only thinking-fan-out) or by claude-4.6 (only
|
|
141
|
+
// opus-fan-out).
|
|
142
|
+
// C. Shortest explicit fallback regardless of childCount. Catches cases
|
|
143
|
+
// like composer-2-fast where the only candidate is explicit but has no
|
|
144
|
+
// other siblings to satisfy the >= 2 rule.
|
|
145
|
+
function chooseBase(
|
|
146
|
+
modelId: string,
|
|
147
|
+
knownModelIds: Set<string>,
|
|
148
|
+
modelIds: readonly string[],
|
|
149
|
+
): string | null {
|
|
150
|
+
const candidates = generateBaseCandidates(modelId);
|
|
151
|
+
if (candidates.length === 0) return null;
|
|
152
|
+
|
|
153
|
+
const stats = new Map<string, CandidateStat>();
|
|
154
|
+
for (const candidate of candidates) {
|
|
155
|
+
stats.set(candidate, computeStats(candidate, modelIds));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let stepA: string | null = null;
|
|
159
|
+
for (const candidate of candidates) {
|
|
160
|
+
if (!knownModelIds.has(candidate)) continue;
|
|
161
|
+
const stat = stats.get(candidate);
|
|
162
|
+
if (!stat || stat.count < 2 || stat.diversity < 2) continue;
|
|
163
|
+
if (stepA === null || candidate.length < stepA.length) stepA = candidate;
|
|
164
|
+
}
|
|
165
|
+
if (stepA !== null) return stepA;
|
|
166
|
+
|
|
167
|
+
let stepB: { base: string; diversity: number } | null = null;
|
|
168
|
+
for (const candidate of candidates) {
|
|
169
|
+
const stat = stats.get(candidate);
|
|
170
|
+
if (!stat || stat.count < 2) continue;
|
|
171
|
+
if (
|
|
172
|
+
stepB === null ||
|
|
173
|
+
stat.diversity > stepB.diversity ||
|
|
174
|
+
(stat.diversity === stepB.diversity && candidate.length > stepB.base.length)
|
|
175
|
+
) {
|
|
176
|
+
stepB = { base: candidate, diversity: stat.diversity };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (stepB !== null) return stepB.base;
|
|
180
|
+
|
|
181
|
+
let stepC: string | null = null;
|
|
182
|
+
for (const candidate of candidates) {
|
|
183
|
+
if (!knownModelIds.has(candidate)) continue;
|
|
184
|
+
if (stepC === null || candidate.length < stepC.length) stepC = candidate;
|
|
185
|
+
}
|
|
186
|
+
return stepC;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function getDefaultMember(members: CursorModelVariant[]): CursorModelVariant {
|
|
190
|
+
for (const variant of DEFAULT_VARIANT_ORDER) {
|
|
191
|
+
const member = members.find(candidate => candidate.variant === variant);
|
|
192
|
+
if (member) return member;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return members[0];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function formatModelName(modelId: string): string {
|
|
199
|
+
return modelId
|
|
200
|
+
.split("-")
|
|
201
|
+
.map(part => {
|
|
202
|
+
if (part === "gpt") return "GPT";
|
|
203
|
+
if (part === "xhigh") return "XHigh";
|
|
204
|
+
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
205
|
+
})
|
|
206
|
+
.join(" ");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function compareVariants(a: CursorModelVariant, b: CursorModelVariant): number {
|
|
210
|
+
if (a.variant === null) return -1;
|
|
211
|
+
if (b.variant === null) return 1;
|
|
212
|
+
|
|
213
|
+
const aIndex = VARIANT_DISPLAY_ORDER.indexOf(a.variant);
|
|
214
|
+
const bIndex = VARIANT_DISPLAY_ORDER.indexOf(b.variant);
|
|
215
|
+
|
|
216
|
+
if (aIndex !== -1 && bIndex !== -1) return aIndex - bIndex;
|
|
217
|
+
if (aIndex !== -1) return -1;
|
|
218
|
+
if (bIndex !== -1) return 1;
|
|
219
|
+
return a.variant.localeCompare(b.variant);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function createGroup(baseId: string, members: CursorModelVariant[]): CursorModelGroup {
|
|
223
|
+
const defaultMember = getDefaultMember(members);
|
|
224
|
+
const variants: Record<string, string> = {};
|
|
225
|
+
|
|
226
|
+
for (const member of [...members].sort(compareVariants)) {
|
|
227
|
+
if (member.variant) {
|
|
228
|
+
variants[member.variant] = member.cursorModelId;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
baseId,
|
|
234
|
+
name: defaultMember.variant === null ? defaultMember.name : formatModelName(baseId),
|
|
235
|
+
defaultCursorModelId: defaultMember.cursorModelId,
|
|
236
|
+
variants,
|
|
237
|
+
members,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function groupCursorModels(models: DiscoveredCursorModel[]): CursorModelGroups {
|
|
242
|
+
const knownModelIds = new Set(models.map(model => model.id));
|
|
243
|
+
const modelIds = models.map(model => model.id);
|
|
244
|
+
|
|
245
|
+
const preferredBase = new Map<string, string>();
|
|
246
|
+
for (const model of models) {
|
|
247
|
+
const base = chooseBase(model.id, knownModelIds, modelIds);
|
|
248
|
+
if (base) preferredBase.set(model.id, base);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// A model that is itself chosen as a base by some other model joins its own
|
|
252
|
+
// group as variant=null instead of being absorbed into a (different) base.
|
|
253
|
+
// This preserves explicit-base semantics: e.g. gpt-5.3-codex stays the head
|
|
254
|
+
// of its group rather than being folded under gpt-5.3 just because chooseBase
|
|
255
|
+
// for gpt-5.3-codex would otherwise return gpt-5.3.
|
|
256
|
+
const baseSet = new Set<string>(preferredBase.values());
|
|
257
|
+
|
|
258
|
+
const groupMembers = new Map<string, CursorModelVariant[]>();
|
|
259
|
+
const groupOrder: string[] = [];
|
|
260
|
+
|
|
261
|
+
const recordMember = (baseId: string, member: CursorModelVariant): void => {
|
|
262
|
+
const existing = groupMembers.get(baseId);
|
|
263
|
+
if (existing) {
|
|
264
|
+
existing.push(member);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
groupMembers.set(baseId, [member]);
|
|
268
|
+
groupOrder.push(baseId);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
for (const model of models) {
|
|
272
|
+
if (baseSet.has(model.id) && knownModelIds.has(model.id)) {
|
|
273
|
+
recordMember(model.id, {
|
|
274
|
+
baseId: model.id,
|
|
275
|
+
variant: null,
|
|
276
|
+
cursorModelId: model.id,
|
|
277
|
+
name: model.name,
|
|
278
|
+
});
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const base = preferredBase.get(model.id);
|
|
283
|
+
if (!base) continue;
|
|
284
|
+
|
|
285
|
+
recordMember(base, {
|
|
286
|
+
baseId: base,
|
|
287
|
+
variant: model.id.slice(base.length + 1),
|
|
288
|
+
cursorModelId: model.id,
|
|
289
|
+
name: model.name,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const groupedIds = new Set<string>();
|
|
294
|
+
const groups: CursorModelGroup[] = [];
|
|
295
|
+
|
|
296
|
+
for (const baseId of groupOrder) {
|
|
297
|
+
const members = groupMembers.get(baseId);
|
|
298
|
+
if (!members || members.length < 2) continue;
|
|
299
|
+
groups.push(createGroup(baseId, members));
|
|
300
|
+
for (const member of members) groupedIds.add(member.cursorModelId);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const direct: DiscoveredCursorModel[] = [];
|
|
304
|
+
for (const model of models) {
|
|
305
|
+
if (groupedIds.has(model.id)) continue;
|
|
306
|
+
direct.push(model);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return { groups, direct };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function createVariantModelEntries(models: DiscoveredCursorModel[]): {
|
|
313
|
+
entries: Record<string, OpenCodeCursorModelEntry>;
|
|
314
|
+
groupedModelIds: Set<string>;
|
|
315
|
+
} {
|
|
316
|
+
const { groups, direct } = groupCursorModels(models);
|
|
317
|
+
const entries: Record<string, OpenCodeCursorModelEntry> = {};
|
|
318
|
+
const groupedModelIds = new Set<string>();
|
|
319
|
+
|
|
320
|
+
for (const group of groups) {
|
|
321
|
+
const variants: Record<string, { cursorModel: string; cost?: OpenCodeModelCost }> = {};
|
|
322
|
+
for (const [variant, cursorModel] of Object.entries(group.variants)) {
|
|
323
|
+
const variantEntry: { cursorModel: string; cost?: OpenCodeModelCost } = { cursorModel };
|
|
324
|
+
const variantCost = getCursorModelCost(cursorModel);
|
|
325
|
+
if (variantCost) variantEntry.cost = variantCost;
|
|
326
|
+
variants[variant] = variantEntry;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const groupEntry: OpenCodeCursorModelEntry = {
|
|
330
|
+
name: group.name,
|
|
331
|
+
options: {
|
|
332
|
+
cursorModel: group.defaultCursorModelId,
|
|
333
|
+
},
|
|
334
|
+
variants,
|
|
335
|
+
};
|
|
336
|
+
const defaultCost = getCursorModelCost(group.defaultCursorModelId);
|
|
337
|
+
if (defaultCost) groupEntry.cost = defaultCost;
|
|
338
|
+
entries[group.baseId] = groupEntry;
|
|
339
|
+
|
|
340
|
+
for (const member of group.members) {
|
|
341
|
+
groupedModelIds.add(member.cursorModelId);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
for (const model of direct) {
|
|
346
|
+
const entry: OpenCodeCursorModelEntry = { name: model.name };
|
|
347
|
+
const directCost = getCursorModelCost(model.id);
|
|
348
|
+
if (directCost) entry.cost = directCost;
|
|
349
|
+
entries[model.id] = entry;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return { entries, groupedModelIds };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function mergeCursorModelEntries(
|
|
356
|
+
existingModels: Record<string, unknown>,
|
|
357
|
+
discoveredModels: DiscoveredCursorModel[],
|
|
358
|
+
options: CursorModelMergeOptions,
|
|
359
|
+
): CursorModelMergeResult {
|
|
360
|
+
if (!options.variants) {
|
|
361
|
+
return mergeDirectModelEntries(existingModels, discoveredModels);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const { entries, groupedModelIds } = createVariantModelEntries(discoveredModels);
|
|
365
|
+
const models = { ...existingModels };
|
|
366
|
+
let removedCount = 0;
|
|
367
|
+
|
|
368
|
+
if (options.compact) {
|
|
369
|
+
for (const modelId of groupedModelIds) {
|
|
370
|
+
if (!Object.prototype.hasOwnProperty.call(models, modelId)) continue;
|
|
371
|
+
if (Object.prototype.hasOwnProperty.call(entries, modelId)) continue;
|
|
372
|
+
delete models[modelId];
|
|
373
|
+
removedCount++;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
for (const [modelId, entry] of Object.entries(entries)) {
|
|
378
|
+
models[modelId] = mergeEntryPreservingUserFields(models[modelId], entry);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
models,
|
|
383
|
+
syncedCount: Object.keys(entries).length,
|
|
384
|
+
groupedCount: groupedModelIds.size,
|
|
385
|
+
removedCount,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function mergeDirectModelEntries(
|
|
390
|
+
existingModels: Record<string, unknown>,
|
|
391
|
+
discoveredModels: DiscoveredCursorModel[],
|
|
392
|
+
): CursorModelMergeResult {
|
|
393
|
+
const models = { ...existingModels };
|
|
394
|
+
|
|
395
|
+
for (const model of discoveredModels) {
|
|
396
|
+
const generated: OpenCodeCursorModelEntry = { name: model.name };
|
|
397
|
+
const directCost = getCursorModelCost(model.id);
|
|
398
|
+
if (directCost) generated.cost = directCost;
|
|
399
|
+
models[model.id] = mergeEntryPreservingUserFields(models[model.id], generated);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
models,
|
|
404
|
+
syncedCount: discoveredModels.length,
|
|
405
|
+
groupedCount: 0,
|
|
406
|
+
removedCount: 0,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
411
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Preserve user-set cost on every sync. Only fill cost when the user has not.
|
|
415
|
+
function mergeEntryPreservingUserFields(
|
|
416
|
+
existing: unknown,
|
|
417
|
+
generated: OpenCodeCursorModelEntry,
|
|
418
|
+
): OpenCodeCursorModelEntry {
|
|
419
|
+
if (!isPlainObject(existing)) return generated;
|
|
420
|
+
|
|
421
|
+
const merged: Record<string, unknown> = { ...existing, ...generated };
|
|
422
|
+
|
|
423
|
+
if (existing.cost !== undefined) {
|
|
424
|
+
merged.cost = existing.cost;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (isPlainObject(existing.variants) && isPlainObject(generated.variants)) {
|
|
428
|
+
const mergedVariants: Record<string, unknown> = { ...generated.variants };
|
|
429
|
+
for (const [variantKey, existingVariant] of Object.entries(existing.variants)) {
|
|
430
|
+
const generatedVariant = (generated.variants as Record<string, unknown>)[variantKey];
|
|
431
|
+
if (!isPlainObject(existingVariant)) continue;
|
|
432
|
+
if (!isPlainObject(generatedVariant)) {
|
|
433
|
+
mergedVariants[variantKey] = existingVariant;
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
const variantMerged: Record<string, unknown> = { ...generatedVariant };
|
|
437
|
+
if (existingVariant.cost !== undefined) {
|
|
438
|
+
variantMerged.cost = existingVariant.cost;
|
|
439
|
+
}
|
|
440
|
+
mergedVariants[variantKey] = variantMerged;
|
|
441
|
+
}
|
|
442
|
+
merged.variants = mergedVariants;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return merged as OpenCodeCursorModelEntry;
|
|
446
|
+
}
|
package/src/plugin-toggle.ts
CHANGED
|
@@ -33,6 +33,12 @@ export function isCursorPluginEnabledInConfig(config: unknown): boolean {
|
|
|
33
33
|
|
|
34
34
|
const configObject = config as { plugin?: unknown; provider?: unknown };
|
|
35
35
|
|
|
36
|
+
if (configObject.provider && typeof configObject.provider === "object") {
|
|
37
|
+
if (CURSOR_PROVIDER_ID in (configObject.provider as Record<string, unknown>)) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
if (Array.isArray(configObject.plugin)) {
|
|
37
43
|
return configObject.plugin.some((entry) => matchesPlugin(entry));
|
|
38
44
|
}
|
|
@@ -63,7 +69,7 @@ export function shouldEnableCursorPlugin(env: EnvLike = process.env): {
|
|
|
63
69
|
return {
|
|
64
70
|
enabled,
|
|
65
71
|
configPath,
|
|
66
|
-
reason: enabled ? "
|
|
72
|
+
reason: enabled ? "enabled" : "disabled_in_plugin_array",
|
|
67
73
|
};
|
|
68
74
|
} catch {
|
|
69
75
|
return {
|