cursor-opencode-provider 0.8.0 → 0.8.3
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/CHANGELOG.md +174 -0
- package/README.md +230 -44
- package/SECURITY.md +60 -12
- package/dist/activity.d.ts +73 -1
- package/dist/activity.js +231 -5
- package/dist/agent-url.js +37 -9
- package/dist/auth-renewal.d.ts +1 -1
- package/dist/auth-renewal.js +58 -26
- package/dist/billing.d.ts +105 -0
- package/dist/billing.js +212 -0
- package/dist/classic-tool-results.d.ts +1 -0
- package/dist/classic-tool-results.js +42 -0
- package/dist/context/build.d.ts +23 -4
- package/dist/context/build.js +65 -10
- package/dist/context/dynamic-catalog.js +6 -1
- package/dist/context/env.js +27 -3
- package/dist/context/epoch.js +9 -5
- package/dist/context/frozen.js +6 -2
- package/dist/context/git.d.ts +5 -2
- package/dist/context/git.js +17 -7
- package/dist/context/index.d.ts +2 -0
- package/dist/context/index.js +2 -0
- package/dist/context/paths.d.ts +45 -18
- package/dist/context/paths.js +72 -22
- package/dist/context/skills-bridge.d.ts +22 -0
- package/dist/context/skills-bridge.js +20 -0
- package/dist/context/skills.d.ts +34 -0
- package/dist/context/skills.js +211 -0
- package/dist/debug.js +13 -2
- package/dist/early-tool-rows.d.ts +60 -0
- package/dist/early-tool-rows.js +96 -0
- package/dist/errors.d.ts +49 -7
- package/dist/errors.js +163 -8
- package/dist/host-agent-mode.d.ts +53 -4
- package/dist/host-agent-mode.js +117 -13
- package/dist/host-interrupt.d.ts +12 -0
- package/dist/host-interrupt.js +18 -0
- package/dist/host-plan-file.d.ts +28 -0
- package/dist/host-plan-file.js +76 -0
- package/dist/host-steer.d.ts +71 -0
- package/dist/host-steer.js +232 -0
- package/dist/image-input.d.ts +18 -0
- package/dist/image-input.js +122 -30
- package/dist/image-save.d.ts +10 -2
- package/dist/image-save.js +33 -21
- package/dist/index.d.ts +2 -2
- package/dist/language-model.d.ts +149 -30
- package/dist/language-model.js +3127 -673
- package/dist/lost-request.d.ts +39 -0
- package/dist/lost-request.js +111 -0
- package/dist/model-config.d.ts +12 -1
- package/dist/model-config.js +78 -30
- package/dist/models.d.ts +26 -3
- package/dist/models.js +101 -45
- package/dist/opencode2/catalog.d.ts +1 -0
- package/dist/opencode2/catalog.js +2 -0
- package/dist/opencode2/image-save-tool.d.ts +8 -0
- package/dist/opencode2/image-save-tool.js +84 -0
- package/dist/opencode2/tool-results.d.ts +18 -0
- package/dist/opencode2/tool-results.js +66 -0
- package/dist/opencode2/types.d.ts +15 -0
- package/dist/parallel-step.d.ts +65 -0
- package/dist/parallel-step.js +102 -0
- package/dist/plugin-opencode2.js +233 -65
- package/dist/plugin.js +97 -23
- package/dist/pricing-data.d.ts +40 -3
- package/dist/pricing-data.js +40 -3
- package/dist/pricing.d.ts +1 -1
- package/dist/pricing.js +7 -4
- package/dist/process-shared.d.ts +14 -0
- package/dist/process-shared.js +18 -0
- package/dist/protocol/apply-patch.d.ts +25 -13
- package/dist/protocol/apply-patch.js +79 -30
- package/dist/protocol/ask-question.d.ts +12 -1
- package/dist/protocol/ask-question.js +77 -3
- package/dist/protocol/blob-store.d.ts +5 -1
- package/dist/protocol/blob-store.js +10 -6
- package/dist/protocol/client-version.js +6 -4
- package/dist/protocol/conversation-cache-header.d.ts +23 -0
- package/dist/protocol/conversation-cache-header.js +149 -0
- package/dist/protocol/conversation-persistence.d.ts +27 -1
- package/dist/protocol/conversation-persistence.js +628 -126
- package/dist/protocol/conversation-state.d.ts +8 -1
- package/dist/protocol/conversation-state.js +28 -7
- package/dist/protocol/create-plan.d.ts +47 -25
- package/dist/protocol/create-plan.js +33 -23
- package/dist/protocol/host-notes.d.ts +31 -0
- package/dist/protocol/host-notes.js +85 -0
- package/dist/protocol/interactions.d.ts +26 -17
- package/dist/protocol/interactions.js +80 -31
- package/dist/protocol/messages.js +164 -21
- package/dist/protocol/request.d.ts +40 -19
- package/dist/protocol/request.js +55 -28
- package/dist/protocol/sent-history-images.d.ts +49 -0
- package/dist/protocol/sent-history-images.js +102 -0
- package/dist/protocol/switch-mode.d.ts +66 -27
- package/dist/protocol/switch-mode.js +147 -73
- package/dist/protocol/token-details.d.ts +2 -0
- package/dist/protocol/token-details.js +11 -1
- package/dist/protocol/tool-call-bridge.d.ts +6 -0
- package/dist/protocol/tool-call-bridge.js +24 -0
- package/dist/protocol/tools.d.ts +90 -9
- package/dist/protocol/tools.js +543 -151
- package/dist/protocol/turn-provenance.d.ts +19 -4
- package/dist/protocol/turn-provenance.js +77 -20
- package/dist/replay-safety.d.ts +22 -4
- package/dist/replay-safety.js +86 -27
- package/dist/retry-budget.d.ts +21 -0
- package/dist/retry-budget.js +94 -0
- package/dist/runtime-files.d.ts +23 -0
- package/dist/runtime-files.js +581 -0
- package/dist/session.d.ts +129 -10
- package/dist/session.js +198 -35
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +1 -1
- package/dist/shell-timeout.d.ts +54 -10
- package/dist/shell-timeout.js +362 -92
- package/dist/stream-write.d.ts +20 -0
- package/dist/stream-write.js +87 -0
- package/dist/terminal-runner.d.ts +15 -0
- package/dist/terminal-runner.js +528 -0
- package/dist/todo-store.d.ts +2 -0
- package/dist/todo-store.js +19 -0
- package/dist/transport/connect.d.ts +10 -1
- package/dist/transport/connect.js +124 -27
- package/dist/usage.d.ts +37 -17
- package/dist/usage.js +85 -34
- package/package.json +5 -3
- package/dist/plan-execution-kickoff.d.ts +0 -57
- package/dist/plan-execution-kickoff.js +0 -186
- package/dist/protocol/thinking.d.ts +0 -15
- package/dist/protocol/thinking.js +0 -17
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type LostRequest = {
|
|
2
|
+
conversationId: string;
|
|
3
|
+
checkpointKey: string;
|
|
4
|
+
/** Everything Cursor has not checkpointed, as the last attempt sent it. */
|
|
5
|
+
text: string;
|
|
6
|
+
/** That attempt's own live text, before anything was prepended. */
|
|
7
|
+
latest: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function checkpointKey(bytes: Uint8Array | undefined): string;
|
|
10
|
+
/**
|
|
11
|
+
* Prepend a user message whose Run never received a newer checkpoint.
|
|
12
|
+
* A history rebuild already contains it, so that path drops the record.
|
|
13
|
+
* A retry is the previous attempt's live text again: it sends what that
|
|
14
|
+
* attempt sent. A host that sends the same note before every attempt must not
|
|
15
|
+
* pile it up. Any other text is a new request and is appended, even when it
|
|
16
|
+
* repeats an older one (red, blue, then red again).
|
|
17
|
+
*/
|
|
18
|
+
export declare function carryLostRequest(input: {
|
|
19
|
+
sessionKey: string;
|
|
20
|
+
conversationId: string;
|
|
21
|
+
checkpoint: Uint8Array | undefined;
|
|
22
|
+
text: string;
|
|
23
|
+
rebuildFromHistory: boolean;
|
|
24
|
+
}): string;
|
|
25
|
+
/** `text` is what this turn sends; `latest` is its own live text (defaults to `text`). */
|
|
26
|
+
export declare function rememberLostRequest(input: {
|
|
27
|
+
sessionKey: string;
|
|
28
|
+
conversationId: string;
|
|
29
|
+
checkpoint: Uint8Array | undefined;
|
|
30
|
+
text: string;
|
|
31
|
+
latest?: string;
|
|
32
|
+
}): void;
|
|
33
|
+
/** A checkpoint other than the one this turn was sent on means Cursor kept the turn. */
|
|
34
|
+
export declare function noteCheckpoint(sessionKey: string | undefined, bytes: Uint8Array): void;
|
|
35
|
+
export declare function hasLostRequest(sessionKey: string | undefined): boolean;
|
|
36
|
+
export declare function dropLostRequest(sessionKey: string | undefined): void;
|
|
37
|
+
export declare function serializeLostRequest(sessionKey: string | undefined): string | undefined;
|
|
38
|
+
export declare function restoreLostRequest(sessionKey: string, raw: string | undefined): void;
|
|
39
|
+
export declare function resetLostRequestsForTests(): void;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { processShared } from "./process-shared.js";
|
|
3
|
+
const MAX_RECORDS = 1_024;
|
|
4
|
+
const records = () => processShared("lost-request.v2", () => new Map());
|
|
5
|
+
export function checkpointKey(bytes) {
|
|
6
|
+
if (!bytes?.length)
|
|
7
|
+
return "none";
|
|
8
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Prepend a user message whose Run never received a newer checkpoint.
|
|
12
|
+
* A history rebuild already contains it, so that path drops the record.
|
|
13
|
+
* A retry is the previous attempt's live text again: it sends what that
|
|
14
|
+
* attempt sent. A host that sends the same note before every attempt must not
|
|
15
|
+
* pile it up. Any other text is a new request and is appended, even when it
|
|
16
|
+
* repeats an older one (red, blue, then red again).
|
|
17
|
+
*/
|
|
18
|
+
export function carryLostRequest(input) {
|
|
19
|
+
const map = records();
|
|
20
|
+
const record = map.get(input.sessionKey);
|
|
21
|
+
if (!record)
|
|
22
|
+
return input.text;
|
|
23
|
+
const key = checkpointKey(input.checkpoint);
|
|
24
|
+
if (input.rebuildFromHistory
|
|
25
|
+
|| record.conversationId !== input.conversationId
|
|
26
|
+
|| record.checkpointKey !== key) {
|
|
27
|
+
map.delete(input.sessionKey);
|
|
28
|
+
return input.text;
|
|
29
|
+
}
|
|
30
|
+
if (input.text === record.latest)
|
|
31
|
+
return record.text;
|
|
32
|
+
// The host already resends the lost text ahead of this one (OpenCode 1.x
|
|
33
|
+
// merges user messages left without a reply into one live turn).
|
|
34
|
+
if (input.text === record.text || input.text.startsWith(`${record.text}\n\n`))
|
|
35
|
+
return input.text;
|
|
36
|
+
map.delete(input.sessionKey);
|
|
37
|
+
return `${record.text}\n\n${input.text}`;
|
|
38
|
+
}
|
|
39
|
+
/** `text` is what this turn sends; `latest` is its own live text (defaults to `text`). */
|
|
40
|
+
export function rememberLostRequest(input) {
|
|
41
|
+
if (!input.sessionKey || !input.text || input.text === ".")
|
|
42
|
+
return;
|
|
43
|
+
setRecord(input.sessionKey, {
|
|
44
|
+
conversationId: input.conversationId,
|
|
45
|
+
checkpointKey: checkpointKey(input.checkpoint),
|
|
46
|
+
text: input.text,
|
|
47
|
+
latest: input.latest ?? input.text,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** A session that never sends another turn must not keep its record forever. */
|
|
51
|
+
function setRecord(sessionKey, record) {
|
|
52
|
+
const map = records();
|
|
53
|
+
map.delete(sessionKey);
|
|
54
|
+
map.set(sessionKey, record);
|
|
55
|
+
while (map.size > MAX_RECORDS) {
|
|
56
|
+
const oldest = map.keys().next().value;
|
|
57
|
+
if (oldest === undefined)
|
|
58
|
+
break;
|
|
59
|
+
map.delete(oldest);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** A checkpoint other than the one this turn was sent on means Cursor kept the turn. */
|
|
63
|
+
export function noteCheckpoint(sessionKey, bytes) {
|
|
64
|
+
if (!sessionKey)
|
|
65
|
+
return;
|
|
66
|
+
const map = records();
|
|
67
|
+
const record = map.get(sessionKey);
|
|
68
|
+
if (!record)
|
|
69
|
+
return;
|
|
70
|
+
if (checkpointKey(bytes) !== record.checkpointKey)
|
|
71
|
+
map.delete(sessionKey);
|
|
72
|
+
}
|
|
73
|
+
export function hasLostRequest(sessionKey) {
|
|
74
|
+
return !!sessionKey && records().has(sessionKey);
|
|
75
|
+
}
|
|
76
|
+
export function dropLostRequest(sessionKey) {
|
|
77
|
+
if (sessionKey)
|
|
78
|
+
records().delete(sessionKey);
|
|
79
|
+
}
|
|
80
|
+
export function serializeLostRequest(sessionKey) {
|
|
81
|
+
if (!sessionKey)
|
|
82
|
+
return undefined;
|
|
83
|
+
const record = records().get(sessionKey);
|
|
84
|
+
return record ? JSON.stringify(record) : undefined;
|
|
85
|
+
}
|
|
86
|
+
export function restoreLostRequest(sessionKey, raw) {
|
|
87
|
+
if (!raw || records().has(sessionKey))
|
|
88
|
+
return;
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = JSON.parse(raw);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (typeof parsed.conversationId !== "string"
|
|
97
|
+
|| typeof parsed.checkpointKey !== "string"
|
|
98
|
+
|| typeof parsed.text !== "string"
|
|
99
|
+
|| parsed.text.length === 0
|
|
100
|
+
|| (parsed.latest !== undefined && typeof parsed.latest !== "string"))
|
|
101
|
+
return;
|
|
102
|
+
setRecord(sessionKey, {
|
|
103
|
+
conversationId: parsed.conversationId,
|
|
104
|
+
checkpointKey: parsed.checkpointKey,
|
|
105
|
+
text: parsed.text,
|
|
106
|
+
latest: parsed.latest ?? parsed.text,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
export function resetLostRequestsForTests() {
|
|
110
|
+
records().clear();
|
|
111
|
+
}
|
package/dist/model-config.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ModelInfo, type ModelVariant } from "./models.js";
|
|
2
|
+
type ContextTier = "base" | "long" | number;
|
|
2
3
|
/**
|
|
3
4
|
* Display names shared by both a thinking and a non-thinking model. A thinking
|
|
4
5
|
* model with such a name needs a "Thinking" tag to disambiguate it from its
|
|
@@ -8,10 +9,20 @@ import { type ModelInfo, type ModelVariant } from "./models.js";
|
|
|
8
9
|
* tagged redundantly.
|
|
9
10
|
*/
|
|
10
11
|
export declare function thinkingSuffixBaseNames(models: ModelInfo[]): Set<string>;
|
|
12
|
+
/**
|
|
13
|
+
* models.dev-style family for a Cursor model id: the id without its version
|
|
14
|
+
* segments and context/speed suffixes (`claude-haiku-4-5` → `claude-haiku`,
|
|
15
|
+
* `gemini-3.8-flash` → `gemini-flash`, `gpt-5.6-luna` → `gpt-luna`).
|
|
16
|
+
* Kimi keeps its major generation (`kimi-k2.7-code` → `kimi-k2`). OpenCode
|
|
17
|
+
* picks its small title model by family and falls back to the session model when
|
|
18
|
+
* no entry has one. Cursor Auto (`default`) is not a model family.
|
|
19
|
+
*/
|
|
20
|
+
export declare function cursorModelFamily(id: string): string | undefined;
|
|
11
21
|
export declare function modelInfoToConfig(mi: ModelInfo, options?: {
|
|
12
22
|
thinkingSuffix?: boolean;
|
|
13
|
-
contextTier?:
|
|
23
|
+
contextTier?: ContextTier;
|
|
14
24
|
variants?: ModelVariant[];
|
|
15
25
|
fast?: boolean;
|
|
16
26
|
}): Record<string, any>;
|
|
17
27
|
export declare function modelsToConfig(models: ModelInfo[]): Record<string, any>;
|
|
28
|
+
export {};
|
package/dist/model-config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CURSOR_VARIANT_PARAMETERS_KEY, CURSOR_WIRE_MODEL_ID_KEY, parseCursorContextLimit, } from "./models.js";
|
|
1
|
+
import { CURSOR_VARIANT_PARAMETERS_KEY, CURSOR_WIRE_MODEL_ID_KEY, findDefaultVariant, parseCursorContextLimit, } from "./models.js";
|
|
2
2
|
import { applyCursorModelCost, hasCursorFastPricing } from "./pricing.js";
|
|
3
3
|
import { getDocumentedCursorModelContext, resolveCursorModelSupportsImages, } from "./model-metadata.js";
|
|
4
4
|
/**
|
|
@@ -91,7 +91,7 @@ function modelInfoVariants(mi, variants) {
|
|
|
91
91
|
// variant entry indistinguishable from the model entry in pickers that
|
|
92
92
|
// collapse them.
|
|
93
93
|
if (key === baseName && !usedKeys.has(key)) {
|
|
94
|
-
key = `${baseName}${tagDims(v.parameterValues)
|
|
94
|
+
key = `${baseName}${tagDims(v.parameterValues) || " default"}`;
|
|
95
95
|
}
|
|
96
96
|
else if (usedKeys.has(key)) {
|
|
97
97
|
key = `${sanitized}${tagDims(v.parameterValues)}`;
|
|
@@ -112,8 +112,23 @@ function isLongContextVariant(v) {
|
|
|
112
112
|
function isFastVariant(v) {
|
|
113
113
|
return v.parameterValues.some((p) => p.id === "fast" && p.value === "true");
|
|
114
114
|
}
|
|
115
|
+
function variantContext(v) {
|
|
116
|
+
return parseCursorContextLimit(v.parameterValues.find((p) => p.id === "context")?.value);
|
|
117
|
+
}
|
|
118
|
+
function isSeparateContext(mi, v) {
|
|
119
|
+
const context = variantContext(v);
|
|
120
|
+
return isLongContextVariant(v) ||
|
|
121
|
+
(v.isMaxMode === true && context !== undefined && context !== mi.maxContext);
|
|
122
|
+
}
|
|
123
|
+
function contextSuffix(context) {
|
|
124
|
+
return context % 1_000_000 === 0 ? `${context / 1_000_000}m` : `${context / 1_000}k`;
|
|
125
|
+
}
|
|
115
126
|
function variantsForTier(mi, tier) {
|
|
116
|
-
return mi.variants.filter((v) =>
|
|
127
|
+
return mi.variants.filter((v) => tier === "base"
|
|
128
|
+
? !isSeparateContext(mi, v)
|
|
129
|
+
: tier === "long"
|
|
130
|
+
? isLongContextVariant(v)
|
|
131
|
+
: isSeparateContext(mi, v) && variantContext(v) === tier);
|
|
117
132
|
}
|
|
118
133
|
/**
|
|
119
134
|
* Display names shared by both a thinking and a non-thinking model. A thinking
|
|
@@ -140,6 +155,27 @@ export function thinkingSuffixBaseNames(models) {
|
|
|
140
155
|
ambiguous.add(base);
|
|
141
156
|
return ambiguous;
|
|
142
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* models.dev-style family for a Cursor model id: the id without its version
|
|
160
|
+
* segments and context/speed suffixes (`claude-haiku-4-5` → `claude-haiku`,
|
|
161
|
+
* `gemini-3.8-flash` → `gemini-flash`, `gpt-5.6-luna` → `gpt-luna`).
|
|
162
|
+
* Kimi keeps its major generation (`kimi-k2.7-code` → `kimi-k2`). OpenCode
|
|
163
|
+
* picks its small title model by family and falls back to the session model when
|
|
164
|
+
* no entry has one. Cursor Auto (`default`) is not a model family.
|
|
165
|
+
*/
|
|
166
|
+
export function cursorModelFamily(id) {
|
|
167
|
+
if (id === "default")
|
|
168
|
+
return undefined;
|
|
169
|
+
const base = id.replace(/(?:-(?:1m|fast))+$/, "");
|
|
170
|
+
const kimi = /^kimi-(k\d+)(?:[.p]\d+)*(?:-code(?:-highspeed)?)?$/.exec(base);
|
|
171
|
+
if (kimi)
|
|
172
|
+
return `kimi-${kimi[1]}`;
|
|
173
|
+
const family = base
|
|
174
|
+
.split("-")
|
|
175
|
+
.filter((part) => !/^\d+(?:[.p]\d+)*$/.test(part))
|
|
176
|
+
.join("-");
|
|
177
|
+
return family || undefined;
|
|
178
|
+
}
|
|
143
179
|
export function modelInfoToConfig(mi, options = {}) {
|
|
144
180
|
const contextTier = options.contextTier ?? "base";
|
|
145
181
|
const variants = options.variants ?? variantsForTier(mi, contextTier);
|
|
@@ -150,6 +186,8 @@ export function modelInfoToConfig(mi, options = {}) {
|
|
|
150
186
|
name += " Fast";
|
|
151
187
|
if (contextTier === "long")
|
|
152
188
|
name += " 1M";
|
|
189
|
+
if (typeof contextTier === "number")
|
|
190
|
+
name += ` ${contextSuffix(contextTier).toUpperCase()}`;
|
|
153
191
|
// OpenCode's context limit is static per model entry, while Cursor's context
|
|
154
192
|
// tier is a variant parameter. Long-context choices are therefore emitted as
|
|
155
193
|
// separate OpenCode entries by modelsToConfig.
|
|
@@ -158,9 +196,12 @@ export function modelInfoToConfig(mi, options = {}) {
|
|
|
158
196
|
// live checkpoints report 256k for the base tier — the previous 200k
|
|
159
197
|
// fallback made the host context meter disagree with Cursor.
|
|
160
198
|
const fallbackContext = mi.id === "default" ? 256_000 : 200_000;
|
|
161
|
-
const context = contextTier === "
|
|
162
|
-
?
|
|
163
|
-
:
|
|
199
|
+
const context = typeof contextTier === "number"
|
|
200
|
+
? contextTier
|
|
201
|
+
: contextTier === "long"
|
|
202
|
+
? (variants.some(isLongContextVariant) ? 1_000_000
|
|
203
|
+
: (mi.maxContextForMaxMode ?? documentedContext?.maxContextForMaxMode ?? 1_000_000))
|
|
204
|
+
: (mi.maxContext ?? documentedContext?.maxContext ?? fallbackContext);
|
|
164
205
|
// OpenCode's overflow/compaction/UI use limit.context; generation and
|
|
165
206
|
// thinking budgets use limit.output. models.dev 1M peers advertise
|
|
166
207
|
// 64k–128k output — a tiny cap makes long-context sessions feel broken
|
|
@@ -182,13 +223,17 @@ export function modelInfoToConfig(mi, options = {}) {
|
|
|
182
223
|
output,
|
|
183
224
|
},
|
|
184
225
|
};
|
|
226
|
+
// Optional cache metadata may supply a family; the live AvailableModels
|
|
227
|
+
// schema has no family field. Empty metadata must not block derivation.
|
|
228
|
+
const reported = typeof mi.family === "string" ? mi.family.trim() : "";
|
|
229
|
+
const family = reported || cursorModelFamily(mi.id);
|
|
230
|
+
if (family)
|
|
231
|
+
config.family = family;
|
|
185
232
|
const variantConfig = modelInfoVariants(mi, variants);
|
|
186
233
|
if (variantConfig)
|
|
187
234
|
config.variants = variantConfig;
|
|
188
|
-
if (contextTier
|
|
189
|
-
const defaultVariant = contextTier
|
|
190
|
-
? (variants.find((v) => v.isDefaultMax) ?? variants[0])
|
|
191
|
-
: (variants.find((v) => v.isDefaultNonMax) ?? variants[0]);
|
|
235
|
+
if (contextTier !== "base" || options.fast) {
|
|
236
|
+
const defaultVariant = findDefaultVariant(mi, variants, contextTier !== "base") ?? variants[0];
|
|
192
237
|
if (defaultVariant) {
|
|
193
238
|
config.options = {
|
|
194
239
|
[CURSOR_WIRE_MODEL_ID_KEY]: mi.id,
|
|
@@ -218,11 +263,9 @@ function uniqueCatalogId(usedIds, modelId, suffix) {
|
|
|
218
263
|
}
|
|
219
264
|
let n = 2;
|
|
220
265
|
while (true) {
|
|
221
|
-
const candidate = suffix
|
|
222
|
-
? `${modelId}-
|
|
223
|
-
: suffix
|
|
224
|
-
? `${modelId}-1m-${n}-fast`
|
|
225
|
-
: `${modelId}-1m-${n}`;
|
|
266
|
+
const candidate = suffix !== "-fast" && suffix.endsWith("-fast")
|
|
267
|
+
? `${modelId}${suffix.slice(0, -5)}-${n}-fast`
|
|
268
|
+
: `${modelId}${suffix}-${n}`;
|
|
226
269
|
if (!usedIds.has(candidate)) {
|
|
227
270
|
usedIds.add(candidate);
|
|
228
271
|
return candidate;
|
|
@@ -238,13 +281,28 @@ export function modelsToConfig(models) {
|
|
|
238
281
|
const thinkingSuffix = !!m.supportsThinking && ambiguous.has(baseName(m));
|
|
239
282
|
const splitFast = hasCursorFastPricing(m.id);
|
|
240
283
|
const groups = [];
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
284
|
+
const extraContexts = new Set(m.variants
|
|
285
|
+
.filter((v) => isSeparateContext(m, v) && !isLongContextVariant(v))
|
|
286
|
+
.map(variantContext)
|
|
287
|
+
.filter((context) => context !== undefined));
|
|
288
|
+
const tiers = ["base", "long", ...extraContexts];
|
|
289
|
+
for (const contextTier of tiers) {
|
|
290
|
+
const tierSuffix = contextTier === "base" ? ""
|
|
291
|
+
: contextTier === "long" ? "-1m" : `-${contextSuffix(contextTier)}`;
|
|
292
|
+
const suffixFor = (fast) => `${tierSuffix}${fast ? "-fast" : ""}`;
|
|
293
|
+
const tierVariants = new Set(variantsForTier(m, contextTier));
|
|
294
|
+
// Base/Fast ids and labels are persisted by the host. Keep their pre-split
|
|
295
|
+
// non-1M variants available while dedicated entries supply accurate limits.
|
|
296
|
+
const advertised = contextTier === "base"
|
|
297
|
+
? m.variants.filter((variant) => !isLongContextVariant(variant))
|
|
298
|
+
: [...tierVariants];
|
|
299
|
+
for (const group of speedGroups(advertised, splitFast)) {
|
|
300
|
+
const tierGroup = group.variants.filter((variant) => tierVariants.has(variant));
|
|
244
301
|
groups.push({
|
|
245
302
|
contextTier,
|
|
246
303
|
fast: group.fast,
|
|
247
|
-
variants: group.variants,
|
|
304
|
+
variants: tierGroup.length > 0 ? tierGroup : group.variants,
|
|
305
|
+
advertisedVariants: group.variants,
|
|
248
306
|
suffix: suffixFor(group.fast),
|
|
249
307
|
});
|
|
250
308
|
}
|
|
@@ -266,17 +324,7 @@ export function modelsToConfig(models) {
|
|
|
266
324
|
variants: group.variants,
|
|
267
325
|
fast: group.fast,
|
|
268
326
|
});
|
|
269
|
-
|
|
270
|
-
const defaultVariant = (group.contextTier === "long"
|
|
271
|
-
? group.variants.find((v) => v.isDefaultMax)
|
|
272
|
-
: group.variants.find((v) => v.isDefaultNonMax)) ?? group.variants[0];
|
|
273
|
-
if (defaultVariant) {
|
|
274
|
-
config.options = {
|
|
275
|
-
[CURSOR_WIRE_MODEL_ID_KEY]: m.id,
|
|
276
|
-
[CURSOR_VARIANT_PARAMETERS_KEY]: defaultVariant.parameterValues.map((p) => ({ ...p })),
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
}
|
|
327
|
+
config.variants = modelInfoVariants(m, group.advertisedVariants);
|
|
280
328
|
out[catalogId] = applyCursorModelCost(pricingId, config);
|
|
281
329
|
}
|
|
282
330
|
}
|
package/dist/models.d.ts
CHANGED
|
@@ -12,6 +12,11 @@ export type ModelVariant = {
|
|
|
12
12
|
displayName: string;
|
|
13
13
|
isDefaultNonMax: boolean;
|
|
14
14
|
isDefaultMax: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Cursor's `AvailableModelVariant.is_max_mode`: this tuple runs only with
|
|
17
|
+
* request-level max mode. Absent when discovery omits the marker.
|
|
18
|
+
*/
|
|
19
|
+
isMaxMode?: boolean;
|
|
15
20
|
};
|
|
16
21
|
export type ModelInfo = {
|
|
17
22
|
id: string;
|
|
@@ -24,6 +29,11 @@ export type ModelInfo = {
|
|
|
24
29
|
/** Context window when max-mode is on (proto field 16). */
|
|
25
30
|
maxContextForMaxMode?: number;
|
|
26
31
|
supportsMaxMode?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* `AvailableModel.supports_non_max_mode`; `false` means every variant runs
|
|
34
|
+
* in max mode. Absent when discovery omits it.
|
|
35
|
+
*/
|
|
36
|
+
supportsNonMaxMode?: boolean;
|
|
27
37
|
variants: ModelVariant[];
|
|
28
38
|
};
|
|
29
39
|
export type ModelCache = {
|
|
@@ -45,17 +55,30 @@ export declare function normalizeModelCache(value: unknown): ModelCache | null;
|
|
|
45
55
|
*/
|
|
46
56
|
export declare function extractCursorVariantParameters(providerOptions: Record<string, unknown> | undefined): ModelParameterValue[] | undefined;
|
|
47
57
|
export declare function resolveCursorWireModelId(providerOptions: Record<string, unknown> | undefined, fallback: string): string;
|
|
48
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* True when resolved params select the long-context (1m) tier. Fallback for
|
|
60
|
+
* variants without Cursor's `isMaxMode` marker.
|
|
61
|
+
*/
|
|
49
62
|
export declare function paramsImplyMaxMode(params: ModelParameterValue[]): boolean;
|
|
50
63
|
/**
|
|
51
|
-
* Resolve the request-level max_mode bit.
|
|
52
|
-
*
|
|
64
|
+
* Resolve the request-level max_mode bit. Like Cursor CLI, a cataloged variant
|
|
65
|
+
* requires max mode when Cursor marks it `isMaxMode` (Grok 4.7's 500k tier as
|
|
66
|
+
* well as 1m tiers) or the model does not support non-max mode. An explicit
|
|
67
|
+
* variant pick owns the context tier; otherwise a standalone maxMode hint may
|
|
68
|
+
* select max mode.
|
|
53
69
|
*/
|
|
54
70
|
export declare function resolveVariantMaxMode(params: ModelParameterValue[], opts?: {
|
|
55
71
|
picked?: ModelParameterValue[];
|
|
56
72
|
maxMode?: boolean;
|
|
73
|
+
model?: ModelInfo;
|
|
57
74
|
}): boolean;
|
|
58
75
|
export declare function parseCursorContextLimit(value: unknown): number | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* Find Cursor's declared default within a catalog subset, retaining its effort:
|
|
78
|
+
* the declared variant, else its speed twin, else its twin in another context
|
|
79
|
+
* tier (a 500k entry when the declared max default is 1m).
|
|
80
|
+
*/
|
|
81
|
+
export declare function findDefaultVariant(model: ModelInfo, candidates: ModelVariant[], maxMode: boolean): ModelVariant | undefined;
|
|
59
82
|
export declare function isCacheFresh(cache: ModelCache, ttlMs?: number): boolean;
|
|
60
83
|
export declare function cacheFilePath(cacheDir: string): string;
|
|
61
84
|
export declare function readCache(cacheDir: string): Promise<ModelCache | null>;
|