cursor-opencode-provider 0.8.1 → 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 +95 -0
- package/README.md +204 -37
- package/SECURITY.md +20 -8
- 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 +1 -0
- package/dist/context/build.js +32 -5
- package/dist/context/env.js +27 -3
- package/dist/context/epoch.js +9 -5
- package/dist/context/git.d.ts +5 -2
- package/dist/context/git.js +17 -7
- package/dist/context/paths.d.ts +1 -1
- package/dist/context/paths.js +7 -4
- 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-interrupt.d.ts +12 -0
- package/dist/host-interrupt.js +18 -0
- package/dist/host-steer.d.ts +71 -0
- package/dist/host-steer.js +232 -0
- package/dist/image-input.js +4 -3
- package/dist/index.d.ts +2 -2
- package/dist/language-model.d.ts +114 -23
- package/dist/language-model.js +2290 -469
- package/dist/lost-request.d.ts +39 -0
- package/dist/lost-request.js +111 -0
- package/dist/model-config.d.ts +3 -1
- package/dist/model-config.js +50 -29
- package/dist/models.d.ts +26 -3
- package/dist/models.js +101 -45
- package/dist/opencode2/tool-results.d.ts +18 -0
- package/dist/opencode2/tool-results.js +66 -0
- package/dist/plugin-opencode2.js +185 -25
- package/dist/plugin.js +28 -5
- package/dist/pricing-data.d.ts +26 -2
- package/dist/pricing-data.js +26 -2
- 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/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/host-notes.d.ts +31 -0
- package/dist/protocol/host-notes.js +85 -0
- package/dist/protocol/messages.js +91 -8
- package/dist/protocol/request.d.ts +35 -19
- package/dist/protocol/request.js +53 -28
- package/dist/protocol/sent-history-images.d.ts +49 -0
- package/dist/protocol/sent-history-images.js +102 -0
- package/dist/protocol/tool-call-bridge.d.ts +2 -0
- package/dist/protocol/tool-call-bridge.js +10 -0
- package/dist/protocol/tools.d.ts +74 -8
- package/dist/protocol/tools.js +431 -141
- package/dist/protocol/turn-provenance.d.ts +19 -4
- package/dist/protocol/turn-provenance.js +77 -20
- package/dist/replay-safety.d.ts +16 -4
- package/dist/replay-safety.js +34 -25
- 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 +94 -9
- package/dist/session.js +153 -34
- 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/transport/connect.d.ts +8 -1
- package/dist/transport/connect.js +103 -23
- package/dist/usage.d.ts +37 -17
- package/dist/usage.js +63 -29
- package/package.json +3 -2
- package/dist/protocol/thinking.d.ts +0 -15
- package/dist/protocol/thinking.js +0 -17
package/SECURITY.md
CHANGED
|
@@ -21,19 +21,28 @@ OpenCode's `opencode.json` / `opencode.jsonc` `instructions` array may list:
|
|
|
21
21
|
- relative paths / globs (e.g. `.cursor/rules/*.md`)
|
|
22
22
|
- absolute paths
|
|
23
23
|
- home-relative paths (`~/…`)
|
|
24
|
-
- remote URLs
|
|
24
|
+
- remote URLs supported by the host
|
|
25
25
|
|
|
26
|
-
OpenCode
|
|
26
|
+
OpenCode owns instruction discovery and supplies the resulting system prompt
|
|
27
|
+
to the AI SDK provider. This package forwards that prompt as Cursor's
|
|
28
|
+
always-apply system-instructions rule; it does not independently read instruction
|
|
29
|
+
or skill file bodies or fetch instruction URLs. Its merged-config read supplies
|
|
30
|
+
MCP server ids, plugin metadata and interaction guidance
|
|
31
|
+
([`src/context/rules.ts`](src/context/rules.ts),
|
|
32
|
+
[`src/context/build.ts`](src/context/build.ts)).
|
|
27
33
|
|
|
28
34
|
So a project config like:
|
|
29
35
|
|
|
30
36
|
```json
|
|
31
37
|
{
|
|
32
|
-
"instructions": ["
|
|
38
|
+
"instructions": ["docs/project-guide.md", "/absolute/path/to/approved-guide.md"]
|
|
33
39
|
}
|
|
34
40
|
```
|
|
35
41
|
|
|
36
|
-
can cause those files to be read
|
|
42
|
+
can cause those files to be read by OpenCode and included in the prompt sent to
|
|
43
|
+
Cursor. The provider does not add an instruction-path allowlist or remove content
|
|
44
|
+
already supplied by the host. Review instruction paths before enabling project
|
|
45
|
+
configuration, especially if they could refer to sensitive local files.
|
|
37
46
|
|
|
38
47
|
Treat project `opencode.json` as **trusted**. Do not open untrusted repositories with project config enabled if that is unacceptable.
|
|
39
48
|
|
|
@@ -45,13 +54,16 @@ To ignore project-level OpenCode config (including project `instructions` and pr
|
|
|
45
54
|
export OPENCODE_DISABLE_PROJECT_CONFIG=1
|
|
46
55
|
```
|
|
47
56
|
|
|
48
|
-
|
|
57
|
+
Set this before starting OpenCode and use the host's project-config controls to
|
|
58
|
+
suppress project instructions. The provider also skips its own project config
|
|
59
|
+
metadata reads when this flag is set; global config still applies. It cannot
|
|
60
|
+
remove instruction text that a host has already included in the supplied prompt
|
|
61
|
+
([`loadMergedConfig`](src/context/rules.ts)).
|
|
49
62
|
|
|
50
63
|
Truthy values: `1` or `true` (case-insensitive), same as OpenCode.
|
|
51
64
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
- Remote `instructions` URLs are **HTTPS-only** (`http://` is skipped). Redirects (including to a local proxy) are intentional and not blocked.
|
|
65
|
+
Instruction URL fetching and redirect policy belong to the host; this provider
|
|
66
|
+
has no separate instruction-fetch policy.
|
|
55
67
|
|
|
56
68
|
### Out of Scope
|
|
57
69
|
|
package/dist/activity.d.ts
CHANGED
|
@@ -1,15 +1,87 @@
|
|
|
1
|
+
/** A host tool may keep its Run open this long from its own start. */
|
|
2
|
+
export declare const RUNNING_TOOL_CAP_MS: number;
|
|
3
|
+
type RunningTool = {
|
|
4
|
+
sessionId: string;
|
|
5
|
+
startedAt: number;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Everything the tracker knows, as plain data. The plugin that receives host
|
|
9
|
+
* events and the provider that holds the Run can be different module copies
|
|
10
|
+
* (OpenCode 2 loads one per location), so the process-wide tracker keeps this
|
|
11
|
+
* in `processShared`: parent links, activity and prompts must all be seen by
|
|
12
|
+
* the copy that renews the Run's lease.
|
|
13
|
+
*/
|
|
14
|
+
export type SessionActivityState = {
|
|
15
|
+
parentBySession: Map<string, string>;
|
|
16
|
+
lastActivityBySession: Map<string, number>;
|
|
17
|
+
/** Open host prompts: `${sessionId}\0${requestId}` → session id and when it opened. */
|
|
18
|
+
openPrompts: Map<string, {
|
|
19
|
+
sessionId: string;
|
|
20
|
+
openedAt: number;
|
|
21
|
+
}>;
|
|
22
|
+
/** `${sessionId}\0${callId}` → when that host tool started running. */
|
|
23
|
+
runningTools: Map<string, RunningTool>;
|
|
24
|
+
};
|
|
25
|
+
export declare function createSessionActivityState(): SessionActivityState;
|
|
1
26
|
export type SessionActivitySource = {
|
|
2
27
|
lastActivityAt(sessionId: string): number | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Latest 4h ceiling among host tools still running in this session or a
|
|
30
|
+
* descendant. Undefined when none are still inside their own ceiling.
|
|
31
|
+
*/
|
|
32
|
+
runningToolDeadline?(sessionId: string, now: number, toolCapMs: number): number | undefined;
|
|
3
33
|
};
|
|
4
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Tracks OpenCode message progress and propagates it through subagent ancestry.
|
|
36
|
+
*
|
|
37
|
+
* A permission or question prompt the host has open is waiting on the user, not
|
|
38
|
+
* stalled: the host emits no events until the user answers, so an open prompt
|
|
39
|
+
* counts as current activity for its session and every ancestor.
|
|
40
|
+
*/
|
|
5
41
|
export declare class SessionActivityTracker implements SessionActivitySource {
|
|
6
42
|
private readonly parentBySession;
|
|
7
43
|
private readonly lastActivityBySession;
|
|
44
|
+
private readonly openPrompts;
|
|
45
|
+
private readonly runningTools;
|
|
46
|
+
constructor(state?: SessionActivityState);
|
|
8
47
|
linkSession(sessionId: string, parentId?: string): void;
|
|
9
48
|
recordActivity(sessionId: string, at?: number): void;
|
|
10
49
|
lastActivityAt(sessionId: string): number | undefined;
|
|
50
|
+
/** The host opened a permission or question prompt in `sessionId`. */
|
|
51
|
+
openPrompt(sessionId: string, requestId: string, at?: number): void;
|
|
52
|
+
/** The prompt was answered, rejected or cancelled. */
|
|
53
|
+
closePrompt(sessionId: string, requestId: string, at?: number): void;
|
|
54
|
+
/**
|
|
55
|
+
* The host started running this tool. A repeated start keeps the original
|
|
56
|
+
* time so a later event cannot extend the 4h ceiling.
|
|
57
|
+
*/
|
|
58
|
+
toolStarted(sessionId: string, callId: string, at?: number): void;
|
|
59
|
+
/** Drop every running tool for this session. Activity history stays. */
|
|
60
|
+
endRunningTools(sessionId: string, at?: number): void;
|
|
61
|
+
/** The host finished the tool. The normal inactivity cap applies from now. */
|
|
62
|
+
toolEnded(sessionId: string, callId: string, at?: number): void;
|
|
63
|
+
runningToolDeadline(sessionId: string, now: number, toolCapMs: number): number | undefined;
|
|
11
64
|
removeSession(sessionId: string): void;
|
|
12
65
|
clear(): void;
|
|
66
|
+
/** A prompt is open in `sessionId` or in one of its descendants. */
|
|
67
|
+
private awaitsUser;
|
|
13
68
|
private prune;
|
|
69
|
+
private belongsTo;
|
|
70
|
+
private clearRunningTools;
|
|
14
71
|
}
|
|
15
72
|
export declare const sessionActivity: SessionActivityTracker;
|
|
73
|
+
/**
|
|
74
|
+
* Open or close a host prompt from an OpenCode event. OpenCode 1.x publishes
|
|
75
|
+
* `permission.asked` / `permission.replied` and `question.asked` /
|
|
76
|
+
* `question.replied` / `question.rejected` (older releases used
|
|
77
|
+
* `permission.updated` and `permissionID`); OpenCode 2 publishes the same
|
|
78
|
+
* permission events plus `form.created` / `form.replied` / `form.cancelled`.
|
|
79
|
+
*/
|
|
80
|
+
export declare function applyHostPromptEvent(tracker: SessionActivityTracker, type: unknown, payload: unknown): void;
|
|
81
|
+
/**
|
|
82
|
+
* A host tool that is actually running holds the Run past the inactivity cap.
|
|
83
|
+
* OpenCode 1 reports that on the tool part (`running`, and not provider-executed).
|
|
84
|
+
* OpenCode 2 reports it on `session.tool.called` when `executed` is not true.
|
|
85
|
+
*/
|
|
86
|
+
export declare function applyHostToolEvent(tracker: SessionActivityTracker, type: unknown, payload: unknown): void;
|
|
87
|
+
export {};
|
package/dist/activity.js
CHANGED
|
@@ -1,10 +1,35 @@
|
|
|
1
|
+
import { processShared } from "./process-shared.js";
|
|
2
|
+
/** A host tool may keep its Run open this long from its own start. */
|
|
3
|
+
export const RUNNING_TOOL_CAP_MS = 4 * 60 * 60 * 1_000;
|
|
4
|
+
export function createSessionActivityState() {
|
|
5
|
+
return {
|
|
6
|
+
parentBySession: new Map(),
|
|
7
|
+
lastActivityBySession: new Map(),
|
|
8
|
+
openPrompts: new Map(),
|
|
9
|
+
runningTools: new Map(),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
1
12
|
const MAX_ANCESTRY_DEPTH = 64;
|
|
2
13
|
const MAX_TRACKED_SESSIONS = 1_024;
|
|
3
14
|
const ACTIVITY_RETENTION_MS = 24 * 60 * 60 * 1_000;
|
|
4
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Tracks OpenCode message progress and propagates it through subagent ancestry.
|
|
17
|
+
*
|
|
18
|
+
* A permission or question prompt the host has open is waiting on the user, not
|
|
19
|
+
* stalled: the host emits no events until the user answers, so an open prompt
|
|
20
|
+
* counts as current activity for its session and every ancestor.
|
|
21
|
+
*/
|
|
5
22
|
export class SessionActivityTracker {
|
|
6
|
-
parentBySession
|
|
7
|
-
lastActivityBySession
|
|
23
|
+
parentBySession;
|
|
24
|
+
lastActivityBySession;
|
|
25
|
+
openPrompts;
|
|
26
|
+
runningTools;
|
|
27
|
+
constructor(state = createSessionActivityState()) {
|
|
28
|
+
this.parentBySession = state.parentBySession;
|
|
29
|
+
this.lastActivityBySession = state.lastActivityBySession;
|
|
30
|
+
this.openPrompts = state.openPrompts;
|
|
31
|
+
this.runningTools = state.runningTools;
|
|
32
|
+
}
|
|
8
33
|
linkSession(sessionId, parentId) {
|
|
9
34
|
if (!sessionId)
|
|
10
35
|
return;
|
|
@@ -39,19 +64,114 @@ export class SessionActivityTracker {
|
|
|
39
64
|
this.prune(at);
|
|
40
65
|
}
|
|
41
66
|
lastActivityAt(sessionId) {
|
|
42
|
-
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
this.prune(now);
|
|
69
|
+
if (this.awaitsUser(sessionId))
|
|
70
|
+
return now;
|
|
43
71
|
return this.lastActivityBySession.get(sessionId);
|
|
44
72
|
}
|
|
73
|
+
/** The host opened a permission or question prompt in `sessionId`. */
|
|
74
|
+
openPrompt(sessionId, requestId, at = Date.now()) {
|
|
75
|
+
if (!sessionId || !requestId)
|
|
76
|
+
return;
|
|
77
|
+
const key = `${sessionId}\0${requestId}`;
|
|
78
|
+
this.openPrompts.delete(key);
|
|
79
|
+
this.openPrompts.set(key, { sessionId, openedAt: at });
|
|
80
|
+
this.recordActivity(sessionId, at);
|
|
81
|
+
}
|
|
82
|
+
/** The prompt was answered, rejected or cancelled. */
|
|
83
|
+
closePrompt(sessionId, requestId, at = Date.now()) {
|
|
84
|
+
if (!sessionId || !requestId)
|
|
85
|
+
return;
|
|
86
|
+
this.openPrompts.delete(`${sessionId}\0${requestId}`);
|
|
87
|
+
this.recordActivity(sessionId, at);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The host started running this tool. A repeated start keeps the original
|
|
91
|
+
* time so a later event cannot extend the 4h ceiling.
|
|
92
|
+
*/
|
|
93
|
+
toolStarted(sessionId, callId, at = Date.now()) {
|
|
94
|
+
if (!sessionId || !callId || !Number.isFinite(at))
|
|
95
|
+
return;
|
|
96
|
+
const key = `${sessionId}\0${callId}`;
|
|
97
|
+
if (!this.runningTools.has(key))
|
|
98
|
+
this.runningTools.set(key, { sessionId, startedAt: at });
|
|
99
|
+
this.recordActivity(sessionId, at);
|
|
100
|
+
}
|
|
101
|
+
/** Drop every running tool for this session. Activity history stays. */
|
|
102
|
+
endRunningTools(sessionId, at = Date.now()) {
|
|
103
|
+
if (!sessionId)
|
|
104
|
+
return;
|
|
105
|
+
let ended = false;
|
|
106
|
+
for (const [key, tool] of this.runningTools) {
|
|
107
|
+
if (tool.sessionId !== sessionId)
|
|
108
|
+
continue;
|
|
109
|
+
this.runningTools.delete(key);
|
|
110
|
+
ended = true;
|
|
111
|
+
}
|
|
112
|
+
if (ended)
|
|
113
|
+
this.recordActivity(sessionId, at);
|
|
114
|
+
}
|
|
115
|
+
/** The host finished the tool. The normal inactivity cap applies from now. */
|
|
116
|
+
toolEnded(sessionId, callId, at = Date.now()) {
|
|
117
|
+
if (!sessionId || !callId)
|
|
118
|
+
return;
|
|
119
|
+
this.runningTools.delete(`${sessionId}\0${callId}`);
|
|
120
|
+
this.recordActivity(sessionId, at);
|
|
121
|
+
}
|
|
122
|
+
runningToolDeadline(sessionId, now, toolCapMs) {
|
|
123
|
+
let deadline;
|
|
124
|
+
for (const tool of this.runningTools.values()) {
|
|
125
|
+
if (!this.belongsTo(tool.sessionId, sessionId))
|
|
126
|
+
continue;
|
|
127
|
+
const cap = tool.startedAt + toolCapMs;
|
|
128
|
+
if (now >= cap)
|
|
129
|
+
continue;
|
|
130
|
+
deadline = deadline === undefined ? cap : Math.max(deadline, cap);
|
|
131
|
+
}
|
|
132
|
+
return deadline;
|
|
133
|
+
}
|
|
45
134
|
removeSession(sessionId) {
|
|
46
135
|
this.parentBySession.delete(sessionId);
|
|
47
136
|
this.lastActivityBySession.delete(sessionId);
|
|
137
|
+
for (const [key, prompt] of this.openPrompts) {
|
|
138
|
+
if (prompt.sessionId === sessionId)
|
|
139
|
+
this.openPrompts.delete(key);
|
|
140
|
+
}
|
|
141
|
+
this.clearRunningTools(sessionId);
|
|
48
142
|
}
|
|
49
143
|
clear() {
|
|
50
144
|
this.parentBySession.clear();
|
|
51
145
|
this.lastActivityBySession.clear();
|
|
146
|
+
this.openPrompts.clear();
|
|
147
|
+
this.runningTools.clear();
|
|
148
|
+
}
|
|
149
|
+
/** A prompt is open in `sessionId` or in one of its descendants. */
|
|
150
|
+
awaitsUser(sessionId) {
|
|
151
|
+
for (const prompt of this.openPrompts.values()) {
|
|
152
|
+
let current = prompt.sessionId;
|
|
153
|
+
for (let depth = 0; current && depth < MAX_ANCESTRY_DEPTH; depth++) {
|
|
154
|
+
if (current === sessionId)
|
|
155
|
+
return true;
|
|
156
|
+
current = this.parentBySession.get(current);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
52
160
|
}
|
|
53
161
|
prune(now) {
|
|
54
162
|
const oldestAllowed = now - ACTIVITY_RETENTION_MS;
|
|
163
|
+
// A prompt whose reply never arrived must not hold a Run open forever.
|
|
164
|
+
for (const [key, prompt] of this.openPrompts) {
|
|
165
|
+
if (prompt.openedAt >= oldestAllowed)
|
|
166
|
+
break;
|
|
167
|
+
this.openPrompts.delete(key);
|
|
168
|
+
}
|
|
169
|
+
while (this.openPrompts.size > MAX_TRACKED_SESSIONS) {
|
|
170
|
+
const oldest = this.openPrompts.keys().next().value;
|
|
171
|
+
if (!oldest)
|
|
172
|
+
break;
|
|
173
|
+
this.openPrompts.delete(oldest);
|
|
174
|
+
}
|
|
55
175
|
for (const [sessionId, activityAt] of this.lastActivityBySession) {
|
|
56
176
|
if (activityAt >= oldestAllowed)
|
|
57
177
|
break;
|
|
@@ -71,6 +191,112 @@ export class SessionActivityTracker {
|
|
|
71
191
|
break;
|
|
72
192
|
this.parentBySession.delete(oldest);
|
|
73
193
|
}
|
|
194
|
+
for (const [key, tool] of this.runningTools) {
|
|
195
|
+
if (tool.startedAt < oldestAllowed)
|
|
196
|
+
this.runningTools.delete(key);
|
|
197
|
+
}
|
|
198
|
+
// A tool whose end event never arrived must not grow the map without bound.
|
|
199
|
+
while (this.runningTools.size > MAX_TRACKED_SESSIONS) {
|
|
200
|
+
const oldest = this.runningTools.keys().next().value;
|
|
201
|
+
if (!oldest)
|
|
202
|
+
break;
|
|
203
|
+
this.runningTools.delete(oldest);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
belongsTo(toolSessionId, sessionId) {
|
|
207
|
+
let current = toolSessionId;
|
|
208
|
+
for (let depth = 0; current && depth < MAX_ANCESTRY_DEPTH; depth++) {
|
|
209
|
+
if (current === sessionId)
|
|
210
|
+
return true;
|
|
211
|
+
current = this.parentBySession.get(current);
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
clearRunningTools(sessionId) {
|
|
216
|
+
for (const [key, tool] of this.runningTools) {
|
|
217
|
+
if (tool.sessionId === sessionId)
|
|
218
|
+
this.runningTools.delete(key);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
export const sessionActivity = new SessionActivityTracker(processShared("session-activity.v1", createSessionActivityState));
|
|
223
|
+
/**
|
|
224
|
+
* Open or close a host prompt from an OpenCode event. OpenCode 1.x publishes
|
|
225
|
+
* `permission.asked` / `permission.replied` and `question.asked` /
|
|
226
|
+
* `question.replied` / `question.rejected` (older releases used
|
|
227
|
+
* `permission.updated` and `permissionID`); OpenCode 2 publishes the same
|
|
228
|
+
* permission events plus `form.created` / `form.replied` / `form.cancelled`.
|
|
229
|
+
*/
|
|
230
|
+
export function applyHostPromptEvent(tracker, type, payload) {
|
|
231
|
+
const data = payload && typeof payload === "object" ? payload : {};
|
|
232
|
+
const text = (value) => typeof value === "string" ? value : "";
|
|
233
|
+
switch (type) {
|
|
234
|
+
case "permission.asked":
|
|
235
|
+
case "permission.updated":
|
|
236
|
+
case "question.asked":
|
|
237
|
+
tracker.openPrompt(text(data.sessionID), text(data.id));
|
|
238
|
+
break;
|
|
239
|
+
case "permission.replied":
|
|
240
|
+
case "question.replied":
|
|
241
|
+
case "question.rejected":
|
|
242
|
+
tracker.closePrompt(text(data.sessionID), text(data.requestID) || text(data.permissionID));
|
|
243
|
+
break;
|
|
244
|
+
case "form.created": {
|
|
245
|
+
const form = data.form && typeof data.form === "object" ? data.form : {};
|
|
246
|
+
tracker.openPrompt(text(form.sessionID), text(form.id));
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
case "form.replied":
|
|
250
|
+
case "form.cancelled":
|
|
251
|
+
tracker.closePrompt(text(data.sessionID), text(data.id));
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* A host tool that is actually running holds the Run past the inactivity cap.
|
|
257
|
+
* OpenCode 1 reports that on the tool part (`running`, and not provider-executed).
|
|
258
|
+
* OpenCode 2 reports it on `session.tool.called` when `executed` is not true.
|
|
259
|
+
*/
|
|
260
|
+
export function applyHostToolEvent(tracker, type, payload) {
|
|
261
|
+
const data = payload && typeof payload === "object" ? payload : {};
|
|
262
|
+
const text = (value) => typeof value === "string" ? value : "";
|
|
263
|
+
if (type === "message.part.updated") {
|
|
264
|
+
const part = data.part && typeof data.part === "object" ? data.part : undefined;
|
|
265
|
+
if (!part || part.type !== "tool")
|
|
266
|
+
return;
|
|
267
|
+
const sessionId = text(part.sessionID);
|
|
268
|
+
const callId = text(part.callID) || text(part.callId);
|
|
269
|
+
const state = part.state && typeof part.state === "object" ? part.state : undefined;
|
|
270
|
+
const metadata = part.metadata && typeof part.metadata === "object"
|
|
271
|
+
? part.metadata
|
|
272
|
+
: undefined;
|
|
273
|
+
if (!sessionId || !callId || !state)
|
|
274
|
+
return;
|
|
275
|
+
if (state.status === "running" && metadata?.providerExecuted !== true)
|
|
276
|
+
tracker.toolStarted(sessionId, callId);
|
|
277
|
+
else if (state.status === "completed" || state.status === "error")
|
|
278
|
+
tracker.toolEnded(sessionId, callId);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (type === "session.tool.called") {
|
|
282
|
+
const sessionId = text(data.sessionID);
|
|
283
|
+
const callId = text(data.id) || text(data.callID);
|
|
284
|
+
if (!sessionId || !callId || data.executed === true)
|
|
285
|
+
return;
|
|
286
|
+
tracker.toolStarted(sessionId, callId);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (type === "session.tool.success" || type === "session.tool.failed") {
|
|
290
|
+
const sessionId = text(data.sessionID);
|
|
291
|
+
const callId = text(data.id) || text(data.callID);
|
|
292
|
+
if (sessionId && callId)
|
|
293
|
+
tracker.toolEnded(sessionId, callId);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (type === "session.idle") {
|
|
297
|
+
const info = data.info && typeof data.info === "object" ? data.info : undefined;
|
|
298
|
+
const sessionId = text(data.sessionID) || text(info?.id);
|
|
299
|
+
if (sessionId)
|
|
300
|
+
tracker.endRunningTools(sessionId);
|
|
74
301
|
}
|
|
75
302
|
}
|
|
76
|
-
export const sessionActivity = new SessionActivityTracker();
|
package/dist/agent-url.js
CHANGED
|
@@ -2,10 +2,13 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { fetchAgentUrl } from "./transport/connect.js";
|
|
3
3
|
import { errorMessage, trace } from "./debug.js";
|
|
4
4
|
import { CURSOR_API_HOST } from "./shared.js";
|
|
5
|
+
import { processShared } from "./process-shared.js";
|
|
6
|
+
import { CursorAuthError, CursorProtocolError, CursorProviderError, CursorServerError, CursorTransportError, toCursorProviderError, } from "./errors.js";
|
|
5
7
|
const DEFAULT_API_BASE = `https://${CURSOR_API_HOST}`;
|
|
6
8
|
// In-process memo of the region-specific Run stream origin (agentnUrl). Resolved
|
|
7
|
-
// once per process
|
|
8
|
-
//
|
|
9
|
+
// once per process, shared by every copy of this package that the host loads.
|
|
10
|
+
// The auth loader warms it and the first startSession reuses it. It is held
|
|
11
|
+
// for the process lifetime. Region routing is near-static, and
|
|
9
12
|
// re-resolving mid-session would break a held-open bidi Run stream anyway.
|
|
10
13
|
//
|
|
11
14
|
// Unlike the models/version caches this is NOT persisted to disk. A wrong agent
|
|
@@ -26,9 +29,23 @@ function resolveCacheKey(token, options) {
|
|
|
26
29
|
const tokenHash = createHash("sha256").update(token).digest("hex").slice(0, 16);
|
|
27
30
|
return `${tokenHash}|${normalizeApiBaseURL(options.apiBaseURL ?? options.baseURL)}|telem:${options.telemetryEnabled === true}`;
|
|
28
31
|
}
|
|
29
|
-
const _resolved = new Map();
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
const _resolved = processShared("agent-url.resolved.v1", () => new Map());
|
|
33
|
+
const _inflight = processShared("agent-url.inflight.v2", () => new Map());
|
|
34
|
+
// A shared promise must not reject with another module graph's error instance,
|
|
35
|
+
// nor let one caller's replay-safety mutation alter a sibling caller's error.
|
|
36
|
+
async function localResult(promise) {
|
|
37
|
+
const result = await promise;
|
|
38
|
+
if ("url" in result)
|
|
39
|
+
return result.url;
|
|
40
|
+
const { message, ...options } = result.failure;
|
|
41
|
+
switch (options.origin) {
|
|
42
|
+
case "auth": throw new CursorAuthError(message, options);
|
|
43
|
+
case "transport": throw new CursorTransportError(message, options);
|
|
44
|
+
case "server": throw new CursorServerError(message, options);
|
|
45
|
+
case "protocol": throw new CursorProtocolError(message, options);
|
|
46
|
+
default: throw new CursorProviderError(message, options);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
32
49
|
/**
|
|
33
50
|
* Resolve the Run stream origin for this account via the `GetServerConfig`
|
|
34
51
|
* Connect RPC. Memoized for the process lifetime.
|
|
@@ -49,26 +66,37 @@ export async function resolveAgentUrl(token, options = {}) {
|
|
|
49
66
|
const inflight = _inflight.get(cacheKey);
|
|
50
67
|
if (inflight) {
|
|
51
68
|
trace("agent-url: awaiting in-flight GetServerConfig");
|
|
52
|
-
return inflight;
|
|
69
|
+
return localResult(inflight);
|
|
53
70
|
}
|
|
54
71
|
const promise = (async () => {
|
|
55
72
|
try {
|
|
56
73
|
const url = await fetchAgentUrl(token, options);
|
|
57
74
|
_resolved.set(cacheKey, url);
|
|
58
75
|
trace(`agent-url: resolved via GetServerConfig → ${url}`);
|
|
59
|
-
return url;
|
|
76
|
+
return { url };
|
|
60
77
|
}
|
|
61
78
|
catch (err) {
|
|
62
79
|
const reason = errorMessage(err);
|
|
63
80
|
trace(`agent-url: GetServerConfig failed (${reason}); no fallback agent host will be used`);
|
|
64
|
-
|
|
81
|
+
const failure = toCursorProviderError(err, { replaySafe: true });
|
|
82
|
+
return { failure: {
|
|
83
|
+
message: failure.message,
|
|
84
|
+
origin: failure.origin,
|
|
85
|
+
transient: failure.transient,
|
|
86
|
+
replaySafe: failure.replaySafe,
|
|
87
|
+
statusCode: failure.statusCode,
|
|
88
|
+
grpcStatus: failure.grpcStatus,
|
|
89
|
+
rstCode: failure.rstCode,
|
|
90
|
+
code: failure.code,
|
|
91
|
+
retryAfterMs: failure.retryAfterMs,
|
|
92
|
+
} };
|
|
65
93
|
}
|
|
66
94
|
finally {
|
|
67
95
|
_inflight.delete(cacheKey);
|
|
68
96
|
}
|
|
69
97
|
})();
|
|
70
98
|
_inflight.set(cacheKey, promise);
|
|
71
|
-
return promise;
|
|
99
|
+
return localResult(promise);
|
|
72
100
|
}
|
|
73
101
|
/** Reset the in-process memo. Tests only. */
|
|
74
102
|
export function resetAgentUrlCache() {
|
package/dist/auth-renewal.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ export type SessionRenewal = {
|
|
|
33
33
|
* older stored credential (persisting failed, or a host that persists only
|
|
34
34
|
* later) gets the newest token instead of renewing again.
|
|
35
35
|
*/
|
|
36
|
-
export declare function latestSessionTokens(tokens: SessionTokens): SessionTokens;
|
|
36
|
+
export declare function latestSessionTokens(tokens: SessionTokens, baseUrl?: string): SessionTokens;
|
|
37
37
|
export declare function isSessionRenewalDue(token: string, now?: number): boolean;
|
|
38
38
|
/**
|
|
39
39
|
* Return a usable session token, renewing it first when it is due (or when
|