cursor-opencode-provider 0.4.3 → 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 +387 -118
- 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 +40 -35
- package/dist/usage.js +199 -68
- package/package.json +1 -1
package/dist/usage.js
CHANGED
|
@@ -5,86 +5,217 @@ export function turnEndedCounter(te, key) {
|
|
|
5
5
|
? Math.trunc(value)
|
|
6
6
|
: 0;
|
|
7
7
|
}
|
|
8
|
+
export function cursorUsageCountersFromTurnEnded(te) {
|
|
9
|
+
return {
|
|
10
|
+
inputTokens: turnEndedCounter(te, "input_tokens"),
|
|
11
|
+
outputTokens: turnEndedCounter(te, "output_tokens"),
|
|
12
|
+
cacheRead: turnEndedCounter(te, "cache_read"),
|
|
13
|
+
cacheWrite: turnEndedCounter(te, "cache_write"),
|
|
14
|
+
reasoningTokens: turnEndedCounter(te, "reasoning_tokens"),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
8
17
|
/**
|
|
9
|
-
* Map
|
|
10
|
-
*
|
|
11
|
-
* for a Run with no prior tool boundary; multi-step TurnEnded is cumulative.
|
|
12
|
-
*
|
|
13
|
-
* Contract: `input_tokens` is non-cached input; cache fields are separate.
|
|
14
|
-
* Cursor's agent wire currently sends `cache_write=0` even when later turns
|
|
15
|
-
* report large `cache_read`; we still forward the counter when non-zero.
|
|
16
|
-
* If production semantics differ (input_tokens already total), adjust here.
|
|
18
|
+
* Map Cursor counters to AI SDK V3. Cursor's `input_tokens` already includes
|
|
19
|
+
* cache reads/writes, and `output_tokens` already includes reasoning.
|
|
17
20
|
*/
|
|
18
|
-
export function
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
21
|
+
export function buildLanguageModelV3UsageFromCounters(counters, options = {}) {
|
|
22
|
+
const rawInput = Math.max(0, Math.trunc(counters.inputTokens));
|
|
23
|
+
const rawOutput = Math.max(0, Math.trunc(counters.outputTokens));
|
|
24
|
+
const rawCacheRead = Math.min(Math.max(0, Math.trunc(counters.cacheRead)), rawInput);
|
|
25
|
+
const rawCacheWrite = Math.min(Math.max(0, Math.trunc(counters.cacheWrite)), rawInput - rawCacheRead);
|
|
26
|
+
const rawReasoning = Math.min(Math.max(0, Math.trunc(counters.reasoningTokens)), rawOutput);
|
|
27
|
+
const contextTotal = options.contextTotalTokens;
|
|
28
|
+
const hasContextTotal = typeof contextTotal === "number" && Number.isFinite(contextTotal) && contextTotal >= 0;
|
|
29
|
+
const total = hasContextTotal ? Math.trunc(contextTotal) : rawInput + rawOutput;
|
|
30
|
+
const output = Math.min(rawOutput, total);
|
|
31
|
+
const input = Math.max(0, total - output);
|
|
32
|
+
// TurnEnded can aggregate several internal model calls, so its absolute input
|
|
33
|
+
// and cache counts can exceed the final checkpoint occupancy. Preserve the
|
|
34
|
+
// cache proportions while normalizing the partition to Cursor's context total.
|
|
35
|
+
const cacheRead = rawInput > 0
|
|
36
|
+
? Math.min(input, Math.round(input * rawCacheRead / rawInput))
|
|
37
|
+
: 0;
|
|
38
|
+
const cacheWrite = rawInput > 0
|
|
39
|
+
? Math.min(input - cacheRead, Math.round(input * rawCacheWrite / rawInput))
|
|
40
|
+
: 0;
|
|
41
|
+
const reasoning = rawOutput > 0
|
|
42
|
+
? Math.min(output, Math.round(output * rawReasoning / rawOutput))
|
|
43
|
+
: 0;
|
|
24
44
|
return {
|
|
25
45
|
inputTokens: {
|
|
26
|
-
total:
|
|
27
|
-
noCache:
|
|
28
|
-
cacheRead
|
|
29
|
-
cacheWrite
|
|
46
|
+
total: input,
|
|
47
|
+
noCache: Math.max(input - cacheRead - cacheWrite, 0),
|
|
48
|
+
cacheRead,
|
|
49
|
+
cacheWrite,
|
|
30
50
|
},
|
|
31
51
|
outputTokens: {
|
|
32
|
-
total:
|
|
33
|
-
text:
|
|
34
|
-
reasoning
|
|
52
|
+
total: output,
|
|
53
|
+
text: Math.max(output - reasoning, 0),
|
|
54
|
+
reasoning,
|
|
35
55
|
},
|
|
36
56
|
};
|
|
37
57
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const trustCeiling = Math.max(requestLocal * CUMULATIVE_TURN_ENDED_RATIO, MIN_TRUSTED_TURN_ENDED_TOTAL);
|
|
60
|
-
return total > trustCeiling;
|
|
58
|
+
export function buildLanguageModelV3UsageFromTurnEnded(te, options = {}) {
|
|
59
|
+
return buildLanguageModelV3UsageFromCounters(cursorUsageCountersFromTurnEnded(te), options);
|
|
60
|
+
}
|
|
61
|
+
function usageCount(value) {
|
|
62
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
63
|
+
? Math.trunc(value)
|
|
64
|
+
: 0;
|
|
65
|
+
}
|
|
66
|
+
function usageRatio(part, total) {
|
|
67
|
+
return total > 0 ? `${(part / total * 100).toFixed(1)}%` : "n/a";
|
|
68
|
+
}
|
|
69
|
+
function categoryTokens(details) {
|
|
70
|
+
return new Map(details?.breakdown?.categories.map((category) => [
|
|
71
|
+
category.id || category.label || "(unnamed)",
|
|
72
|
+
category.estimatedTokens,
|
|
73
|
+
]) ?? []);
|
|
74
|
+
}
|
|
75
|
+
/** Compact, parseable category snapshot for checkpoint-by-checkpoint traces. */
|
|
76
|
+
export function formatCursorTokenCategories(details) {
|
|
77
|
+
const categories = Object.fromEntries(categoryTokens(details));
|
|
78
|
+
return Object.keys(categories).length > 0 ? JSON.stringify(categories) : "unavailable";
|
|
61
79
|
}
|
|
62
80
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* `est.outputTokens` accumulates model output across earlier tool boundaries.
|
|
67
|
-
* Move prior output into the current input estimate and report only this
|
|
68
|
-
* doStream request's output. Cache counters are intentionally absent: the
|
|
69
|
-
* only counters Cursor exposes at TurnEnded are cumulative for the whole Run.
|
|
81
|
+
* Explain Cursor's aggregate cache counters using the state visible to this
|
|
82
|
+
* client. `stepStarts` is deliberately not called a model-call count: Cursor
|
|
83
|
+
* does not expose per-model-call cache accounting on the Run stream.
|
|
70
84
|
*/
|
|
71
|
-
export function
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
export function formatCursorCacheDiagnostics(counters, current, prior, stats) {
|
|
86
|
+
const rawInput = Math.max(0, Math.trunc(counters.inputTokens));
|
|
87
|
+
const rawRead = Math.min(rawInput, Math.max(0, Math.trunc(counters.cacheRead)));
|
|
88
|
+
const rawWrite = Math.min(rawInput - rawRead, Math.max(0, Math.trunc(counters.cacheWrite)));
|
|
89
|
+
const rawUncached = Math.max(0, rawInput - rawRead - rawWrite);
|
|
90
|
+
const priorCategories = categoryTokens(prior);
|
|
91
|
+
const currentCategories = categoryTokens(current);
|
|
92
|
+
const categoriesComparable = !!prior?.breakdown && !!current?.breakdown;
|
|
93
|
+
const categoryDelta = {};
|
|
94
|
+
let sameSizedCategoryTokens = 0;
|
|
95
|
+
if (categoriesComparable) {
|
|
96
|
+
for (const id of new Set([...priorCategories.keys(), ...currentCategories.keys()])) {
|
|
97
|
+
const before = priorCategories.get(id);
|
|
98
|
+
const after = currentCategories.get(id);
|
|
99
|
+
if (before === undefined)
|
|
100
|
+
categoryDelta[id] = "new";
|
|
101
|
+
else if (after === undefined)
|
|
102
|
+
categoryDelta[id] = "removed";
|
|
103
|
+
else {
|
|
104
|
+
categoryDelta[id] = after - before;
|
|
105
|
+
if (after === before)
|
|
106
|
+
sameSizedCategoryTokens += after;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const continuity = stats.startedWithCheckpoint
|
|
111
|
+
? prior ? "warm" : "checkpoint-without-token-details"
|
|
112
|
+
: "cold";
|
|
113
|
+
const contextDelta = current && prior ? current.usedTokens - prior.usedTokens : undefined;
|
|
114
|
+
return [
|
|
115
|
+
"cache diagnosis:",
|
|
116
|
+
`sessionKey=${stats.sessionKey ?? "-"}`,
|
|
117
|
+
`conversationId=${stats.conversationId}`,
|
|
118
|
+
`conversationGroupId=${stats.conversationGroupId ?? "-"}`,
|
|
119
|
+
`model=${stats.modelId ?? "-"}`,
|
|
120
|
+
`continuity=${continuity}`,
|
|
121
|
+
`rawInput=${rawInput}`,
|
|
122
|
+
`rawCacheRead=${rawRead}`,
|
|
123
|
+
`rawCacheWrite=${rawWrite}`,
|
|
124
|
+
`rawUncached=${rawUncached}`,
|
|
125
|
+
`rawReadRatio=${usageRatio(rawRead, rawInput)}`,
|
|
126
|
+
`rawWriteRatio=${usageRatio(rawWrite, rawInput)}`,
|
|
127
|
+
`priorContext=${prior?.usedTokens ?? "unavailable"}`,
|
|
128
|
+
`currentContext=${current?.usedTokens ?? "unavailable"}`,
|
|
129
|
+
`contextDelta=${contextDelta ?? "unavailable"}`,
|
|
130
|
+
`rawReadVsPriorContext=${prior ? usageRatio(rawRead, prior.usedTokens) : "n/a"}`,
|
|
131
|
+
`sameSizedCategoryTokens=${categoriesComparable ? sameSizedCategoryTokens : "unavailable"}`,
|
|
132
|
+
`categoryDelta=${categoriesComparable && Object.keys(categoryDelta).length > 0 ? JSON.stringify(categoryDelta) : "unavailable"}`,
|
|
133
|
+
`requestContext=${stats.requestContextReused ? "reused" : "built"}`,
|
|
134
|
+
`requestContextHash=${stats.requestContextHash.slice(0, 16)}`,
|
|
135
|
+
`systemPromptHash=${stats.systemPromptHash?.slice(0, 16) ?? "none"}`,
|
|
136
|
+
`systemPromptSent=${!stats.startedWithCheckpoint}`,
|
|
137
|
+
`checkpointUpdates=${stats.checkpointUpdates}`,
|
|
138
|
+
`tokenDetailUpdates=${stats.tokenDetailUpdates}`,
|
|
139
|
+
`pumpPasses=${stats.pumpPasses}`,
|
|
140
|
+
`steps=${stats.stepStarts}/${stats.stepCompletes}`,
|
|
141
|
+
`displayToolCalls=${stats.displayToolCalls}`,
|
|
142
|
+
`execRequests=${stats.execRequests}`,
|
|
143
|
+
"perModelCallCache=unavailable",
|
|
144
|
+
].join(" ");
|
|
145
|
+
}
|
|
146
|
+
/** One-line proof that Cursor, AI SDK, and projected OpenCode totals agree. */
|
|
147
|
+
export function formatTurnUsageValidation(counters, usage, tokenDetails, contextSource) {
|
|
148
|
+
const input = usageCount(usage.inputTokens.total);
|
|
149
|
+
const noCache = usageCount(usage.inputTokens.noCache);
|
|
150
|
+
const cacheRead = usageCount(usage.inputTokens.cacheRead);
|
|
151
|
+
const cacheWrite = usageCount(usage.inputTokens.cacheWrite);
|
|
152
|
+
const inputParts = noCache + cacheRead + cacheWrite;
|
|
153
|
+
const output = usageCount(usage.outputTokens.total);
|
|
154
|
+
const text = usageCount(usage.outputTokens.text);
|
|
155
|
+
const reasoning = usageCount(usage.outputTokens.reasoning);
|
|
156
|
+
const outputParts = text + reasoning;
|
|
157
|
+
const sentTotal = input + output;
|
|
158
|
+
const projectedOpenCodeTotal = inputParts + outputParts;
|
|
159
|
+
const rawTotal = counters.inputTokens + counters.outputTokens;
|
|
160
|
+
const cursor = tokenDetails
|
|
161
|
+
? `${tokenDetails.usedTokens}/${tokenDetails.maxTokens}` +
|
|
162
|
+
`(${usageRatio(tokenDetails.usedTokens, tokenDetails.maxTokens)})`
|
|
163
|
+
: "unavailable";
|
|
164
|
+
const totalMatch = tokenDetails ? String(sentTotal === tokenDetails.usedTokens) : "unavailable";
|
|
165
|
+
const breakdown = tokenDetails?.breakdown;
|
|
166
|
+
const categorySum = breakdown?.categories.reduce((sum, category) => sum + category.estimatedTokens, 0);
|
|
167
|
+
const breakdownMatch = breakdown && categorySum !== undefined
|
|
168
|
+
? breakdown.totalUsedTokens === tokenDetails.usedTokens &&
|
|
169
|
+
categorySum === breakdown.totalUsedTokens
|
|
170
|
+
: undefined;
|
|
171
|
+
const rawCached = counters.cacheRead + counters.cacheWrite;
|
|
172
|
+
const sentCached = cacheRead + cacheWrite;
|
|
173
|
+
const cacheRatioMatch = tokenDetails
|
|
174
|
+
? counters.inputTokens > 0 && input > 0
|
|
175
|
+
? Math.abs(rawCached / counters.inputTokens - sentCached / input) <= 1 / input
|
|
176
|
+
: rawCached === 0 && sentCached === 0
|
|
177
|
+
: undefined;
|
|
178
|
+
const status = input === inputParts &&
|
|
179
|
+
output === outputParts &&
|
|
180
|
+
projectedOpenCodeTotal === sentTotal &&
|
|
181
|
+
(cacheRatioMatch ?? true) &&
|
|
182
|
+
(!tokenDetails || sentTotal === tokenDetails.usedTokens) &&
|
|
183
|
+
(breakdownMatch ?? true)
|
|
184
|
+
? "ok"
|
|
185
|
+
: "mismatch";
|
|
186
|
+
return [
|
|
187
|
+
"turn usage validation:",
|
|
188
|
+
`status=${status}`,
|
|
189
|
+
`source=${tokenDetails ? contextSource ?? "checkpoint-current-run" : "unavailable"}`,
|
|
190
|
+
`cursor=${cursor}`,
|
|
191
|
+
`rawTotal=${rawTotal}`,
|
|
192
|
+
`sentTotal=${sentTotal}`,
|
|
193
|
+
`totalMatch=${totalMatch}`,
|
|
194
|
+
`input=${input}`,
|
|
195
|
+
`inputParts=${inputParts}`,
|
|
196
|
+
`inputMatch=${input === inputParts}`,
|
|
197
|
+
`output=${output}`,
|
|
198
|
+
`outputParts=${outputParts}`,
|
|
199
|
+
`outputMatch=${output === outputParts}`,
|
|
200
|
+
`opencodeProjectedTotal=${projectedOpenCodeTotal}`,
|
|
201
|
+
`opencodeMatch=${projectedOpenCodeTotal === sentTotal}`,
|
|
202
|
+
`breakdownTotal=${breakdown?.totalUsedTokens ?? "unavailable"}`,
|
|
203
|
+
`categorySum=${categorySum ?? "unavailable"}`,
|
|
204
|
+
`breakdownMatch=${breakdownMatch ?? "unavailable"}`,
|
|
205
|
+
`rawCachedRatio=${usageRatio(rawCached, counters.inputTokens)}`,
|
|
206
|
+
`sentCachedRatio=${usageRatio(sentCached, input)}`,
|
|
207
|
+
`cacheRatioMatch=${cacheRatioMatch ?? "unavailable"}`,
|
|
208
|
+
].join(" ");
|
|
209
|
+
}
|
|
210
|
+
/** OpenCode requires a usage object at every step boundary. */
|
|
211
|
+
export function emptyLanguageModelV3Usage() {
|
|
212
|
+
return buildLanguageModelV3UsageFromCounters({
|
|
213
|
+
inputTokens: 0,
|
|
214
|
+
outputTokens: 0,
|
|
215
|
+
cacheRead: 0,
|
|
216
|
+
cacheWrite: 0,
|
|
217
|
+
reasoningTokens: 0,
|
|
218
|
+
});
|
|
88
219
|
}
|
|
89
220
|
/** Mirror Kilo `Session.getUsage` / AI SDK flat fields from V3 nested usage. */
|
|
90
221
|
export function kiloShapedUsageFromV3(usage) {
|