cursor-opencode-provider 0.4.2 → 0.5.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 +17 -12
- package/dist/context/build.d.ts +8 -0
- package/dist/context/build.js +72 -23
- package/dist/context/frozen.d.ts +29 -0
- package/dist/context/frozen.js +168 -0
- package/dist/context/index.d.ts +2 -1
- package/dist/context/index.js +2 -1
- package/dist/context/rules.d.ts +1 -0
- package/dist/context/rules.js +1 -1
- package/dist/language-model.d.ts +10 -9
- package/dist/language-model.js +390 -112
- package/dist/pricing-data.d.ts +6 -0
- package/dist/pricing-data.js +6 -0
- package/dist/pricing.d.ts +1 -1
- package/dist/pricing.js +1 -1
- package/dist/protocol/blob-reachability.d.ts +15 -0
- package/dist/protocol/blob-reachability.js +140 -0
- package/dist/protocol/blob-store.d.ts +37 -0
- package/dist/protocol/blob-store.js +115 -0
- package/dist/protocol/conversation-bind.d.ts +13 -0
- package/dist/protocol/conversation-bind.js +27 -1
- package/dist/protocol/conversation-persistence.d.ts +32 -0
- package/dist/protocol/conversation-persistence.js +504 -0
- package/dist/protocol/conversation-state.d.ts +16 -0
- package/dist/protocol/conversation-state.js +62 -0
- package/dist/protocol/kv.d.ts +1 -0
- package/dist/protocol/kv.js +2 -0
- package/dist/protocol/messages.d.ts +2 -0
- package/dist/protocol/messages.js +30 -2
- package/dist/protocol/request.d.ts +2 -0
- package/dist/protocol/request.js +2 -0
- package/dist/protocol/token-details.d.ts +33 -0
- package/dist/protocol/token-details.js +155 -0
- package/dist/protocol/tools.d.ts +17 -0
- package/dist/protocol/tools.js +76 -0
- package/dist/session.d.ts +39 -11
- package/dist/shared.d.ts +3 -0
- package/dist/shared.js +3 -0
- package/dist/usage.d.ts +42 -22
- package/dist/usage.js +200 -45
- package/package.json +2 -1
package/dist/protocol/request.js
CHANGED
|
@@ -76,6 +76,8 @@ export function buildRunRequest(input) {
|
|
|
76
76
|
});
|
|
77
77
|
const runRequest = {
|
|
78
78
|
conversation_id: input.conversationId,
|
|
79
|
+
conversation_group_id: input.conversationGroupId ?? input.conversationId,
|
|
80
|
+
run_id: msgId,
|
|
79
81
|
action,
|
|
80
82
|
requested_model: {
|
|
81
83
|
// The provider always selects a concrete model. Cursor's "default"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type CursorPromptTokenCategory = {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
estimatedTokens: number;
|
|
5
|
+
characterCount?: number;
|
|
6
|
+
};
|
|
7
|
+
export type CursorPromptTokenBreakdown = {
|
|
8
|
+
totalUsedTokens: number;
|
|
9
|
+
maxTokens: number;
|
|
10
|
+
categories: CursorPromptTokenCategory[];
|
|
11
|
+
};
|
|
12
|
+
export type CursorConversationTokenDetails = {
|
|
13
|
+
usedTokens: number;
|
|
14
|
+
maxTokens: number;
|
|
15
|
+
breakdown?: CursorPromptTokenBreakdown;
|
|
16
|
+
};
|
|
17
|
+
export type CursorContextUsageSource = "checkpoint-current-run" | "checkpoint-previous-turn";
|
|
18
|
+
/**
|
|
19
|
+
* Read field #5 (`ConversationTokenDetails`) from Cursor's opaque
|
|
20
|
+
* `ConversationStateStructure` checkpoint without decoding or re-encoding the
|
|
21
|
+
* rest of the state. Unknown checkpoint fields therefore remain byte-exact.
|
|
22
|
+
*/
|
|
23
|
+
export declare function decodeConversationTokenDetails(checkpoint: Uint8Array | undefined): CursorConversationTokenDetails | undefined;
|
|
24
|
+
export declare function cursorContextUsageMetadata(details: CursorConversationTokenDetails, source?: CursorContextUsageSource): {
|
|
25
|
+
contextUsageVersion: number;
|
|
26
|
+
source: CursorContextUsageSource;
|
|
27
|
+
stale: boolean;
|
|
28
|
+
usedTokens: number;
|
|
29
|
+
maxTokens: number;
|
|
30
|
+
remainingTokens: number;
|
|
31
|
+
usedPercent?: number;
|
|
32
|
+
breakdown?: CursorPromptTokenBreakdown;
|
|
33
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import protobuf from "protobufjs";
|
|
2
|
+
const MAX_BREAKDOWN_CATEGORIES = 128;
|
|
3
|
+
function requireWireType(actual, expected, field) {
|
|
4
|
+
if (actual !== expected)
|
|
5
|
+
throw new Error(`invalid ${field} wire type`);
|
|
6
|
+
}
|
|
7
|
+
function decodeCategory(data) {
|
|
8
|
+
const reader = protobuf.Reader.create(data);
|
|
9
|
+
let id = "";
|
|
10
|
+
let label = "";
|
|
11
|
+
let estimatedTokens = 0;
|
|
12
|
+
let characterCount;
|
|
13
|
+
while (reader.pos < reader.len) {
|
|
14
|
+
const tag = reader.uint32();
|
|
15
|
+
const wireType = tag & 7;
|
|
16
|
+
switch (tag >>> 3) {
|
|
17
|
+
case 1:
|
|
18
|
+
requireWireType(wireType, 2, "category id");
|
|
19
|
+
id = reader.string();
|
|
20
|
+
break;
|
|
21
|
+
case 2:
|
|
22
|
+
requireWireType(wireType, 2, "category label");
|
|
23
|
+
label = reader.string();
|
|
24
|
+
break;
|
|
25
|
+
case 3:
|
|
26
|
+
requireWireType(wireType, 0, "category token count");
|
|
27
|
+
estimatedTokens = reader.uint32();
|
|
28
|
+
break;
|
|
29
|
+
case 4:
|
|
30
|
+
requireWireType(wireType, 0, "category character count");
|
|
31
|
+
characterCount = reader.uint32();
|
|
32
|
+
break;
|
|
33
|
+
default:
|
|
34
|
+
reader.skipType(wireType);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
id,
|
|
39
|
+
label,
|
|
40
|
+
estimatedTokens,
|
|
41
|
+
...(characterCount === undefined ? {} : { characterCount }),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function decodeBreakdown(data) {
|
|
45
|
+
const reader = protobuf.Reader.create(data);
|
|
46
|
+
let totalUsedTokens = 0;
|
|
47
|
+
let maxTokens = 0;
|
|
48
|
+
const categories = [];
|
|
49
|
+
while (reader.pos < reader.len) {
|
|
50
|
+
const tag = reader.uint32();
|
|
51
|
+
const wireType = tag & 7;
|
|
52
|
+
switch (tag >>> 3) {
|
|
53
|
+
case 1:
|
|
54
|
+
requireWireType(wireType, 0, "breakdown used tokens");
|
|
55
|
+
totalUsedTokens = reader.uint32();
|
|
56
|
+
break;
|
|
57
|
+
case 2:
|
|
58
|
+
requireWireType(wireType, 0, "breakdown max tokens");
|
|
59
|
+
maxTokens = reader.uint32();
|
|
60
|
+
break;
|
|
61
|
+
case 3:
|
|
62
|
+
requireWireType(wireType, 2, "breakdown category");
|
|
63
|
+
if (categories.length < MAX_BREAKDOWN_CATEGORIES) {
|
|
64
|
+
categories.push(decodeCategory(reader.bytes()));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
reader.bytes();
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
default:
|
|
71
|
+
reader.skipType(wireType);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { totalUsedTokens, maxTokens, categories };
|
|
75
|
+
}
|
|
76
|
+
function decodeTokenDetails(data) {
|
|
77
|
+
const reader = protobuf.Reader.create(data);
|
|
78
|
+
let usedTokens = 0;
|
|
79
|
+
let maxTokens = 0;
|
|
80
|
+
let sawUsedTokens = false;
|
|
81
|
+
let sawMaxTokens = false;
|
|
82
|
+
let breakdown;
|
|
83
|
+
while (reader.pos < reader.len) {
|
|
84
|
+
const tag = reader.uint32();
|
|
85
|
+
const wireType = tag & 7;
|
|
86
|
+
switch (tag >>> 3) {
|
|
87
|
+
case 1:
|
|
88
|
+
requireWireType(wireType, 0, "used tokens");
|
|
89
|
+
usedTokens = reader.uint32();
|
|
90
|
+
sawUsedTokens = true;
|
|
91
|
+
break;
|
|
92
|
+
case 2:
|
|
93
|
+
requireWireType(wireType, 0, "max tokens");
|
|
94
|
+
maxTokens = reader.uint32();
|
|
95
|
+
sawMaxTokens = true;
|
|
96
|
+
break;
|
|
97
|
+
case 3:
|
|
98
|
+
requireWireType(wireType, 2, "token breakdown");
|
|
99
|
+
breakdown = decodeBreakdown(reader.bytes());
|
|
100
|
+
break;
|
|
101
|
+
default:
|
|
102
|
+
reader.skipType(wireType);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!sawUsedTokens && !sawMaxTokens && !breakdown)
|
|
106
|
+
return undefined;
|
|
107
|
+
return {
|
|
108
|
+
usedTokens,
|
|
109
|
+
maxTokens,
|
|
110
|
+
...(breakdown ? { breakdown } : {}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Read field #5 (`ConversationTokenDetails`) from Cursor's opaque
|
|
115
|
+
* `ConversationStateStructure` checkpoint without decoding or re-encoding the
|
|
116
|
+
* rest of the state. Unknown checkpoint fields therefore remain byte-exact.
|
|
117
|
+
*/
|
|
118
|
+
export function decodeConversationTokenDetails(checkpoint) {
|
|
119
|
+
if (!checkpoint?.length)
|
|
120
|
+
return undefined;
|
|
121
|
+
try {
|
|
122
|
+
const reader = protobuf.Reader.create(checkpoint);
|
|
123
|
+
let details;
|
|
124
|
+
while (reader.pos < reader.len) {
|
|
125
|
+
const tag = reader.uint32();
|
|
126
|
+
const wireType = tag & 7;
|
|
127
|
+
if ((tag >>> 3) === 5) {
|
|
128
|
+
requireWireType(wireType, 2, "conversation token details");
|
|
129
|
+
details = decodeTokenDetails(reader.bytes());
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
reader.skipType(wireType);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return details;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export function cursorContextUsageMetadata(details, source = "checkpoint-current-run") {
|
|
142
|
+
const usedPercent = details.maxTokens > 0
|
|
143
|
+
? Math.max(0, Math.min(100, Math.round(details.usedTokens / details.maxTokens * 1_000) / 10))
|
|
144
|
+
: undefined;
|
|
145
|
+
return {
|
|
146
|
+
contextUsageVersion: 2,
|
|
147
|
+
source,
|
|
148
|
+
stale: source === "checkpoint-previous-turn",
|
|
149
|
+
usedTokens: details.usedTokens,
|
|
150
|
+
maxTokens: details.maxTokens,
|
|
151
|
+
remainingTokens: Math.max(0, details.maxTokens - details.usedTokens),
|
|
152
|
+
...(usedPercent === undefined ? {} : { usedPercent }),
|
|
153
|
+
...(details.breakdown ? { breakdown: details.breakdown } : {}),
|
|
154
|
+
};
|
|
155
|
+
}
|
package/dist/protocol/tools.d.ts
CHANGED
|
@@ -89,6 +89,16 @@ export declare function resolveCursorSubagentType(subagentType: string, catalog?
|
|
|
89
89
|
export declare function mapCursorSubagentTypeToOpenCode(subagentType: string): string;
|
|
90
90
|
/** Resolve a native Cursor subagent request to this turn's executor and catalog. */
|
|
91
91
|
export declare function remapNativeSubagentForCatalog(parsed: ParsedExecRequest, advertisedToolNames: Iterable<string>, catalog?: HostSubagentCatalog): void;
|
|
92
|
+
/**
|
|
93
|
+
* Complete the private read inside Cursor's legacy edit transaction without
|
|
94
|
+
* routing it through OpenCode's user-facing `read` tool. The latter caps every
|
|
95
|
+
* result at 50 KB, while Cursor's editor needs the complete file to calculate
|
|
96
|
+
* the correlated whole-file `write_args` that follows.
|
|
97
|
+
*
|
|
98
|
+
* The caller is responsible for proving that `filePath` is the same regular
|
|
99
|
+
* file named by an active `edit_tool_call` and is contained by the workspace.
|
|
100
|
+
*/
|
|
101
|
+
export declare function buildCompleteEditReadMessages(execId: number, sourcePath: string, resultPath?: string): Uint8Array[] | undefined;
|
|
92
102
|
/**
|
|
93
103
|
* Preserve a legacy Cursor edit's intent when its internal executor follows
|
|
94
104
|
* `edit_tool_call` with a whole-file `write_args` request.
|
|
@@ -125,6 +135,13 @@ export type ParsedExecRequest = {
|
|
|
125
135
|
/** Original request values needed by typed Cursor result messages. */
|
|
126
136
|
resultMetadata?: Record<string, unknown>;
|
|
127
137
|
};
|
|
138
|
+
/**
|
|
139
|
+
* A partial-read notice is for Cursor's model, never for a file. Refuse only
|
|
140
|
+
* whole-file mutation forms that echo it. A targeted edit or Update File patch
|
|
141
|
+
* cannot truncate an unseen tail, and may legitimately edit source that quotes
|
|
142
|
+
* this provider's warning text.
|
|
143
|
+
*/
|
|
144
|
+
export declare function rejectPartialReadMutation(parsed: ParsedExecRequest): void;
|
|
128
145
|
export declare function parseExecServerMessage(msg: Record<string, unknown>): ParsedExecRequest | undefined;
|
|
129
146
|
/**
|
|
130
147
|
* Remap Cursor exec / MCP arg shapes onto OpenCode's Effect Schema keys.
|
package/dist/protocol/tools.js
CHANGED
|
@@ -506,6 +506,45 @@ export function remapNativeSubagentForCatalog(parsed, advertisedToolNames, catal
|
|
|
506
506
|
}
|
|
507
507
|
/** Matches Cursor's own pre-write read threshold (`local-exec` 52428800). */
|
|
508
508
|
const MAX_EDIT_SOURCE_BYTES = 50 * 1024 * 1024;
|
|
509
|
+
/**
|
|
510
|
+
* Complete the private read inside Cursor's legacy edit transaction without
|
|
511
|
+
* routing it through OpenCode's user-facing `read` tool. The latter caps every
|
|
512
|
+
* result at 50 KB, while Cursor's editor needs the complete file to calculate
|
|
513
|
+
* the correlated whole-file `write_args` that follows.
|
|
514
|
+
*
|
|
515
|
+
* The caller is responsible for proving that `filePath` is the same regular
|
|
516
|
+
* file named by an active `edit_tool_call` and is contained by the workspace.
|
|
517
|
+
*/
|
|
518
|
+
export function buildCompleteEditReadMessages(execId, sourcePath, resultPath = sourcePath) {
|
|
519
|
+
try {
|
|
520
|
+
const stat = fs.statSync(sourcePath);
|
|
521
|
+
if (!stat.isFile() || stat.size > MAX_EDIT_SOURCE_BYTES)
|
|
522
|
+
return undefined;
|
|
523
|
+
const content = fs.readFileSync(sourcePath, "utf8");
|
|
524
|
+
return [
|
|
525
|
+
encodeMessage("AgentClientMessage", {
|
|
526
|
+
exec_client_message: {
|
|
527
|
+
id: execId,
|
|
528
|
+
local_execution_time_ms: 0,
|
|
529
|
+
read_result: {
|
|
530
|
+
success: {
|
|
531
|
+
path: resultPath,
|
|
532
|
+
content,
|
|
533
|
+
total_lines: countLines(content),
|
|
534
|
+
file_size: stat.size,
|
|
535
|
+
truncated: false,
|
|
536
|
+
range_applied: false,
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
}),
|
|
541
|
+
buildExecStreamClose(execId),
|
|
542
|
+
];
|
|
543
|
+
}
|
|
544
|
+
catch {
|
|
545
|
+
return undefined;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
509
548
|
/** Return the number of non-overlapping occurrences, stopping after ambiguity. */
|
|
510
549
|
function replacementOccurrenceCount(source, needle) {
|
|
511
550
|
if (!needle)
|
|
@@ -709,6 +748,43 @@ export function remapEditToolsForCatalog(parsed, advertisedToolNames, workspaceR
|
|
|
709
748
|
// the requested path for the typed Cursor result.
|
|
710
749
|
parsed.resultMetadata = { ...parsed.resultMetadata, path: filePath };
|
|
711
750
|
}
|
|
751
|
+
/**
|
|
752
|
+
* A partial-read notice is for Cursor's model, never for a file. Refuse only
|
|
753
|
+
* whole-file mutation forms that echo it. A targeted edit or Update File patch
|
|
754
|
+
* cannot truncate an unseen tail, and may legitimately edit source that quotes
|
|
755
|
+
* this provider's warning text.
|
|
756
|
+
*/
|
|
757
|
+
export function rejectPartialReadMutation(parsed) {
|
|
758
|
+
if (parsed.localError)
|
|
759
|
+
return;
|
|
760
|
+
let content;
|
|
761
|
+
if (parsed.toolName === "write") {
|
|
762
|
+
content = parsed.args.content;
|
|
763
|
+
}
|
|
764
|
+
else if (parsed.toolName === APPLY_PATCH_TOOL) {
|
|
765
|
+
const patchText = parsed.args.patchText;
|
|
766
|
+
if (typeof patchText !== "string" || !patchText.includes("*** Add File:"))
|
|
767
|
+
return;
|
|
768
|
+
content = patchText;
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
if (typeof content !== "string"
|
|
774
|
+
|| !content.includes("[Partial read:")
|
|
775
|
+
|| !content.includes("It is NOT the complete file."))
|
|
776
|
+
return;
|
|
777
|
+
const filePath = typeof parsed.args.filePath === "string"
|
|
778
|
+
? parsed.args.filePath
|
|
779
|
+
: "the target file";
|
|
780
|
+
const nextOffset = /Continue with offset=(\d+)/.exec(content)?.[1];
|
|
781
|
+
parsed.localError =
|
|
782
|
+
`NO FILE CHANGE WAS MADE. Refusing a whole-file mutation of ${JSON.stringify(filePath)} ` +
|
|
783
|
+
"because it contains the provider's partial-read notice. Do not retry the same mutation. " +
|
|
784
|
+
(nextOffset
|
|
785
|
+
? `Read the file from offset=${nextOffset}, then use a targeted edit or Update File patch.`
|
|
786
|
+
: "Read the remaining file ranges, then use a targeted edit or Update File patch.");
|
|
787
|
+
}
|
|
712
788
|
export function parseExecServerMessage(msg) {
|
|
713
789
|
const id = msg.id;
|
|
714
790
|
if (id === undefined)
|
package/dist/session.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { BidiStream } from "./transport/connect.js";
|
|
2
2
|
import { type CursorProviderError } from "./errors.js";
|
|
3
3
|
import { type SessionActivitySource } from "./activity.js";
|
|
4
|
-
import type { HostSubagentCatalog, ToolAliasRegistry } from "./protocol/tools.js";
|
|
4
|
+
import type { HostSubagentCatalog, OpencodeToolDef, ToolAliasRegistry } from "./protocol/tools.js";
|
|
5
|
+
import type { CursorConversationTokenDetails } from "./protocol/token-details.js";
|
|
5
6
|
export type Frame = {
|
|
6
7
|
flags: number;
|
|
7
8
|
payload: Uint8Array;
|
|
@@ -99,10 +100,44 @@ export type CursorSession = {
|
|
|
99
100
|
* conversation_checkpoint_update (CLI parity).
|
|
100
101
|
*/
|
|
101
102
|
conversationId: string;
|
|
103
|
+
/** Cache root owning this conversation's durable restart state. */
|
|
104
|
+
cacheDir?: string;
|
|
102
105
|
/** Latest eligible checkpoint emitted by this Run attempt. */
|
|
103
106
|
resumeCheckpoint?: Uint8Array;
|
|
107
|
+
/** Last checkpoint-derived context snapshot, independent of retry eligibility. */
|
|
108
|
+
tokenDetails?: CursorConversationTokenDetails;
|
|
109
|
+
/** True only after this Run receives a checkpoint containing token details. */
|
|
110
|
+
tokenDetailsFresh?: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Run-lifetime cache evidence. Cursor exposes only aggregate cache counters at
|
|
113
|
+
* TurnEnded, so retain the inputs and protocol activity needed to explain
|
|
114
|
+
* whether a low ratio came from a cold/rebased prefix, context growth, or a
|
|
115
|
+
* multi-step agent Run.
|
|
116
|
+
*/
|
|
117
|
+
cacheDiagnostics?: {
|
|
118
|
+
sessionKey?: string;
|
|
119
|
+
conversationId: string;
|
|
120
|
+
conversationGroupId?: string;
|
|
121
|
+
modelId?: string;
|
|
122
|
+
priorTokenDetails?: CursorConversationTokenDetails;
|
|
123
|
+
startedWithCheckpoint: boolean;
|
|
124
|
+
requestContextReused: boolean;
|
|
125
|
+
requestContextHash: string;
|
|
126
|
+
systemPromptHash?: string;
|
|
127
|
+
checkpointUpdates: number;
|
|
128
|
+
tokenDetailUpdates: number;
|
|
129
|
+
pumpPasses: number;
|
|
130
|
+
stepStarts: number;
|
|
131
|
+
stepCompletes: number;
|
|
132
|
+
displayToolCalls: number;
|
|
133
|
+
execRequests: number;
|
|
134
|
+
};
|
|
104
135
|
/** OpenCode session whose own or descendant activity renews tool leases. */
|
|
105
136
|
openCodeSessionId?: string;
|
|
137
|
+
/** Completed compaction must rebase once before resuming a normal agent. */
|
|
138
|
+
postCompactionRebase?: boolean;
|
|
139
|
+
/** Last real host catalog, retained only as a lifecycle-turn fallback. */
|
|
140
|
+
toolCatalog?: OpencodeToolDef[];
|
|
106
141
|
stream: BidiStream;
|
|
107
142
|
frames: AsyncIterator<Frame>;
|
|
108
143
|
pending: Map<number, PendingExec>;
|
|
@@ -119,6 +154,7 @@ export type CursorSession = {
|
|
|
119
154
|
*/
|
|
120
155
|
editToolCalls?: Map<string, {
|
|
121
156
|
path: string;
|
|
157
|
+
completeRead?: boolean;
|
|
122
158
|
}>;
|
|
123
159
|
/** Monotonic synthetic exec ids for bridged (display-only) OpenCode tool calls. */
|
|
124
160
|
nextBridgedExecId: number;
|
|
@@ -139,9 +175,8 @@ export type CursorSession = {
|
|
|
139
175
|
*/
|
|
140
176
|
allowTools: boolean;
|
|
141
177
|
/**
|
|
142
|
-
* Best-effort
|
|
143
|
-
*
|
|
144
|
-
* tool-call finishes so OpenCode does not store all-zero usage mid-loop.
|
|
178
|
+
* Best-effort held-Run activity counters used only for diagnostics. AI SDK
|
|
179
|
+
* accounting comes from the request-local final TurnEnded, never this estimate.
|
|
145
180
|
*/
|
|
146
181
|
usageEstimate: {
|
|
147
182
|
inputTokens: number;
|
|
@@ -150,13 +185,6 @@ export type CursorSession = {
|
|
|
150
185
|
cacheWrite: number;
|
|
151
186
|
reasoningTokens: number;
|
|
152
187
|
};
|
|
153
|
-
/**
|
|
154
|
-
* True after this held Run has already crossed an OpenCode tool-call
|
|
155
|
-
* boundary. Cursor's eventual TurnEnded counters then cover the whole Run,
|
|
156
|
-
* not only the final doStream request, so they cannot be used as that
|
|
157
|
-
* request's context usage.
|
|
158
|
-
*/
|
|
159
|
-
hadToolCallBoundary?: boolean;
|
|
160
188
|
/**
|
|
161
189
|
* True while a doStream pull() is actively reading this session's frames.
|
|
162
190
|
* Prevents a late cancel/abort from a prior ReadableStream from destroying
|
package/dist/shared.d.ts
CHANGED
|
@@ -12,6 +12,9 @@ export declare const MODEL_CACHE_FILE = "cursor-models.json";
|
|
|
12
12
|
/** Bumped when the on-disk model cache shape/semantics change (forces refetch). */
|
|
13
13
|
export declare const MODEL_CACHE_SCHEMA_VERSION = 3;
|
|
14
14
|
export declare const MODEL_CACHE_TTL_MS = 86400000;
|
|
15
|
+
export declare const CONVERSATION_CACHE_DIR = "cursor-conversations";
|
|
16
|
+
export declare const CONVERSATION_CACHE_SCHEMA_VERSION = 3;
|
|
17
|
+
export declare const CONVERSATION_CACHE_TTL_MS = 86400000;
|
|
15
18
|
export declare const VERSION_CACHE_FILE = "cursor-client-version.json";
|
|
16
19
|
export declare const CONTENT_TYPE_CONNECT_PROTO = "application/connect+proto";
|
|
17
20
|
export declare const CONNECT_PROTOCOL_VERSION = "1";
|
package/dist/shared.js
CHANGED
|
@@ -12,6 +12,9 @@ export const MODEL_CACHE_FILE = "cursor-models.json";
|
|
|
12
12
|
/** Bumped when the on-disk model cache shape/semantics change (forces refetch). */
|
|
13
13
|
export const MODEL_CACHE_SCHEMA_VERSION = 3;
|
|
14
14
|
export const MODEL_CACHE_TTL_MS = 86_400_000;
|
|
15
|
+
export const CONVERSATION_CACHE_DIR = "cursor-conversations";
|
|
16
|
+
export const CONVERSATION_CACHE_SCHEMA_VERSION = 3;
|
|
17
|
+
export const CONVERSATION_CACHE_TTL_MS = 86_400_000;
|
|
15
18
|
export const VERSION_CACHE_FILE = "cursor-client-version.json";
|
|
16
19
|
export const CONTENT_TYPE_CONNECT_PROTO = "application/connect+proto";
|
|
17
20
|
export const CONNECT_PROTOCOL_VERSION = "1";
|
package/dist/usage.d.ts
CHANGED
|
@@ -1,34 +1,54 @@
|
|
|
1
1
|
import type { LanguageModelV3Usage } from "@ai-sdk/provider";
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
/**
|
|
5
|
-
* Map request-local Cursor TurnEnded counters to AI SDK V3 usage (Kilo/OpenCode
|
|
6
|
-
* flatten via input total = noCache + cacheRead + cacheWrite). Only use this
|
|
7
|
-
* for a Run with no prior tool boundary; multi-step TurnEnded is cumulative.
|
|
8
|
-
*
|
|
9
|
-
* Contract: `input_tokens` is non-cached input; cache fields are separate.
|
|
10
|
-
* Cursor's agent wire currently sends `cache_write=0` even when later turns
|
|
11
|
-
* report large `cache_read`; we still forward the counter when non-zero.
|
|
12
|
-
* If production semantics differ (input_tokens already total), adjust here.
|
|
13
|
-
*/
|
|
14
|
-
export declare function buildLanguageModelV3UsageFromTurnEnded(te: Record<string, unknown>): LanguageModelV3Usage;
|
|
15
|
-
export type CursorUsageEstimate = {
|
|
2
|
+
import type { CursorContextUsageSource, CursorConversationTokenDetails } from "./protocol/token-details.js";
|
|
3
|
+
export type CursorUsageCounters = {
|
|
16
4
|
inputTokens: number;
|
|
17
5
|
outputTokens: number;
|
|
18
6
|
cacheRead: number;
|
|
19
7
|
cacheWrite: number;
|
|
20
8
|
reasoningTokens: number;
|
|
21
9
|
};
|
|
10
|
+
export type CursorUsageOptions = {
|
|
11
|
+
/** Cursor checkpoint occupancy, including the current turn's output. */
|
|
12
|
+
contextTotalTokens?: number;
|
|
13
|
+
};
|
|
14
|
+
export type CursorCacheDiagnosticStats = {
|
|
15
|
+
sessionKey?: string;
|
|
16
|
+
conversationId: string;
|
|
17
|
+
conversationGroupId?: string;
|
|
18
|
+
modelId?: string;
|
|
19
|
+
startedWithCheckpoint: boolean;
|
|
20
|
+
requestContextReused: boolean;
|
|
21
|
+
requestContextHash: string;
|
|
22
|
+
systemPromptHash?: string;
|
|
23
|
+
checkpointUpdates: number;
|
|
24
|
+
tokenDetailUpdates: number;
|
|
25
|
+
pumpPasses: number;
|
|
26
|
+
stepStarts: number;
|
|
27
|
+
stepCompletes: number;
|
|
28
|
+
displayToolCalls: number;
|
|
29
|
+
execRequests: number;
|
|
30
|
+
};
|
|
31
|
+
/** Non-negative integer counter from a Cursor `turn_ended` field. */
|
|
32
|
+
export declare function turnEndedCounter(te: Record<string, unknown>, key: string): number;
|
|
33
|
+
export declare function cursorUsageCountersFromTurnEnded(te: Record<string, unknown>): CursorUsageCounters;
|
|
34
|
+
/**
|
|
35
|
+
* Map Cursor counters to AI SDK V3. Cursor's `input_tokens` already includes
|
|
36
|
+
* cache reads/writes, and `output_tokens` already includes reasoning.
|
|
37
|
+
*/
|
|
38
|
+
export declare function buildLanguageModelV3UsageFromCounters(counters: CursorUsageCounters, options?: CursorUsageOptions): LanguageModelV3Usage;
|
|
39
|
+
export declare function buildLanguageModelV3UsageFromTurnEnded(te: Record<string, unknown>, options?: CursorUsageOptions): LanguageModelV3Usage;
|
|
40
|
+
/** Compact, parseable category snapshot for checkpoint-by-checkpoint traces. */
|
|
41
|
+
export declare function formatCursorTokenCategories(details: CursorConversationTokenDetails | undefined): string;
|
|
22
42
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* `est.outputTokens` accumulates model output across earlier tool boundaries.
|
|
27
|
-
* Move prior output into the current input estimate and report only this
|
|
28
|
-
* doStream request's output. Cache counters are intentionally absent: the
|
|
29
|
-
* only counters Cursor exposes at TurnEnded are cumulative for the whole Run.
|
|
43
|
+
* Explain Cursor's aggregate cache counters using the state visible to this
|
|
44
|
+
* client. `stepStarts` is deliberately not called a model-call count: Cursor
|
|
45
|
+
* does not expose per-model-call cache accounting on the Run stream.
|
|
30
46
|
*/
|
|
31
|
-
export declare function
|
|
47
|
+
export declare function formatCursorCacheDiagnostics(counters: CursorUsageCounters, current: CursorConversationTokenDetails | undefined, prior: CursorConversationTokenDetails | undefined, stats: CursorCacheDiagnosticStats): string;
|
|
48
|
+
/** One-line proof that Cursor, AI SDK, and projected OpenCode totals agree. */
|
|
49
|
+
export declare function formatTurnUsageValidation(counters: CursorUsageCounters, usage: LanguageModelV3Usage, tokenDetails?: CursorConversationTokenDetails, contextSource?: CursorContextUsageSource): string;
|
|
50
|
+
/** OpenCode requires a usage object at every step boundary. */
|
|
51
|
+
export declare function emptyLanguageModelV3Usage(): LanguageModelV3Usage;
|
|
32
52
|
/** Mirror Kilo `Session.getUsage` / AI SDK flat fields from V3 nested usage. */
|
|
33
53
|
export declare function kiloShapedUsageFromV3(usage: LanguageModelV3Usage): {
|
|
34
54
|
inputTokens: number;
|