cursor-opencode-provider 0.6.0 → 0.6.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 +4 -0
- package/dist/context/plugins.js +1 -0
- package/dist/context/rules.js +1 -0
- package/dist/context/skills.js +1 -0
- package/dist/language-model.js +24 -11
- package/dist/protocol/conversation-bind.d.ts +1 -0
- package/dist/protocol/conversation-bind.js +3 -0
- package/dist/protocol/struct.js +1 -1
- package/dist/protocol/tools.js +3 -1
- package/dist/usage.d.ts +2 -0
- package/dist/usage.js +13 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -368,8 +368,12 @@ bun run typecheck # type-check without emit
|
|
|
368
368
|
bun test # run unit tests
|
|
369
369
|
bun run test:node-http2 # Node-specific HTTP/2 detach regression
|
|
370
370
|
bun run test:watch # watch mode
|
|
371
|
+
bun run generate:pricing # refresh src/pricing-data.ts from Cursor docs
|
|
372
|
+
bun run check:pricing # fixture coverage for known model ids
|
|
371
373
|
```
|
|
372
374
|
|
|
375
|
+
Before any version bump or `v*` tag, run `generate:pricing` and `check:pricing` and commit mapping/`pricing-data.ts` updates. Publish CI regenerates rates from the live docs; an unmapped Cursor display name fails the job.
|
|
376
|
+
|
|
373
377
|
## Architecture
|
|
374
378
|
|
|
375
379
|
```
|
package/dist/context/plugins.js
CHANGED
package/dist/context/rules.js
CHANGED
package/dist/context/skills.js
CHANGED
package/dist/language-model.js
CHANGED
|
@@ -625,12 +625,18 @@ async function startSession(modelId, token, callOptions, options, startOptions)
|
|
|
625
625
|
// Compaction must not reuse the prior conversation; its first normal turn
|
|
626
626
|
// must also rebase so the summary-agent checkpoint cannot replace the normal
|
|
627
627
|
// system prompt and OpenCode's newly compacted history.
|
|
628
|
+
const lifecycle = !allowTools && !isCompaction && !recovery;
|
|
628
629
|
let bound = resuming
|
|
629
630
|
? { conversationId: resumeRecovery.conversationId, reset: false, previousId: undefined }
|
|
630
|
-
: bindConversationId(sessionKey, {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
631
|
+
: bindConversationId(sessionKey, {
|
|
632
|
+
reset: resetState.reset || recovery?.kind === "rebase",
|
|
633
|
+
ephemeral: lifecycle,
|
|
634
|
+
});
|
|
635
|
+
let conversationState = lifecycle
|
|
636
|
+
? undefined
|
|
637
|
+
: resuming
|
|
638
|
+
? resumeRecovery.checkpoint
|
|
639
|
+
: (bound.reset ? undefined : getCheckpoint(bound.conversationId));
|
|
634
640
|
let checkpointGraph = conversationState
|
|
635
641
|
? inspectConversationBlobGraph(bound.conversationId, conversationState)
|
|
636
642
|
: { count: 0, bytes: 0, complete: true };
|
|
@@ -685,12 +691,13 @@ async function startSession(modelId, token, callOptions, options, startOptions)
|
|
|
685
691
|
const interactionGuidance = buildOpenCodeInteractionGuidance(cursorTools, isCompaction, workspaceRoot);
|
|
686
692
|
// After an approved SwitchMode, inject the Cursor CLI-shaped mode reminder
|
|
687
693
|
// (same <system_reminder> contract the CLI uses after flipping unifiedMode).
|
|
688
|
-
const
|
|
694
|
+
const startedWithCheckpoint = !!conversationState;
|
|
695
|
+
const modeReminder = isCompaction || startedWithCheckpoint || lifecycle
|
|
689
696
|
? undefined
|
|
690
697
|
: takeActiveCursorModeReminder(sessionKey, {
|
|
691
698
|
advertisedTools: cursorTools.map((tool) => tool.name),
|
|
692
699
|
});
|
|
693
|
-
const kickoffWarning = isCompaction
|
|
700
|
+
const kickoffWarning = isCompaction || startedWithCheckpoint || lifecycle
|
|
694
701
|
? undefined
|
|
695
702
|
: takePlanExecutionKickoffWarning(sessionKey);
|
|
696
703
|
const systemPrompt = [
|
|
@@ -898,7 +905,7 @@ async function startSession(modelId, token, callOptions, options, startOptions)
|
|
|
898
905
|
displayToolCalls: 0,
|
|
899
906
|
execRequests: 0,
|
|
900
907
|
},
|
|
901
|
-
openCodeSessionId: sessionKey,
|
|
908
|
+
openCodeSessionId: lifecycle ? undefined : sessionKey,
|
|
902
909
|
postCompactionRebase: isCompaction,
|
|
903
910
|
toolCatalog: snapshotToolCatalog(sessionKey),
|
|
904
911
|
stream,
|
|
@@ -1700,7 +1707,10 @@ export async function pump(session, controller, ids, abortSignal) {
|
|
|
1700
1707
|
const counters = te ? cursorUsageCountersFromTurnEnded(te) : undefined;
|
|
1701
1708
|
const usage = settledUsage ?? (te
|
|
1702
1709
|
? tokenDetails
|
|
1703
|
-
? buildLanguageModelV3UsageFromCounters(counters, {
|
|
1710
|
+
? buildLanguageModelV3UsageFromCounters(counters, {
|
|
1711
|
+
contextTotalTokens: tokenDetails.usedTokens,
|
|
1712
|
+
priorContextTokens: session.cacheDiagnostics?.priorTokenDetails?.usedTokens,
|
|
1713
|
+
})
|
|
1704
1714
|
: emptyLanguageModelV3Usage()
|
|
1705
1715
|
: emptyLanguageModelV3Usage());
|
|
1706
1716
|
const providerMetadata = te
|
|
@@ -2790,6 +2800,9 @@ export function spanEndParts(opts) {
|
|
|
2790
2800
|
out.push({ type: "text-end", id: opts.textId });
|
|
2791
2801
|
return out;
|
|
2792
2802
|
}
|
|
2803
|
+
function toolsInFixedOrder(tools) {
|
|
2804
|
+
return tools.map((tool) => ({ ...tool })).sort((left, right) => (left.name ?? "").localeCompare(right.name ?? ""));
|
|
2805
|
+
}
|
|
2793
2806
|
/** Exported for tests — false for compaction/summary (no tools) and toolChoice none. */
|
|
2794
2807
|
export function computeAllowTools(toolCount, toolChoice) {
|
|
2795
2808
|
return toolCount > 0 && toolChoice?.type !== "none";
|
|
@@ -2797,7 +2810,7 @@ export function computeAllowTools(toolCount, toolChoice) {
|
|
|
2797
2810
|
export async function resolveTurnToolState(input) {
|
|
2798
2811
|
const { sessionKey, incomingTools, isCompaction } = input;
|
|
2799
2812
|
if (sessionKey && incomingTools.length > 0) {
|
|
2800
|
-
rememberToolCatalog(sessionKey, incomingTools
|
|
2813
|
+
rememberToolCatalog(sessionKey, toolsInFixedOrder(incomingTools));
|
|
2801
2814
|
}
|
|
2802
2815
|
// Advertisement and permission are deliberately independent.
|
|
2803
2816
|
//
|
|
@@ -2818,12 +2831,12 @@ export async function resolveTurnToolState(input) {
|
|
|
2818
2831
|
// still refuse execution and cannot duplicate the real turn's side effects.
|
|
2819
2832
|
let advertisedTools;
|
|
2820
2833
|
if (incomingTools.length > 0) {
|
|
2821
|
-
advertisedTools = incomingTools;
|
|
2834
|
+
advertisedTools = toolsInFixedOrder(incomingTools);
|
|
2822
2835
|
}
|
|
2823
2836
|
else if (sessionKey) {
|
|
2824
2837
|
const cached = toolCatalogBySession.get(sessionKey)
|
|
2825
2838
|
?? await waitForSiblingToolCatalog(sessionKey, input.abortSignal);
|
|
2826
|
-
advertisedTools = cached
|
|
2839
|
+
advertisedTools = toolsInFixedOrder(cached);
|
|
2827
2840
|
}
|
|
2828
2841
|
else {
|
|
2829
2842
|
// Without a session key there is no safe sibling-correlation key. Preserve
|
|
@@ -24,6 +24,7 @@ export declare function peekConversationId(sessionKey: string): string;
|
|
|
24
24
|
*/
|
|
25
25
|
export declare function bindConversationId(sessionKey: string | undefined, opts?: {
|
|
26
26
|
reset?: boolean;
|
|
27
|
+
ephemeral?: boolean;
|
|
27
28
|
}): {
|
|
28
29
|
conversationId: string;
|
|
29
30
|
reset: boolean;
|
|
@@ -76,6 +76,9 @@ export function peekConversationId(sessionKey) {
|
|
|
76
76
|
* mint a new id so TurnEnded cache_read cannot keep overflowing OpenCode.
|
|
77
77
|
*/
|
|
78
78
|
export function bindConversationId(sessionKey, opts) {
|
|
79
|
+
if (opts?.ephemeral) {
|
|
80
|
+
return { conversationId: crypto.randomUUID(), reset: false };
|
|
81
|
+
}
|
|
79
82
|
if (!sessionKey) {
|
|
80
83
|
return { conversationId: crypto.randomUUID(), reset: !!opts?.reset };
|
|
81
84
|
}
|
package/dist/protocol/struct.js
CHANGED
|
@@ -249,7 +249,7 @@ export function encodeJsonAsValue(v) {
|
|
|
249
249
|
}
|
|
250
250
|
else if (typeof v === "object") {
|
|
251
251
|
const st = [];
|
|
252
|
-
for (const [key, val] of Object.entries(v)) {
|
|
252
|
+
for (const [key, val] of Object.entries(v).sort(([left], [right]) => left.localeCompare(right))) {
|
|
253
253
|
if (val === undefined)
|
|
254
254
|
continue;
|
|
255
255
|
const entry = [];
|
package/dist/protocol/tools.js
CHANGED
|
@@ -57,7 +57,9 @@ export function resolveToolServerIdentity(opencodeName, defaultServer = "opencod
|
|
|
57
57
|
* reconstructed in `mcpRealToolName`.
|
|
58
58
|
*/
|
|
59
59
|
export function toolsToDescriptors(tools, providerIdentifier = "opencode", knownMcpServers = []) {
|
|
60
|
-
return tools
|
|
60
|
+
return [...tools]
|
|
61
|
+
.sort((left, right) => left.name.localeCompare(right.name))
|
|
62
|
+
.map((t) => {
|
|
61
63
|
const id = resolveToolServerIdentity(t.sourceName ?? t.name, providerIdentifier, knownMcpServers);
|
|
62
64
|
const collisionSafeAlias = COLLISION_SAFE_ALIASES.has(t.name);
|
|
63
65
|
return {
|
package/dist/usage.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export type CursorUsageCounters = {
|
|
|
10
10
|
export type CursorUsageOptions = {
|
|
11
11
|
/** Cursor checkpoint occupancy, including the current turn's output. */
|
|
12
12
|
contextTotalTokens?: number;
|
|
13
|
+
/** Previous turn's occupancy. When Cursor's cache read covers this window, do not dilute the hit by multi-step TurnEnded aggregates. */
|
|
14
|
+
priorContextTokens?: number;
|
|
13
15
|
};
|
|
14
16
|
export type CursorCacheDiagnosticStats = {
|
|
15
17
|
sessionKey?: string;
|
package/dist/usage.js
CHANGED
|
@@ -32,9 +32,17 @@ export function buildLanguageModelV3UsageFromCounters(counters, options = {}) {
|
|
|
32
32
|
// TurnEnded can aggregate several internal model calls, so its absolute input
|
|
33
33
|
// and cache counts can exceed the final checkpoint occupancy. Preserve the
|
|
34
34
|
// cache proportions while normalizing the partition to Cursor's context total.
|
|
35
|
-
const
|
|
35
|
+
const proportionalRead = rawInput > 0
|
|
36
36
|
? Math.min(input, Math.round(input * rawCacheRead / rawInput))
|
|
37
37
|
: 0;
|
|
38
|
+
const priorContext = options.priorContextTokens;
|
|
39
|
+
const prefixRead = typeof priorContext === "number"
|
|
40
|
+
&& Number.isFinite(priorContext)
|
|
41
|
+
&& priorContext > 0
|
|
42
|
+
&& rawCacheRead >= priorContext
|
|
43
|
+
? Math.min(input, Math.trunc(priorContext))
|
|
44
|
+
: 0;
|
|
45
|
+
const cacheRead = Math.min(input, Math.max(proportionalRead, prefixRead));
|
|
38
46
|
const cacheWrite = rawInput > 0
|
|
39
47
|
? Math.min(input - cacheRead, Math.round(input * rawCacheWrite / rawInput))
|
|
40
48
|
: 0;
|
|
@@ -170,9 +178,13 @@ export function formatTurnUsageValidation(counters, usage, tokenDetails, context
|
|
|
170
178
|
: undefined;
|
|
171
179
|
const rawCached = counters.cacheRead + counters.cacheWrite;
|
|
172
180
|
const sentCached = cacheRead + cacheWrite;
|
|
181
|
+
const proportionalCached = counters.inputTokens > 0 && input > 0
|
|
182
|
+
? Math.round(input * rawCached / counters.inputTokens)
|
|
183
|
+
: 0;
|
|
173
184
|
const cacheRatioMatch = tokenDetails
|
|
174
185
|
? counters.inputTokens > 0 && input > 0
|
|
175
186
|
? Math.abs(rawCached / counters.inputTokens - sentCached / input) <= 1 / input
|
|
187
|
+
|| (sentCached >= proportionalCached && sentCached <= input)
|
|
176
188
|
: rawCached === 0 && sentCached === 0
|
|
177
189
|
: undefined;
|
|
178
190
|
const status = input === inputParts &&
|