cursor-opencode-provider 0.4.3 → 0.6.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 +33 -23
- package/dist/context/agents.d.ts +1 -1
- package/dist/context/agents.js +1 -1
- package/dist/context/auth-store.js +2 -2
- package/dist/context/build.d.ts +11 -1
- package/dist/context/build.js +76 -25
- package/dist/context/frozen.d.ts +29 -0
- package/dist/context/frozen.js +168 -0
- package/dist/context/index.d.ts +3 -2
- package/dist/context/index.js +3 -2
- package/dist/context/paths.d.ts +34 -53
- package/dist/context/paths.js +54 -118
- package/dist/context/rules.d.ts +1 -0
- package/dist/context/rules.js +2 -2
- package/dist/image-save-tool.d.ts +19 -0
- package/dist/image-save-tool.js +25 -0
- package/dist/image-save.d.ts +58 -0
- package/dist/image-save.js +189 -0
- package/dist/image-staging.d.ts +59 -0
- package/dist/image-staging.js +91 -0
- package/dist/index.d.ts +1 -1
- package/dist/language-model.d.ts +13 -11
- package/dist/language-model.js +957 -150
- package/dist/plan-execution-kickoff.d.ts +55 -0
- package/dist/plan-execution-kickoff.js +162 -0
- package/dist/plugin-opencode2.js +52 -3
- package/dist/plugin-v2.d.ts +11 -9
- package/dist/plugin-v2.js +6 -13
- package/dist/plugin.d.ts +9 -0
- package/dist/plugin.js +61 -5
- package/dist/pricing-data.d.ts +16 -4
- package/dist/pricing-data.js +16 -4
- package/dist/protocol/ask-question.d.ts +133 -0
- package/dist/protocol/ask-question.js +320 -0
- 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/create-plan.d.ts +141 -0
- package/dist/protocol/create-plan.js +373 -0
- package/dist/protocol/generate-image.d.ts +54 -0
- package/dist/protocol/generate-image.js +101 -0
- package/dist/protocol/interactions.d.ts +110 -8
- package/dist/protocol/interactions.js +295 -35
- 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 +148 -8
- package/dist/protocol/request.d.ts +2 -0
- package/dist/protocol/request.js +2 -0
- package/dist/protocol/switch-mode.d.ts +178 -0
- package/dist/protocol/switch-mode.js +417 -0
- package/dist/protocol/token-details.d.ts +33 -0
- package/dist/protocol/token-details.js +155 -0
- package/dist/protocol/tool-call-bridge.js +10 -5
- package/dist/protocol/tools.d.ts +44 -11
- package/dist/protocol/tools.js +171 -41
- 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 -37
- package/dist/usage.js +201 -70
- package/dist/web-search-tool.d.ts +22 -26
- package/dist/web-search-tool.js +25 -15
- package/package.json +7 -4
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
* fails ("Unexpected token ... is not valid JSON").
|
|
7
7
|
*
|
|
8
8
|
* This store is keyed by conversation_id and survives across Run streams.
|
|
9
|
+
* At a successful TurnEnded it is compacted to the graph reachable from the
|
|
10
|
+
* latest checkpoint, matching Cursor CLI's conversation-export traversal.
|
|
9
11
|
*/
|
|
12
|
+
import { collectReachableConversationBlobIds } from "./blob-reachability.js";
|
|
10
13
|
function hex(b) {
|
|
11
14
|
let s = "";
|
|
12
15
|
for (let i = 0; i < b.length; i++)
|
|
@@ -14,6 +17,12 @@ function hex(b) {
|
|
|
14
17
|
return s;
|
|
15
18
|
}
|
|
16
19
|
const byConversation = new Map();
|
|
20
|
+
function retainedBlobStats(current) {
|
|
21
|
+
let bytes = 0;
|
|
22
|
+
for (const data of current.values())
|
|
23
|
+
bytes += data.length;
|
|
24
|
+
return { count: current.size, bytes };
|
|
25
|
+
}
|
|
17
26
|
function bucket(conversationId) {
|
|
18
27
|
let m = byConversation.get(conversationId);
|
|
19
28
|
if (!m) {
|
|
@@ -34,6 +43,112 @@ export function getConversationBlob(conversationId, blobId) {
|
|
|
34
43
|
export function conversationBlobCount(conversationId) {
|
|
35
44
|
return byConversation.get(conversationId)?.size ?? 0;
|
|
36
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Measure the checkpoint-reachable blob graph without mutating the store.
|
|
48
|
+
* Incomplete graphs conservatively report the complete retained bucket, since
|
|
49
|
+
* that is what the KV channel may have to serve if the checkpoint is used.
|
|
50
|
+
*/
|
|
51
|
+
export function inspectConversationBlobGraph(conversationId, checkpoint) {
|
|
52
|
+
const current = byConversation.get(conversationId) ?? new Map();
|
|
53
|
+
const retainedStats = (fallbackReason) => ({
|
|
54
|
+
...retainedBlobStats(current),
|
|
55
|
+
complete: fallbackReason === undefined,
|
|
56
|
+
...(fallbackReason ? { fallbackReason } : {}),
|
|
57
|
+
});
|
|
58
|
+
if (!checkpoint?.length)
|
|
59
|
+
return retainedStats("checkpoint unavailable");
|
|
60
|
+
try {
|
|
61
|
+
const reachable = collectReachableConversationBlobIds(checkpoint, (id) => {
|
|
62
|
+
const stored = current.get(hex(id));
|
|
63
|
+
if (stored)
|
|
64
|
+
return stored;
|
|
65
|
+
return isBlobIdHash(id) ? undefined : id;
|
|
66
|
+
});
|
|
67
|
+
let count = 0;
|
|
68
|
+
let bytes = 0;
|
|
69
|
+
for (const id of reachable) {
|
|
70
|
+
const data = current.get(id);
|
|
71
|
+
if (!data)
|
|
72
|
+
continue;
|
|
73
|
+
count++;
|
|
74
|
+
bytes += data.length;
|
|
75
|
+
}
|
|
76
|
+
return { count, bytes, complete: true };
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
return retainedStats(error instanceof Error ? error.message : String(error));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Copy all durable blobs so a completed turn can be persisted atomically. */
|
|
83
|
+
export function snapshotConversationBlobs(conversationId) {
|
|
84
|
+
return [...(byConversation.get(conversationId) ?? [])].map(([id, data]) => ({
|
|
85
|
+
id,
|
|
86
|
+
data: Uint8Array.from(data),
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Keep only blobs reachable from the latest checkpoint and snapshot them.
|
|
91
|
+
* Malformed/unknown state or a missing referenced hash falls back to retaining
|
|
92
|
+
* every blob: cache size must never come at the cost of restart correctness.
|
|
93
|
+
*/
|
|
94
|
+
export function compactConversationBlobs(conversationId, checkpoint) {
|
|
95
|
+
const current = byConversation.get(conversationId) ?? new Map();
|
|
96
|
+
const before = retainedBlobStats(current);
|
|
97
|
+
const beforeCount = before.count;
|
|
98
|
+
const beforeBytes = before.bytes;
|
|
99
|
+
const fallback = (reason) => ({
|
|
100
|
+
blobs: snapshotConversationBlobs(conversationId),
|
|
101
|
+
beforeCount,
|
|
102
|
+
beforeBytes,
|
|
103
|
+
afterCount: beforeCount,
|
|
104
|
+
afterBytes: beforeBytes,
|
|
105
|
+
compacted: false,
|
|
106
|
+
fallbackReason: reason,
|
|
107
|
+
});
|
|
108
|
+
if (!checkpoint?.length)
|
|
109
|
+
return fallback("checkpoint unavailable");
|
|
110
|
+
try {
|
|
111
|
+
const reachable = collectReachableConversationBlobIds(checkpoint, (id) => {
|
|
112
|
+
const stored = current.get(hex(id));
|
|
113
|
+
if (stored)
|
|
114
|
+
return stored;
|
|
115
|
+
// Cursor occasionally uses short literal content as its own id.
|
|
116
|
+
return isBlobIdHash(id) ? undefined : id;
|
|
117
|
+
});
|
|
118
|
+
const retained = new Map([...current].filter(([id]) => reachable.has(id)));
|
|
119
|
+
if (retained.size > 0)
|
|
120
|
+
byConversation.set(conversationId, retained);
|
|
121
|
+
else
|
|
122
|
+
byConversation.delete(conversationId);
|
|
123
|
+
const afterBytes = [...retained.values()].reduce((total, data) => total + data.length, 0);
|
|
124
|
+
return {
|
|
125
|
+
blobs: snapshotConversationBlobs(conversationId),
|
|
126
|
+
beforeCount,
|
|
127
|
+
beforeBytes,
|
|
128
|
+
afterCount: retained.size,
|
|
129
|
+
afterBytes,
|
|
130
|
+
compacted: true,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
return fallback(error instanceof Error ? error.message : String(error));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** Replace the in-memory blob bucket when restoring a persisted conversation. */
|
|
138
|
+
export function restoreConversationBlobs(conversationId, blobs) {
|
|
139
|
+
if (!conversationId)
|
|
140
|
+
return;
|
|
141
|
+
const restored = new Map();
|
|
142
|
+
for (const blob of blobs) {
|
|
143
|
+
if (!/^(?:[0-9a-f]{2})+$/i.test(blob.id))
|
|
144
|
+
continue;
|
|
145
|
+
restored.set(blob.id.toLowerCase(), Uint8Array.from(blob.data));
|
|
146
|
+
}
|
|
147
|
+
if (restored.size > 0)
|
|
148
|
+
byConversation.set(conversationId, restored);
|
|
149
|
+
else
|
|
150
|
+
byConversation.delete(conversationId);
|
|
151
|
+
}
|
|
37
152
|
/** Drop all blobs for a conversation (compaction conversation reset). */
|
|
38
153
|
export function clearConversationBlobs(conversationId) {
|
|
39
154
|
byConversation.delete(conversationId);
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
export declare const MAX_ACTIVE_CONVERSATION_BINDINGS = 256;
|
|
2
2
|
export declare function resetConversationBindingsForTests(): void;
|
|
3
|
+
export declare function hasConversationBinding(sessionKey: string): boolean;
|
|
4
|
+
export declare function isActiveConversationBinding(sessionKey: string, conversationId: string): boolean;
|
|
5
|
+
/** Restore a validated durable binding before resolving the next Run. */
|
|
6
|
+
export declare function restoreConversationBinding(sessionKey: string, conversationId: string): void;
|
|
3
7
|
/** Deterministic UUID (version-4 shape) from an arbitrary session key. */
|
|
4
8
|
export declare function sessionIdToUuid(sessionId: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Stable Cursor conversation group for one OpenCode session.
|
|
11
|
+
*
|
|
12
|
+
* Individual conversation ids are reminted for compaction/rebase boundaries,
|
|
13
|
+
* while the native Cursor CLI keeps the enclosing agent-store group stable.
|
|
14
|
+
* With no host session identity, the current conversation is the only safe
|
|
15
|
+
* grouping scope available.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveConversationGroupId(sessionKey: string | undefined, conversationId: string): string;
|
|
5
18
|
/** Current Cursor conversation_id for an OpenCode session key, creating the default binding if needed. */
|
|
6
19
|
export declare function peekConversationId(sessionKey: string): string;
|
|
7
20
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { clearCheckpoint } from "./checkpoint.js";
|
|
3
3
|
import { clearConversationBlobs } from "./blob-store.js";
|
|
4
|
+
import { clearFrozenRequestContext, transferFrozenRequestContext, } from "../context/frozen.js";
|
|
4
5
|
/**
|
|
5
6
|
* OpenCode session key → active Cursor conversation_id.
|
|
6
7
|
*
|
|
@@ -22,11 +23,24 @@ function rememberConversation(sessionKey, conversationId) {
|
|
|
22
23
|
activeBySession.delete(oldest[0]);
|
|
23
24
|
clearCheckpoint(oldest[1]);
|
|
24
25
|
clearConversationBlobs(oldest[1]);
|
|
26
|
+
clearFrozenRequestContext(oldest[1]);
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
export function resetConversationBindingsForTests() {
|
|
28
30
|
activeBySession.clear();
|
|
29
31
|
}
|
|
32
|
+
export function hasConversationBinding(sessionKey) {
|
|
33
|
+
return activeBySession.has(sessionKey);
|
|
34
|
+
}
|
|
35
|
+
export function isActiveConversationBinding(sessionKey, conversationId) {
|
|
36
|
+
return activeBySession.get(sessionKey) === conversationId;
|
|
37
|
+
}
|
|
38
|
+
/** Restore a validated durable binding before resolving the next Run. */
|
|
39
|
+
export function restoreConversationBinding(sessionKey, conversationId) {
|
|
40
|
+
if (!sessionKey || !conversationId)
|
|
41
|
+
return;
|
|
42
|
+
rememberConversation(sessionKey, conversationId);
|
|
43
|
+
}
|
|
30
44
|
/** Deterministic UUID (version-4 shape) from an arbitrary session key. */
|
|
31
45
|
export function sessionIdToUuid(sessionId) {
|
|
32
46
|
const hash = createHash("sha256").update(`cursor-opencode-provider:conv:${sessionId}`).digest();
|
|
@@ -36,6 +50,17 @@ export function sessionIdToUuid(sessionId) {
|
|
|
36
50
|
const hex = bytes.toString("hex");
|
|
37
51
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
38
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Stable Cursor conversation group for one OpenCode session.
|
|
55
|
+
*
|
|
56
|
+
* Individual conversation ids are reminted for compaction/rebase boundaries,
|
|
57
|
+
* while the native Cursor CLI keeps the enclosing agent-store group stable.
|
|
58
|
+
* With no host session identity, the current conversation is the only safe
|
|
59
|
+
* grouping scope available.
|
|
60
|
+
*/
|
|
61
|
+
export function resolveConversationGroupId(sessionKey, conversationId) {
|
|
62
|
+
return sessionKey ? sessionIdToUuid(sessionKey) : conversationId;
|
|
63
|
+
}
|
|
39
64
|
/** Current Cursor conversation_id for an OpenCode session key, creating the default binding if needed. */
|
|
40
65
|
export function peekConversationId(sessionKey) {
|
|
41
66
|
let id = activeBySession.get(sessionKey);
|
|
@@ -56,9 +81,10 @@ export function bindConversationId(sessionKey, opts) {
|
|
|
56
81
|
}
|
|
57
82
|
if (opts?.reset) {
|
|
58
83
|
const previousId = activeBySession.get(sessionKey) ?? sessionIdToUuid(sessionKey);
|
|
84
|
+
const conversationId = crypto.randomUUID();
|
|
59
85
|
clearCheckpoint(previousId);
|
|
60
86
|
clearConversationBlobs(previousId);
|
|
61
|
-
|
|
87
|
+
transferFrozenRequestContext(previousId, conversationId);
|
|
62
88
|
rememberConversation(sessionKey, conversationId);
|
|
63
89
|
return { conversationId, reset: true, previousId };
|
|
64
90
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ConversationBlobSnapshot } from "./blob-store.js";
|
|
2
|
+
import type { OpencodeToolDef } from "./tools.js";
|
|
3
|
+
export type PersistedConversation = {
|
|
4
|
+
sessionKey: string;
|
|
5
|
+
conversationId: string;
|
|
6
|
+
updatedAt: number;
|
|
7
|
+
checkpoint?: Uint8Array;
|
|
8
|
+
blobs: ConversationBlobSnapshot[];
|
|
9
|
+
requestContext: Record<string, unknown>;
|
|
10
|
+
toolCatalog: OpencodeToolDef[];
|
|
11
|
+
postCompactionRebase: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type ConversationLoadStatus = "restored" | "missing" | "invalid" | "expired";
|
|
14
|
+
export type PersistedConversationLoad = {
|
|
15
|
+
status: ConversationLoadStatus;
|
|
16
|
+
value?: PersistedConversation;
|
|
17
|
+
};
|
|
18
|
+
export declare function conversationCacheDirectoryPath(cacheDir: string): string;
|
|
19
|
+
export declare function conversationCacheFilePath(cacheDir: string, sessionKey: string): string;
|
|
20
|
+
/** Create the per-session cache directory and prune records older than 24 hours once per process. */
|
|
21
|
+
export declare function initializeConversationPersistence(cacheDir: string, now?: number): Promise<void>;
|
|
22
|
+
export declare function getPersistedConversation(cacheDir: string, sessionKey: string, now?: number): Promise<PersistedConversation | undefined>;
|
|
23
|
+
/** Read a snapshot together with the reason restart hydration did not restore it. */
|
|
24
|
+
export declare function loadPersistedConversation(cacheDir: string, sessionKey: string, now?: number): Promise<PersistedConversationLoad>;
|
|
25
|
+
/** Replace one session's durable snapshot and refresh its 24-hour lease. */
|
|
26
|
+
export declare function persistConversation(cacheDir: string, value: Omit<PersistedConversation, "updatedAt" | "postCompactionRebase" | "toolCatalog"> & {
|
|
27
|
+
postCompactionRebase?: boolean;
|
|
28
|
+
toolCatalog?: OpencodeToolDef[];
|
|
29
|
+
}, now?: number): Promise<void>;
|
|
30
|
+
/** Delete only the expected binding so an overlapping newer turn cannot be removed. */
|
|
31
|
+
export declare function deletePersistedConversation(cacheDir: string, sessionKey: string, expectedConversationId?: string): Promise<void>;
|
|
32
|
+
export declare function resetConversationPersistenceForTests(): void;
|