cursor-opencode-provider 0.7.5 → 0.8.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 +9 -7
- package/dist/agent-url.js +2 -2
- package/dist/auth-renewal.d.ts +85 -0
- package/dist/auth-renewal.js +284 -0
- package/dist/auth.d.ts +38 -22
- package/dist/auth.js +79 -96
- package/dist/classic-tools.d.ts +15 -0
- package/dist/classic-tools.js +45 -0
- package/dist/context/build.d.ts +42 -5
- package/dist/context/build.js +96 -92
- package/dist/context/dynamic-catalog.d.ts +3 -28
- package/dist/context/dynamic-catalog.js +25 -130
- package/dist/context/epoch.d.ts +6 -4
- package/dist/context/epoch.js +22 -10
- package/dist/context/frozen.js +37 -9
- package/dist/context/index.d.ts +1 -1
- package/dist/context/index.js +1 -1
- package/dist/context/overlay.d.ts +2 -23
- package/dist/context/overlay.js +6 -30
- package/dist/context/paths.d.ts +7 -4
- package/dist/context/paths.js +13 -9
- package/dist/context/rules.d.ts +3 -15
- package/dist/context/rules.js +10 -186
- package/dist/debug.d.ts +2 -0
- package/dist/debug.js +4 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +27 -3
- package/dist/host-agent-mode.js +2 -2
- package/dist/image-save.js +3 -3
- package/dist/index.d.ts +10 -0
- package/dist/language-model.d.ts +59 -8
- package/dist/language-model.js +458 -118
- package/dist/opencode2/integration.d.ts +30 -7
- package/dist/opencode2/integration.js +75 -46
- package/dist/plan-execution-kickoff.js +2 -2
- package/dist/plugin-opencode2.js +10 -16
- package/dist/plugin.d.ts +0 -10
- package/dist/plugin.js +138 -109
- package/dist/protocol/blob-store.js +3 -2
- package/dist/protocol/conversation-bind.js +1 -1
- package/dist/protocol/conversation-persistence.d.ts +2 -0
- package/dist/protocol/conversation-persistence.js +11 -1
- package/dist/protocol/conversation-state.d.ts +2 -0
- package/dist/protocol/conversation-state.js +19 -0
- package/dist/protocol/create-plan.js +2 -1
- package/dist/protocol/messages.js +22 -4
- package/dist/protocol/request.d.ts +11 -9
- package/dist/protocol/request.js +15 -25
- package/dist/protocol/tools.d.ts +17 -10
- package/dist/protocol/tools.js +53 -93
- package/dist/protocol/turn-provenance.d.ts +52 -0
- package/dist/protocol/turn-provenance.js +174 -0
- package/dist/session.d.ts +9 -1
- package/dist/shared.d.ts +7 -0
- package/dist/shared.js +7 -0
- package/package.json +1 -1
- package/dist/context/agents.d.ts +0 -12
- package/dist/context/agents.js +0 -136
- package/dist/context/skills.d.ts +0 -11
- package/dist/context/skills.js +0 -110
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* At a successful TurnEnded it is compacted to the graph reachable from the
|
|
10
10
|
* latest checkpoint, matching Cursor CLI's conversation-export traversal.
|
|
11
11
|
*/
|
|
12
|
+
import { errorMessage } from "../debug.js";
|
|
12
13
|
import { collectReachableConversationBlobIds } from "./blob-reachability.js";
|
|
13
14
|
function hex(b) {
|
|
14
15
|
let s = "";
|
|
@@ -76,7 +77,7 @@ export function inspectConversationBlobGraph(conversationId, checkpoint) {
|
|
|
76
77
|
return { count, bytes, complete: true };
|
|
77
78
|
}
|
|
78
79
|
catch (error) {
|
|
79
|
-
return retainedStats(
|
|
80
|
+
return retainedStats(errorMessage(error));
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
/** Copy all durable blobs so a completed turn can be persisted atomically. */
|
|
@@ -131,7 +132,7 @@ export function compactConversationBlobs(conversationId, checkpoint) {
|
|
|
131
132
|
};
|
|
132
133
|
}
|
|
133
134
|
catch (error) {
|
|
134
|
-
return fallback(
|
|
135
|
+
return fallback(errorMessage(error));
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
138
|
/** Replace the in-memory blob bucket when restoring a persisted conversation. */
|
|
@@ -1,7 +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 { transferFrozenRequestContext
|
|
4
|
+
import { transferFrozenRequestContext } from "../context/frozen.js";
|
|
5
5
|
/**
|
|
6
6
|
* OpenCode session key → active Cursor conversation_id.
|
|
7
7
|
*
|
|
@@ -11,6 +11,8 @@ export type PersistedConversation = {
|
|
|
11
11
|
postCompactionRebase: boolean;
|
|
12
12
|
hostAgent?: string;
|
|
13
13
|
systemPromptHash?: string;
|
|
14
|
+
/** JSON-encoded TurnProvenance (what this provider emitted last). */
|
|
15
|
+
turnProvenance?: string;
|
|
14
16
|
};
|
|
15
17
|
export type ConversationLoadStatus = "restored" | "missing" | "invalid" | "expired";
|
|
16
18
|
export type PersistedConversationLoad = {
|
|
@@ -47,7 +47,8 @@ function cloneConversation(value) {
|
|
|
47
47
|
*
|
|
48
48
|
* ConversationCache: schema_version=1, session_key=2, conversation_id=3,
|
|
49
49
|
* updated_at=4, checkpoint=5, blobs=6, request_context=7, tool_catalog=8,
|
|
50
|
-
* post_compaction_rebase=9, host_agent=10, system_prompt_hash=11
|
|
50
|
+
* post_compaction_rebase=9, host_agent=10, system_prompt_hash=11,
|
|
51
|
+
* turn_provenance_json=12.
|
|
51
52
|
* Blob: id=1, data=2. Tool: name=1, description=2,
|
|
52
53
|
* input_schema_json=3, source_name=4.
|
|
53
54
|
*
|
|
@@ -176,6 +177,8 @@ function encodeCacheFile(value) {
|
|
|
176
177
|
writer.uint32(fieldTag(10, 2)).string(value.hostAgent);
|
|
177
178
|
if (value.systemPromptHash)
|
|
178
179
|
writer.uint32(fieldTag(11, 2)).string(value.systemPromptHash);
|
|
180
|
+
if (value.turnProvenance)
|
|
181
|
+
writer.uint32(fieldTag(12, 2)).string(value.turnProvenance);
|
|
179
182
|
return { protobufBytes: writer.finish(), requestContextBytes: requestContext.length };
|
|
180
183
|
}
|
|
181
184
|
function decodeProtobuf(data) {
|
|
@@ -191,6 +194,7 @@ function decodeProtobuf(data) {
|
|
|
191
194
|
let postCompactionRebase = false;
|
|
192
195
|
let hostAgent;
|
|
193
196
|
let systemPromptHash;
|
|
197
|
+
let turnProvenance;
|
|
194
198
|
while (reader.pos < reader.len) {
|
|
195
199
|
const tag = reader.uint32();
|
|
196
200
|
const wireType = tag & 7;
|
|
@@ -250,6 +254,11 @@ function decodeProtobuf(data) {
|
|
|
250
254
|
throw new Error("invalid system prompt hash");
|
|
251
255
|
systemPromptHash = reader.string();
|
|
252
256
|
break;
|
|
257
|
+
case 12:
|
|
258
|
+
if (wireType !== 2)
|
|
259
|
+
throw new Error("invalid turn provenance");
|
|
260
|
+
turnProvenance = reader.string();
|
|
261
|
+
break;
|
|
253
262
|
default:
|
|
254
263
|
reader.skipType(wireType);
|
|
255
264
|
}
|
|
@@ -270,6 +279,7 @@ function decodeProtobuf(data) {
|
|
|
270
279
|
postCompactionRebase,
|
|
271
280
|
...(hostAgent ? { hostAgent } : {}),
|
|
272
281
|
...(systemPromptHash ? { systemPromptHash } : {}),
|
|
282
|
+
...(turnProvenance ? { turnProvenance } : {}),
|
|
273
283
|
};
|
|
274
284
|
}
|
|
275
285
|
function decodeCacheFile(compressed, expectedSessionKey) {
|
|
@@ -7,6 +7,8 @@ export declare function hydrateConversationState(cacheDir: string, sessionKey: s
|
|
|
7
7
|
hostAgent?: string;
|
|
8
8
|
systemPromptHash?: string;
|
|
9
9
|
} | undefined>;
|
|
10
|
+
/** Restore only turn provenance when its in-memory entry was evicted. */
|
|
11
|
+
export declare function hydrateTurnProvenance(cacheDir: string, sessionKey: string): Promise<void>;
|
|
10
12
|
/** Persist the complete resumable state only after Cursor confirms TurnEnded. */
|
|
11
13
|
export declare function persistConversationState(cacheDir: string, input: {
|
|
12
14
|
sessionKey: string;
|
|
@@ -3,6 +3,7 @@ import { getFrozenRequestContext, setFrozenRequestContext, } from "../context/fr
|
|
|
3
3
|
import { hasConversationBinding, isActiveConversationBinding, restoreConversationBinding, } from "./conversation-bind.js";
|
|
4
4
|
import { compactConversationBlobs, restoreConversationBlobs, } from "./blob-store.js";
|
|
5
5
|
import { getCheckpoint, setCheckpoint } from "./checkpoint.js";
|
|
6
|
+
import { getTurnProvenance, parseTurnProvenance, restoreTurnProvenance, serializeTurnProvenance, } from "./turn-provenance.js";
|
|
6
7
|
import { deletePersistedConversation, loadPersistedConversation, persistConversation, } from "./conversation-persistence.js";
|
|
7
8
|
/** Restore one OpenCode session before its conversation binding is resolved. */
|
|
8
9
|
export async function hydrateConversationState(cacheDir, sessionKey) {
|
|
@@ -20,6 +21,9 @@ export async function hydrateConversationState(cacheDir, sessionKey) {
|
|
|
20
21
|
setCheckpoint(persisted.conversationId, persisted.checkpoint);
|
|
21
22
|
restoreConversationBlobs(persisted.conversationId, persisted.blobs);
|
|
22
23
|
setFrozenRequestContext(persisted.conversationId, persisted.requestContext);
|
|
24
|
+
const provenance = persisted.turnProvenance ? parseTurnProvenance(persisted.turnProvenance) : undefined;
|
|
25
|
+
if (provenance?.conversationId === persisted.conversationId)
|
|
26
|
+
restoreTurnProvenance(sessionKey, provenance);
|
|
23
27
|
trace(`conversation persistence: restored sessionKey=${sessionKey} ` +
|
|
24
28
|
`conversationId=${persisted.conversationId} checkpoint=${persisted.checkpoint?.length ?? 0}B ` +
|
|
25
29
|
`blobs=${persisted.blobs.length}`);
|
|
@@ -31,6 +35,17 @@ export async function hydrateConversationState(cacheDir, sessionKey) {
|
|
|
31
35
|
...(persisted.systemPromptHash ? { systemPromptHash: persisted.systemPromptHash } : {}),
|
|
32
36
|
};
|
|
33
37
|
}
|
|
38
|
+
/** Restore only turn provenance when its in-memory entry was evicted. */
|
|
39
|
+
export async function hydrateTurnProvenance(cacheDir, sessionKey) {
|
|
40
|
+
if (getTurnProvenance(sessionKey))
|
|
41
|
+
return;
|
|
42
|
+
const persisted = (await loadPersistedConversation(cacheDir, sessionKey)).value;
|
|
43
|
+
if (!persisted?.turnProvenance)
|
|
44
|
+
return;
|
|
45
|
+
const provenance = parseTurnProvenance(persisted.turnProvenance);
|
|
46
|
+
if (provenance?.conversationId === persisted.conversationId)
|
|
47
|
+
restoreTurnProvenance(sessionKey, provenance);
|
|
48
|
+
}
|
|
34
49
|
/** Persist the complete resumable state only after Cursor confirms TurnEnded. */
|
|
35
50
|
export async function persistConversationState(cacheDir, input) {
|
|
36
51
|
// A newer Run may have reset/superseded this conversation while its final
|
|
@@ -44,6 +59,7 @@ export async function persistConversationState(cacheDir, input) {
|
|
|
44
59
|
const blobCompaction = compactConversationBlobs(input.conversationId, checkpoint);
|
|
45
60
|
const blobs = blobCompaction.blobs;
|
|
46
61
|
const requestContext = getFrozenRequestContext(input.conversationId) ?? input.requestContext;
|
|
62
|
+
const provenance = getTurnProvenance(input.sessionKey);
|
|
47
63
|
await persistConversation(cacheDir, {
|
|
48
64
|
sessionKey: input.sessionKey,
|
|
49
65
|
conversationId: input.conversationId,
|
|
@@ -54,6 +70,9 @@ export async function persistConversationState(cacheDir, input) {
|
|
|
54
70
|
postCompactionRebase: input.postCompactionRebase,
|
|
55
71
|
hostAgent: input.hostAgent,
|
|
56
72
|
systemPromptHash: input.systemPromptHash,
|
|
73
|
+
...(provenance?.conversationId === input.conversationId
|
|
74
|
+
? { turnProvenance: serializeTurnProvenance(provenance) }
|
|
75
|
+
: {}),
|
|
57
76
|
});
|
|
58
77
|
trace(`conversation persistence: saved sessionKey=${input.sessionKey} ` +
|
|
59
78
|
`conversationId=${input.conversationId} checkpoint=${checkpoint?.length ?? 0}B ` +
|
|
@@ -21,6 +21,7 @@ import { pathToFileURL } from "node:url";
|
|
|
21
21
|
import { hostPlansDir } from "../context/paths.js";
|
|
22
22
|
import { parseAnswerSegments, } from "./ask-question.js";
|
|
23
23
|
import { decodeMessageSparse } from "./messages.js";
|
|
24
|
+
import { errorMessage } from "../debug.js";
|
|
24
25
|
const PLAN_ADJECTIVES = [
|
|
25
26
|
"brave",
|
|
26
27
|
"calm",
|
|
@@ -344,7 +345,7 @@ export function writeOpencodePlanFile(args, workspaceRoot, created = Date.now())
|
|
|
344
345
|
writeFileSync(planPath, markdown, "utf-8");
|
|
345
346
|
}
|
|
346
347
|
catch (err) {
|
|
347
|
-
const message =
|
|
348
|
+
const message = errorMessage(err);
|
|
348
349
|
return { ok: false, error: `Failed to write plan file: ${message}` };
|
|
349
350
|
}
|
|
350
351
|
return {
|
|
@@ -1062,9 +1062,27 @@ export function createMessageTypes() {
|
|
|
1062
1062
|
{ id: 20, name: "is_working_dir_home_dir", type: "bool" },
|
|
1063
1063
|
{ id: 21, name: "process_working_directory", type: "string" },
|
|
1064
1064
|
]);
|
|
1065
|
+
// Cursor applies a rule by its type: `global` is the CLI's `alwaysApply: true`
|
|
1066
|
+
// (its AGENTS.md / CLAUDE.md default). An untyped rule is only attachable on
|
|
1067
|
+
// request, so its content never reaches the model on its own.
|
|
1068
|
+
addType(root, "CursorRuleTypeGlobal", []);
|
|
1069
|
+
addType(root, "CursorRuleTypeFileGlobs", [
|
|
1070
|
+
{ id: 1, name: "globs", type: "string", repeated: true },
|
|
1071
|
+
]);
|
|
1072
|
+
addType(root, "CursorRuleTypeAgentFetched", [
|
|
1073
|
+
{ id: 1, name: "description", type: "string" },
|
|
1074
|
+
]);
|
|
1075
|
+
addType(root, "CursorRuleTypeManuallyAttached", []);
|
|
1076
|
+
addType(root, "CursorRuleType", [
|
|
1077
|
+
{ id: 1, name: "global", type: "CursorRuleTypeGlobal" },
|
|
1078
|
+
{ id: 2, name: "file_globbed", type: "CursorRuleTypeFileGlobs" },
|
|
1079
|
+
{ id: 3, name: "agent_fetched", type: "CursorRuleTypeAgentFetched" },
|
|
1080
|
+
{ id: 4, name: "manually_attached", type: "CursorRuleTypeManuallyAttached" },
|
|
1081
|
+
], [{ name: "type", fields: ["global", "file_globbed", "agent_fetched", "manually_attached"] }]);
|
|
1065
1082
|
addType(root, "CursorRule", [
|
|
1066
1083
|
{ id: 1, name: "full_path", type: "string" },
|
|
1067
1084
|
{ id: 2, name: "content", type: "string" },
|
|
1085
|
+
{ id: 3, name: "type", type: "CursorRuleType" },
|
|
1068
1086
|
]);
|
|
1069
1087
|
addType(root, "RepositoryIndexingInfo", [
|
|
1070
1088
|
{ id: 1, name: "relative_workspace_path", type: "string" },
|
|
@@ -1151,8 +1169,8 @@ export function createMessageTypes() {
|
|
|
1151
1169
|
{ id: 1, name: "success", type: "RequestContextSuccess" },
|
|
1152
1170
|
]);
|
|
1153
1171
|
// Cursor probes MCP server availability before emitting an MCP-backed tool
|
|
1154
|
-
// call. OpenCode owns those servers, so answer from the
|
|
1155
|
-
//
|
|
1172
|
+
// call. OpenCode owns those servers, so answer from the live host catalog
|
|
1173
|
+
// rather than surfacing this as a user tool.
|
|
1156
1174
|
addType(root, "McpStateExecArgs", [
|
|
1157
1175
|
{ id: 1, name: "server_identifiers", type: "string", repeated: true },
|
|
1158
1176
|
{ id: 2, name: "kick_only", type: "bool" },
|
|
@@ -1334,8 +1352,8 @@ export function createMessageTypes() {
|
|
|
1334
1352
|
{ id: 2, name: "message_id", type: "string" },
|
|
1335
1353
|
{ id: 3, name: "selected_context", type: "SelectedContext" },
|
|
1336
1354
|
]);
|
|
1337
|
-
// RequestContext — UserMessageAction #2.
|
|
1338
|
-
//
|
|
1355
|
+
// RequestContext — UserMessageAction #2. Slim mcp_meta_tool_options names
|
|
1356
|
+
// the advertised catalog; AgentRunRequest.mcp_tools (#4) is empty on real turns.
|
|
1339
1357
|
addType(root, "RequestContext", [
|
|
1340
1358
|
{ id: 2, name: "rules", type: "CursorRule", repeated: true },
|
|
1341
1359
|
{ id: 4, name: "env", type: "RequestContextEnv" },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { OpencodeToolDef } from "./tools.js";
|
|
2
2
|
import type { CursorImageInput } from "../image-input.js";
|
|
3
3
|
export type SeedHistoryMessage = {
|
|
4
4
|
role: "system" | "user" | "assistant";
|
|
@@ -11,7 +11,6 @@ export type RunRequestInput = {
|
|
|
11
11
|
conversationId: string;
|
|
12
12
|
/** Stable parent group; unlike conversationId, this survives compaction/rebase. */
|
|
13
13
|
conversationGroupId?: string;
|
|
14
|
-
systemPrompt?: string;
|
|
15
14
|
/**
|
|
16
15
|
* Prior chat turns for a seed ConversationStateStructure (no checkpoint).
|
|
17
16
|
* Tool outputs, when required for compaction/recovery, are represented as
|
|
@@ -22,7 +21,7 @@ export type RunRequestInput = {
|
|
|
22
21
|
* Opaque ConversationStateStructure bytes from the last
|
|
23
22
|
* conversation_checkpoint_update for this conversation_id. When set, echoed
|
|
24
23
|
* as AgentRunRequest.conversation_state (CLI parity). When absent, a seed
|
|
25
|
-
* state
|
|
24
|
+
* state carrying only `history` (or empty) is built for turn 1.
|
|
26
25
|
*/
|
|
27
26
|
conversationState?: Uint8Array;
|
|
28
27
|
parameterValues?: Array<{
|
|
@@ -42,19 +41,22 @@ export type RunRequestInput = {
|
|
|
42
41
|
/**
|
|
43
42
|
* Seed ConversationStateStructure for the first turn (no checkpoint yet).
|
|
44
43
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
44
|
+
* After the first checkpoint arrives we stop inventing state and echo the
|
|
45
|
+
* server's opaque structure instead (CLI behavior). Compaction resets and
|
|
46
|
+
* rebases also use this seed, with `history` carrying OpenCode's prompt turns
|
|
47
|
+
* so Cursor can continue without the old checkpoint.
|
|
48
48
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
49
|
+
* No `system` entry is seeded: Cursor does not follow a client-authored
|
|
50
|
+
* `system` root message, and neither Cursor client writes
|
|
51
|
+
* `root_prompt_messages_json`. Host system context travels as the
|
|
52
|
+
* system-instructions rule in RequestContext (`systemInstructionsRule`), so
|
|
53
|
+
* `system` history entries are dropped here rather than duplicated.
|
|
51
54
|
*
|
|
52
55
|
* We deliberately do NOT use `AgentRunRequest.custom_system_prompt` (#8): that
|
|
53
56
|
* field is the internal `--system-prompt` CLI override and the server rejects
|
|
54
57
|
* it for normal accounts.
|
|
55
58
|
*/
|
|
56
59
|
export declare function buildSeedConversationState(input?: {
|
|
57
|
-
systemPrompt?: string;
|
|
58
60
|
history?: SeedHistoryMessage[];
|
|
59
61
|
}): Uint8Array;
|
|
60
62
|
/**
|
package/dist/protocol/request.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { encodeMessage, getMessageTypes } from "./messages.js";
|
|
2
|
-
import { toolsToDescriptors } from "./tools.js";
|
|
3
2
|
/**
|
|
4
3
|
* Seed ConversationStateStructure for the first turn (no checkpoint yet).
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* After the first checkpoint arrives we stop inventing state and echo the
|
|
6
|
+
* server's opaque structure instead (CLI behavior). Compaction resets and
|
|
7
|
+
* rebases also use this seed, with `history` carrying OpenCode's prompt turns
|
|
8
|
+
* so Cursor can continue without the old checkpoint.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* No `system` entry is seeded: Cursor does not follow a client-authored
|
|
11
|
+
* `system` root message, and neither Cursor client writes
|
|
12
|
+
* `root_prompt_messages_json`. Host system context travels as the
|
|
13
|
+
* system-instructions rule in RequestContext (`systemInstructionsRule`), so
|
|
14
|
+
* `system` history entries are dropped here rather than duplicated.
|
|
12
15
|
*
|
|
13
16
|
* We deliberately do NOT use `AgentRunRequest.custom_system_prompt` (#8): that
|
|
14
17
|
* field is the internal `--system-prompt` CLI override and the server rejects
|
|
@@ -18,14 +21,8 @@ export function buildSeedConversationState(input) {
|
|
|
18
21
|
const root = getMessageTypes();
|
|
19
22
|
const type = root.lookupType("ConversationStateStructure");
|
|
20
23
|
const messages = [];
|
|
21
|
-
if (input?.systemPrompt && input.systemPrompt.length > 0) {
|
|
22
|
-
messages.push(JSON.stringify({ role: "system", content: input.systemPrompt }));
|
|
23
|
-
}
|
|
24
24
|
for (const entry of input?.history ?? []) {
|
|
25
|
-
if (!entry.content)
|
|
26
|
-
continue;
|
|
27
|
-
// Avoid duplicating the system prompt when history also carries one.
|
|
28
|
-
if (entry.role === "system" && input?.systemPrompt)
|
|
25
|
+
if (!entry.content || entry.role === "system")
|
|
29
26
|
continue;
|
|
30
27
|
messages.push(JSON.stringify({ role: entry.role, content: entry.content }));
|
|
31
28
|
}
|
|
@@ -42,11 +39,9 @@ export function buildSeedConversationState(input) {
|
|
|
42
39
|
*/
|
|
43
40
|
export function buildRunRequest(input) {
|
|
44
41
|
const msgId = input.messageId ?? crypto.randomUUID();
|
|
45
|
-
// Advertise
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
const tools = input.tools ?? [];
|
|
49
|
-
const mcpTools = input.toolDescriptors ?? (tools.length > 0 ? toolsToDescriptors(tools) : []);
|
|
42
|
+
// Advertise host tools on UserMessageAction.request_context (#2) via slim
|
|
43
|
+
// mcp_meta_tool_options. AgentRunRequest.mcp_tools (#4) stays empty on real
|
|
44
|
+
// turns (CLI prewarm-only). Full defs are session.toolDescriptors + exec #36.
|
|
50
45
|
const requestContext = input.requestContext;
|
|
51
46
|
const userMessage = {
|
|
52
47
|
text: input.text,
|
|
@@ -70,10 +65,7 @@ export function buildRunRequest(input) {
|
|
|
70
65
|
: { user_message_action: userMessageAction };
|
|
71
66
|
const conversationState = input.conversationState && input.conversationState.length > 0
|
|
72
67
|
? input.conversationState
|
|
73
|
-
: buildSeedConversationState({
|
|
74
|
-
systemPrompt: input.systemPrompt,
|
|
75
|
-
history: input.history,
|
|
76
|
-
});
|
|
68
|
+
: buildSeedConversationState({ history: input.history });
|
|
77
69
|
const runRequest = {
|
|
78
70
|
conversation_id: input.conversationId,
|
|
79
71
|
conversation_group_id: input.conversationGroupId ?? input.conversationId,
|
|
@@ -88,9 +80,7 @@ export function buildRunRequest(input) {
|
|
|
88
80
|
parameters: input.parameterValues ?? [],
|
|
89
81
|
},
|
|
90
82
|
conversation_state: conversationState,
|
|
91
|
-
|
|
92
|
-
// older server builds that still read it).
|
|
93
|
-
mcp_tools: { mcp_tools: mcpTools },
|
|
83
|
+
mcp_tools: { mcp_tools: [] },
|
|
94
84
|
unknown_flag: 0,
|
|
95
85
|
field_12: 0,
|
|
96
86
|
};
|
package/dist/protocol/tools.d.ts
CHANGED
|
@@ -61,7 +61,9 @@ export declare function sanitizeMcpServerId(value: string): string;
|
|
|
61
61
|
export declare function resolveToolServerIdentity(opencodeName: string, defaultServer?: string, knownMcpServers?: Iterable<string>): ToolServerIdentity;
|
|
62
62
|
/**
|
|
63
63
|
* Convert opencode's per-turn tool list into Cursor `McpToolDefinition`
|
|
64
|
-
* entries for
|
|
64
|
+
* entries for session exec remap / bridges. These are not sent on
|
|
65
|
+
* RequestContext.tools (#7) or AgentRunRequest.mcp_tools (both omitted/empty
|
|
66
|
+
* on the wire). Exec #36 still uses the same identity fields.
|
|
65
67
|
*
|
|
66
68
|
* Builtins and unknown plugin/custom tools are advertised under the synthetic
|
|
67
69
|
* default server (`opencode`). Tools whose prefixes match configured MCP
|
|
@@ -79,12 +81,13 @@ export declare function toolsToDescriptors(tools: OpencodeToolDef[], providerIde
|
|
|
79
81
|
export declare function buildCustomWebToolAliases(tools: OpencodeToolDef[]): AliasedToolCatalog;
|
|
80
82
|
export declare function resolveCustomWebToolAlias(toolName: string, aliases: ToolAliasRegistry | undefined): string;
|
|
81
83
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* their upstream server id).
|
|
84
|
+
* Nested MCP descriptors. Full name/description/schema is for exec #36.
|
|
85
|
+
* RequestContext `mcp_meta_tool_options` uses `{ namesOnly: true }` (`tool_name`
|
|
86
|
+
* only). File-system `mcp_descriptors` are omitted on the wire.
|
|
86
87
|
*/
|
|
87
|
-
export declare function toolsToMcpDescriptors(tools: OpencodeToolDef[], providerIdentifier?: string, knownMcpServers?: Iterable<string
|
|
88
|
+
export declare function toolsToMcpDescriptors(tools: OpencodeToolDef[], providerIdentifier?: string, knownMcpServers?: Iterable<string>, options?: {
|
|
89
|
+
namesOnly?: boolean;
|
|
90
|
+
}): Array<Record<string, unknown>>;
|
|
88
91
|
/**
|
|
89
92
|
* @deprecated Prefer `buildRequestContext` from `../context/build.js`.
|
|
90
93
|
* Kept as a sync tools-only fallback for unit tests that don't need collectors.
|
|
@@ -340,11 +343,15 @@ export declare function detectExecVariantField(agentServerPayload: Uint8Array):
|
|
|
340
343
|
*/
|
|
341
344
|
export declare function buildRequestContextResult(execId: number, requestContext: Record<string, unknown>): Uint8Array;
|
|
342
345
|
/**
|
|
343
|
-
* Answer Cursor's exec #36 MCP-state probe from the
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
+
* Answer Cursor's exec #36 MCP-state probe from the session's live tool
|
|
347
|
+
* descriptors: `toolsToDescriptors` output for the advertised catalog (after
|
|
348
|
+
* web-tool aliasing), refreshed on every `doStream`. These are the same
|
|
349
|
+
* identities the names-only RequestContext advertises and exec remap uses, so
|
|
350
|
+
* Cursor's native get_mcp_tools can correlate the later
|
|
351
|
+
* provider_identifier/tool_name request. OpenCode remains the executor; this
|
|
352
|
+
* only confirms those tools are available, with full name/description/schema.
|
|
346
353
|
*/
|
|
347
|
-
export declare function buildMcpStateResult(execId: number, args: Record<string, unknown>,
|
|
354
|
+
export declare function buildMcpStateResult(execId: number, args: Record<string, unknown>, toolDescriptors: ReadonlyArray<Record<string, unknown>>): Uint8Array;
|
|
348
355
|
/**
|
|
349
356
|
* Total fallback for Cursor's native MCP-resource exec channel (agent.v1
|
|
350
357
|
* fields #17/#18, tasks/plans/fix-cursor-mcp-resource-exec.md). Under Option B
|
package/dist/protocol/tools.js
CHANGED
|
@@ -132,7 +132,9 @@ export function resolveToolServerIdentity(opencodeName, defaultServer = "opencod
|
|
|
132
132
|
}
|
|
133
133
|
/**
|
|
134
134
|
* Convert opencode's per-turn tool list into Cursor `McpToolDefinition`
|
|
135
|
-
* entries for
|
|
135
|
+
* entries for session exec remap / bridges. These are not sent on
|
|
136
|
+
* RequestContext.tools (#7) or AgentRunRequest.mcp_tools (both omitted/empty
|
|
137
|
+
* on the wire). Exec #36 still uses the same identity fields.
|
|
136
138
|
*
|
|
137
139
|
* Builtins and unknown plugin/custom tools are advertised under the synthetic
|
|
138
140
|
* default server (`opencode`). Tools whose prefixes match configured MCP
|
|
@@ -249,15 +251,15 @@ export function resolveCustomWebToolAlias(toolName, aliases) {
|
|
|
249
251
|
return toolName;
|
|
250
252
|
}
|
|
251
253
|
/**
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
* their upstream server id).
|
|
254
|
+
* Nested MCP descriptors. Full name/description/schema is for exec #36.
|
|
255
|
+
* RequestContext `mcp_meta_tool_options` uses `{ namesOnly: true }` (`tool_name`
|
|
256
|
+
* only). File-system `mcp_descriptors` are omitted on the wire.
|
|
256
257
|
*/
|
|
257
|
-
export function toolsToMcpDescriptors(tools, providerIdentifier = "opencode", knownMcpServers = []) {
|
|
258
|
+
export function toolsToMcpDescriptors(tools, providerIdentifier = "opencode", knownMcpServers = [], options) {
|
|
258
259
|
if (tools.length === 0)
|
|
259
260
|
return [];
|
|
260
261
|
const byServer = new Map();
|
|
262
|
+
const namesOnly = options?.namesOnly === true;
|
|
261
263
|
// Walk advertised order: first-seen server, then append tools onto that
|
|
262
264
|
// server. Same-set host reorder is resolved by `resolveTurnToolState`
|
|
263
265
|
// before encode so this walk stays epoch-stable.
|
|
@@ -268,11 +270,14 @@ export function toolsToMcpDescriptors(tools, providerIdentifier = "opencode", kn
|
|
|
268
270
|
list = [];
|
|
269
271
|
byServer.set(id.server, list);
|
|
270
272
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
273
|
+
const toolName = t.sourceName ? t.name : id.toolName;
|
|
274
|
+
list.push(namesOnly
|
|
275
|
+
? { tool_name: toolName }
|
|
276
|
+
: {
|
|
277
|
+
tool_name: toolName,
|
|
278
|
+
description: t.description ?? "",
|
|
279
|
+
input_schema: encodeJsonAsValue(normalizeInputSchema(t.inputSchema)),
|
|
280
|
+
});
|
|
276
281
|
}
|
|
277
282
|
return [...byServer.keys()].map((server) => ({
|
|
278
283
|
server_name: server,
|
|
@@ -285,20 +290,17 @@ export function toolsToMcpDescriptors(tools, providerIdentifier = "opencode", kn
|
|
|
285
290
|
* Kept as a sync tools-only fallback for unit tests that don't need collectors.
|
|
286
291
|
*/
|
|
287
292
|
export function buildLiveRequestContext(tools, providerIdentifier = "opencode", knownMcpServers = []) {
|
|
288
|
-
const
|
|
289
|
-
const nested = toolsToMcpDescriptors(tools, providerIdentifier, knownMcpServers);
|
|
293
|
+
const slim = toolsToMcpDescriptors(tools, providerIdentifier, knownMcpServers, { namesOnly: true });
|
|
290
294
|
const cwd = process.cwd();
|
|
291
295
|
const ctx = {
|
|
292
296
|
env: buildEnv(cwd),
|
|
293
|
-
tools: flat,
|
|
294
297
|
mcp_file_system_options: {
|
|
295
298
|
enabled: true,
|
|
296
299
|
workspace_project_dir: ensureOpencodeProjectDir(cwd),
|
|
297
|
-
mcp_descriptors: nested,
|
|
298
300
|
},
|
|
299
301
|
mcp_meta_tool_options: {
|
|
300
302
|
enabled: true,
|
|
301
|
-
mcp_descriptors:
|
|
303
|
+
...(slim.length > 0 ? { mcp_descriptors: slim } : {}),
|
|
302
304
|
},
|
|
303
305
|
web_search_enabled: false,
|
|
304
306
|
web_fetch_enabled: false,
|
|
@@ -3196,41 +3198,45 @@ export function buildRequestContextResult(execId, requestContext) {
|
|
|
3196
3198
|
});
|
|
3197
3199
|
}
|
|
3198
3200
|
/**
|
|
3199
|
-
* Answer Cursor's exec #36 MCP-state probe from the
|
|
3200
|
-
*
|
|
3201
|
-
*
|
|
3201
|
+
* Answer Cursor's exec #36 MCP-state probe from the session's live tool
|
|
3202
|
+
* descriptors: `toolsToDescriptors` output for the advertised catalog (after
|
|
3203
|
+
* web-tool aliasing), refreshed on every `doStream`. These are the same
|
|
3204
|
+
* identities the names-only RequestContext advertises and exec remap uses, so
|
|
3205
|
+
* Cursor's native get_mcp_tools can correlate the later
|
|
3206
|
+
* provider_identifier/tool_name request. OpenCode remains the executor; this
|
|
3207
|
+
* only confirms those tools are available, with full name/description/schema.
|
|
3202
3208
|
*/
|
|
3203
|
-
export function buildMcpStateResult(execId, args,
|
|
3209
|
+
export function buildMcpStateResult(execId, args, toolDescriptors) {
|
|
3204
3210
|
const requested = new Set(Array.isArray(args.server_identifiers)
|
|
3205
3211
|
? args.server_identifiers.filter((id) => typeof id === "string" && id.length > 0)
|
|
3206
3212
|
: []);
|
|
3207
|
-
|
|
3208
|
-
const
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
.
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
});
|
|
3213
|
+
// Group in advertised order: first-seen server, then its tools.
|
|
3214
|
+
const byServer = new Map();
|
|
3215
|
+
for (const tool of toolDescriptors) {
|
|
3216
|
+
const server = stringValue(tool.provider_identifier);
|
|
3217
|
+
const toolName = stringValue(tool.tool_name);
|
|
3218
|
+
if (!server || !toolName)
|
|
3219
|
+
continue;
|
|
3220
|
+
if (requested.size > 0 && !requested.has(server))
|
|
3221
|
+
continue;
|
|
3222
|
+
let list = byServer.get(server);
|
|
3223
|
+
if (!list) {
|
|
3224
|
+
list = [];
|
|
3225
|
+
byServer.set(server, list);
|
|
3226
|
+
}
|
|
3227
|
+
list.push({
|
|
3228
|
+
name: stringValue(tool.name) ?? `${server}-${toolName}`,
|
|
3229
|
+
description: stringValue(tool.description) ?? "",
|
|
3230
|
+
input_schema: tool.input_schema,
|
|
3231
|
+
provider_identifier: server,
|
|
3232
|
+
tool_name: toolName,
|
|
3233
|
+
});
|
|
3234
|
+
}
|
|
3235
|
+
const servers = [...byServer].map(([server, tools]) => ({
|
|
3236
|
+
server_name: server,
|
|
3237
|
+
server_identifier: server,
|
|
3238
|
+
tools,
|
|
3239
|
+
}));
|
|
3234
3240
|
return encodeMessage("AgentClientMessage", {
|
|
3235
3241
|
exec_client_message: {
|
|
3236
3242
|
id: execId,
|
|
@@ -3238,24 +3244,6 @@ export function buildMcpStateResult(execId, args, requestContext) {
|
|
|
3238
3244
|
},
|
|
3239
3245
|
});
|
|
3240
3246
|
}
|
|
3241
|
-
/**
|
|
3242
|
-
* Exec #36 uses McpToolDefinition, not the narrower McpToolDescriptor used by
|
|
3243
|
-
* RequestContext's filesystem/meta-tool catalogs. Rehydrate the full identity
|
|
3244
|
-
* from RequestContext.tools so Cursor's native get_mcp_tools can correlate the
|
|
3245
|
-
* discovered definition with the later provider_identifier/tool_name request.
|
|
3246
|
-
*/
|
|
3247
|
-
function mcpStateToolDefinition(serverIdentifier, descriptor, flatTools) {
|
|
3248
|
-
const toolName = stringValue(descriptor.tool_name) ?? "";
|
|
3249
|
-
const advertised = flatTools.find((tool) => stringValue(tool.provider_identifier) === serverIdentifier
|
|
3250
|
-
&& stringValue(tool.tool_name) === toolName);
|
|
3251
|
-
return {
|
|
3252
|
-
name: stringValue(advertised?.name) ?? `${serverIdentifier}-${toolName}`,
|
|
3253
|
-
description: stringValue(advertised?.description) ?? stringValue(descriptor.description) ?? "",
|
|
3254
|
-
input_schema: advertised?.input_schema ?? descriptor.input_schema,
|
|
3255
|
-
provider_identifier: serverIdentifier,
|
|
3256
|
-
tool_name: toolName,
|
|
3257
|
-
};
|
|
3258
|
-
}
|
|
3259
3247
|
/**
|
|
3260
3248
|
* Total fallback for Cursor's native MCP-resource exec channel (agent.v1
|
|
3261
3249
|
* fields #17/#18, tasks/plans/fix-cursor-mcp-resource-exec.md). Under Option B
|
|
@@ -3285,31 +3273,3 @@ export function buildReadMcpResourceFallback(execId, server, uri) {
|
|
|
3285
3273
|
},
|
|
3286
3274
|
});
|
|
3287
3275
|
}
|
|
3288
|
-
function recordValue(value) {
|
|
3289
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
3290
|
-
? value
|
|
3291
|
-
: undefined;
|
|
3292
|
-
}
|
|
3293
|
-
function descriptorsFromFlatTools(value) {
|
|
3294
|
-
if (!Array.isArray(value))
|
|
3295
|
-
return [];
|
|
3296
|
-
const byServer = new Map();
|
|
3297
|
-
for (const raw of value) {
|
|
3298
|
-
const tool = recordValue(raw);
|
|
3299
|
-
if (!tool)
|
|
3300
|
-
continue;
|
|
3301
|
-
const server = stringValue(tool.provider_identifier) ?? "opencode";
|
|
3302
|
-
const tools = byServer.get(server) ?? [];
|
|
3303
|
-
tools.push({
|
|
3304
|
-
tool_name: stringValue(tool.tool_name) ?? stringValue(tool.name) ?? "",
|
|
3305
|
-
description: stringValue(tool.description) ?? "",
|
|
3306
|
-
input_schema: tool.input_schema,
|
|
3307
|
-
});
|
|
3308
|
-
byServer.set(server, tools);
|
|
3309
|
-
}
|
|
3310
|
-
return [...byServer].map(([server, tools]) => ({
|
|
3311
|
-
server_name: server,
|
|
3312
|
-
server_identifier: server,
|
|
3313
|
-
tools,
|
|
3314
|
-
}));
|
|
3315
|
-
}
|