cursor-opencode-provider 0.7.6 → 0.8.1
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 +79 -0
- package/README.md +36 -15
- package/SECURITY.md +43 -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 +62 -7
- package/dist/context/build.js +127 -95
- package/dist/context/dynamic-catalog.d.ts +2 -58
- package/dist/context/dynamic-catalog.js +16 -161
- package/dist/context/epoch.d.ts +6 -4
- package/dist/context/epoch.js +22 -10
- package/dist/context/frozen.js +42 -15
- package/dist/context/index.d.ts +3 -1
- package/dist/context/index.js +3 -1
- package/dist/context/overlay.d.ts +2 -23
- package/dist/context/overlay.js +6 -30
- package/dist/context/paths.d.ts +51 -21
- package/dist/context/paths.js +78 -27
- package/dist/context/rules.d.ts +3 -15
- package/dist/context/rules.js +10 -186
- package/dist/context/skills-bridge.d.ts +22 -0
- package/dist/context/skills-bridge.js +20 -0
- package/dist/context/skills.d.ts +31 -8
- package/dist/context/skills.js +187 -86
- 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.d.ts +53 -4
- package/dist/host-agent-mode.js +119 -15
- package/dist/host-plan-file.d.ts +28 -0
- package/dist/host-plan-file.js +76 -0
- package/dist/image-input.d.ts +18 -0
- package/dist/image-input.js +119 -28
- package/dist/image-save.d.ts +10 -2
- package/dist/image-save.js +35 -23
- package/dist/index.d.ts +10 -0
- package/dist/language-model.d.ts +86 -25
- package/dist/language-model.js +1295 -369
- package/dist/model-config.d.ts +9 -0
- package/dist/model-config.js +28 -1
- package/dist/opencode2/catalog.d.ts +1 -0
- package/dist/opencode2/catalog.js +2 -0
- package/dist/opencode2/image-save-tool.d.ts +8 -0
- package/dist/opencode2/image-save-tool.js +84 -0
- package/dist/opencode2/integration.d.ts +30 -7
- package/dist/opencode2/integration.js +75 -46
- package/dist/opencode2/types.d.ts +15 -0
- package/dist/parallel-step.d.ts +65 -0
- package/dist/parallel-step.js +102 -0
- package/dist/plugin-opencode2.js +58 -56
- package/dist/plugin.d.ts +0 -10
- package/dist/plugin.js +207 -127
- package/dist/pricing-data.d.ts +14 -1
- package/dist/pricing-data.js +14 -1
- package/dist/protocol/ask-question.d.ts +12 -1
- package/dist/protocol/ask-question.js +77 -3
- 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.d.ts +47 -25
- package/dist/protocol/create-plan.js +35 -24
- package/dist/protocol/interactions.d.ts +26 -17
- package/dist/protocol/interactions.js +80 -31
- package/dist/protocol/messages.js +96 -18
- package/dist/protocol/request.d.ts +16 -9
- package/dist/protocol/request.js +17 -25
- package/dist/protocol/switch-mode.d.ts +66 -27
- package/dist/protocol/switch-mode.js +147 -73
- package/dist/protocol/token-details.d.ts +2 -0
- package/dist/protocol/token-details.js +11 -1
- package/dist/protocol/tool-call-bridge.d.ts +4 -0
- package/dist/protocol/tool-call-bridge.js +14 -0
- package/dist/protocol/tools.d.ts +33 -11
- package/dist/protocol/tools.js +165 -103
- package/dist/protocol/turn-provenance.d.ts +52 -0
- package/dist/protocol/turn-provenance.js +174 -0
- package/dist/replay-safety.d.ts +6 -0
- package/dist/replay-safety.js +56 -6
- package/dist/session.d.ts +44 -2
- package/dist/session.js +45 -1
- package/dist/shared.d.ts +7 -0
- package/dist/shared.js +7 -0
- package/dist/todo-store.d.ts +2 -0
- package/dist/todo-store.js +19 -0
- package/dist/transport/connect.d.ts +2 -0
- package/dist/transport/connect.js +23 -6
- package/dist/usage.js +23 -6
- package/package.json +3 -2
- package/dist/context/agents.d.ts +0 -12
- package/dist/context/agents.js +0 -136
- package/dist/plan-execution-kickoff.d.ts +0 -57
- package/dist/plan-execution-kickoff.js +0 -186
package/dist/context/build.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type OpencodeToolDef } from "../protocol/tools.js";
|
|
2
2
|
import { type OpencodeJson } from "./rules.js";
|
|
3
|
+
import { hostSkillFiles, loadBridgeSkills } from "./skills.js";
|
|
3
4
|
export type BuildRequestContextInput = {
|
|
4
5
|
workspaceRoot: string;
|
|
5
6
|
tools?: OpencodeToolDef[];
|
|
@@ -11,20 +12,74 @@ export type BuildRequestContextInput = {
|
|
|
11
12
|
* guidance MCP server ids). Skips a second `loadMergedConfig` disk read.
|
|
12
13
|
*/
|
|
13
14
|
mergedConfig?: OpencodeJson;
|
|
15
|
+
/** Host system context to deliver as the frozen system-instructions rule. */
|
|
16
|
+
systemInstructions?: SystemInstructions;
|
|
17
|
+
/** Optional host session id for `opencode.host.skills` list(). */
|
|
18
|
+
sessionID?: string;
|
|
14
19
|
};
|
|
15
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Host system context (OpenCode's system prompt plus this provider's
|
|
22
|
+
* interaction guidance) for one Cursor conversation.
|
|
23
|
+
*/
|
|
24
|
+
export type SystemInstructions = {
|
|
25
|
+
text: string;
|
|
26
|
+
/**
|
|
27
|
+
* True when `text` is this conversation's own frozen baseline (a Context
|
|
28
|
+
* Epoch started in this process, or an ephemeral Run's prompt): it replaces
|
|
29
|
+
* any rule already frozen. False when a restart recovered the epoch without
|
|
30
|
+
* its bytes: the persisted rule wins and `text` only fills a missing one.
|
|
31
|
+
*/
|
|
32
|
+
authoritative: boolean;
|
|
33
|
+
};
|
|
34
|
+
/** Rule name Cursor shows for the host system context (not a file). */
|
|
35
|
+
export declare const SYSTEM_INSTRUCTIONS_RULE_PATH = "OpenCode system instructions";
|
|
36
|
+
/**
|
|
37
|
+
* Cursor does not follow a client-seeded `system` message, and applies a rule
|
|
38
|
+
* only by its type. The Cursor CLI sends AGENTS.md as `alwaysApply`
|
|
39
|
+
* (`CursorRuleType.global`) and the IDE ships its own guidance as a non-file
|
|
40
|
+
* global rule; the host system context goes out the same way, once, frozen
|
|
41
|
+
* with the RequestContext base.
|
|
42
|
+
*/
|
|
43
|
+
export declare function systemInstructionsRule(text: string): Record<string, unknown>;
|
|
44
|
+
/** Text of the frozen system-instructions rule, if the context carries one. */
|
|
45
|
+
export declare function systemInstructionsRuleText(context: Record<string, unknown>): string | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Return `context` with the system-instructions rule applied, or the same
|
|
48
|
+
* object when nothing changes (see `SystemInstructions.authoritative`).
|
|
49
|
+
*/
|
|
50
|
+
export declare function withSystemInstructions(context: Record<string, unknown>, instructions: SystemInstructions | undefined): Record<string, unknown>;
|
|
51
|
+
export declare const DYNAMIC_REQUEST_CONTEXT_KEYS: readonly ["tools", "custom_subagents", "mcp_file_system_options", "mcp_meta_tool_options", "web_search_enabled", "web_fetch_enabled", "custom_subagents_info_complete", "mcp_file_system_info_complete", "mcp_info_complete", "hooks_additional_context"];
|
|
16
52
|
export type DynamicRequestContextKey = typeof DYNAMIC_REQUEST_CONTEXT_KEYS[number];
|
|
53
|
+
/**
|
|
54
|
+
* Derived from the system-instructions rule on every materialization (see
|
|
55
|
+
* `skills.ts`); never kept on a frozen base.
|
|
56
|
+
*/
|
|
57
|
+
export declare const HOST_DUPLICATED_REQUEST_CONTEXT_KEYS: readonly ["agent_skills", "agent_skills_info_complete"];
|
|
17
58
|
/**
|
|
18
59
|
* Full RequestContext payload for live UMA + exec #10 reply.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* user's own OpenCode config lists it, exactly as the host would read it.
|
|
60
|
+
* Workspace env/git/layout, the host system-instructions rule, and
|
|
61
|
+
* host-advertised tools and subagents. The provider never looks in Cursor's
|
|
62
|
+
* own directories.
|
|
23
63
|
*/
|
|
24
64
|
export declare function buildRequestContext(input: BuildRequestContextInput): Promise<Record<string, unknown>>;
|
|
65
|
+
/** Locations for path-desc materialization (bridge, then remembered OC2 files). */
|
|
66
|
+
export declare function resolveSkillLocations(input: Pick<BuildRequestContextInput, "tools" | "sessionID">, workspaceRoot: string): Promise<{
|
|
67
|
+
bridgeSkills?: Awaited<ReturnType<typeof loadBridgeSkills>>;
|
|
68
|
+
skillFiles?: ReturnType<typeof hostSkillFiles>;
|
|
69
|
+
}>;
|
|
25
70
|
/** Rediscover only capability/plugin sections that may change during a chat. */
|
|
26
71
|
export declare function buildDynamicRequestContext(input: BuildRequestContextInput): Promise<Record<string, unknown>>;
|
|
27
|
-
|
|
28
|
-
|
|
72
|
+
export type MaterializeRequestContextOptions = {
|
|
73
|
+
tools?: OpencodeToolDef[];
|
|
74
|
+
skillFiles?: ReadonlyMap<string, string>;
|
|
75
|
+
bridgeSkills?: Awaited<ReturnType<typeof loadBridgeSkills>>;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Keep expensive workspace state frozen while replacing every live capability
|
|
79
|
+
* field. Skill locations (bridge / OpenCode 2 files / OpenCode 1 catalog
|
|
80
|
+
* paths) turn the skill catalog in the system-instructions rule into Cursor's
|
|
81
|
+
* path-desc `agent_skills`.
|
|
82
|
+
*/
|
|
83
|
+
export declare function materializeRequestContext(base: Record<string, unknown>, dynamic: Record<string, unknown>, options?: MaterializeRequestContextOptions): Record<string, unknown>;
|
|
29
84
|
/** Strip live capability fields before retaining/persisting a conversation base. */
|
|
30
85
|
export declare function requestContextBase(context: Record<string, unknown>): Record<string, unknown>;
|
package/dist/context/build.js
CHANGED
|
@@ -1,84 +1,116 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { extractHostSubagentCatalog,
|
|
3
|
-
import {
|
|
4
|
-
import { collectSkills } from "./skills.js";
|
|
5
|
-
import { collectAgents } from "./agents.js";
|
|
2
|
+
import { extractHostSubagentCatalog, toolsToMcpDescriptors, } from "../protocol/tools.js";
|
|
3
|
+
import { loadMergedConfig, } from "./rules.js";
|
|
6
4
|
import { collectPlugins } from "./plugins.js";
|
|
7
5
|
import { collectGit } from "./git.js";
|
|
8
6
|
import { collectProjectLayout } from "./layout.js";
|
|
9
7
|
import { buildEnv } from "./env.js";
|
|
10
8
|
import { ensureOpencodeProjectDir } from "./paths.js";
|
|
11
9
|
import { holdCapabilityOverlay } from "./overlay.js";
|
|
10
|
+
import { agentSkillsForCursor, applyAgentSkillsToContext, hostSkillFiles, loadBridgeSkills, skillToolAdvertised, } from "./skills.js";
|
|
12
11
|
import { traceRequestContextPaths } from "../debug.js";
|
|
12
|
+
/** Rule name Cursor shows for the host system context (not a file). */
|
|
13
|
+
export const SYSTEM_INSTRUCTIONS_RULE_PATH = "OpenCode system instructions";
|
|
14
|
+
/**
|
|
15
|
+
* Cursor does not follow a client-seeded `system` message, and applies a rule
|
|
16
|
+
* only by its type. The Cursor CLI sends AGENTS.md as `alwaysApply`
|
|
17
|
+
* (`CursorRuleType.global`) and the IDE ships its own guidance as a non-file
|
|
18
|
+
* global rule; the host system context goes out the same way, once, frozen
|
|
19
|
+
* with the RequestContext base.
|
|
20
|
+
*/
|
|
21
|
+
export function systemInstructionsRule(text) {
|
|
22
|
+
return {
|
|
23
|
+
full_path: SYSTEM_INSTRUCTIONS_RULE_PATH,
|
|
24
|
+
content: text,
|
|
25
|
+
type: { global: {} },
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function isSystemInstructionsRule(rule) {
|
|
29
|
+
if (!rule || typeof rule !== "object")
|
|
30
|
+
return false;
|
|
31
|
+
const record = rule;
|
|
32
|
+
const type = record.type;
|
|
33
|
+
return record.full_path === SYSTEM_INSTRUCTIONS_RULE_PATH && !!type?.global
|
|
34
|
+
&& typeof record.content === "string";
|
|
35
|
+
}
|
|
36
|
+
/** Text of the frozen system-instructions rule, if the context carries one. */
|
|
37
|
+
export function systemInstructionsRuleText(context) {
|
|
38
|
+
const rules = Array.isArray(context.rules) ? context.rules : [];
|
|
39
|
+
const rule = rules.find(isSystemInstructionsRule);
|
|
40
|
+
return rule ? rule.content : undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Return `context` with the system-instructions rule applied, or the same
|
|
44
|
+
* object when nothing changes (see `SystemInstructions.authoritative`).
|
|
45
|
+
*/
|
|
46
|
+
export function withSystemInstructions(context, instructions) {
|
|
47
|
+
const text = instructions?.text.trim() ? instructions.text : undefined;
|
|
48
|
+
if (!text)
|
|
49
|
+
return context;
|
|
50
|
+
const current = systemInstructionsRuleText(context);
|
|
51
|
+
if (current !== undefined && (current === text || !instructions.authoritative))
|
|
52
|
+
return context;
|
|
53
|
+
return { ...context, rules: [systemInstructionsRule(text)] };
|
|
54
|
+
}
|
|
55
|
+
/** Keep only the system-instructions rule; older bases carried untyped rules Cursor never applied. */
|
|
56
|
+
function keepSystemInstructionsRule(context) {
|
|
57
|
+
if (!Object.hasOwn(context, "rules"))
|
|
58
|
+
return;
|
|
59
|
+
const rules = Array.isArray(context.rules) ? context.rules.filter(isSystemInstructionsRule) : [];
|
|
60
|
+
if (rules.length > 0)
|
|
61
|
+
context.rules = rules.slice(0, 1);
|
|
62
|
+
else
|
|
63
|
+
delete context.rules;
|
|
64
|
+
}
|
|
13
65
|
export const DYNAMIC_REQUEST_CONTEXT_KEYS = [
|
|
14
66
|
"tools",
|
|
15
|
-
"agent_skills",
|
|
16
67
|
"custom_subagents",
|
|
17
68
|
"mcp_file_system_options",
|
|
18
69
|
"mcp_meta_tool_options",
|
|
19
70
|
"web_search_enabled",
|
|
20
71
|
"web_fetch_enabled",
|
|
21
|
-
"agent_skills_info_complete",
|
|
22
72
|
"custom_subagents_info_complete",
|
|
23
73
|
"mcp_file_system_info_complete",
|
|
24
74
|
"mcp_info_complete",
|
|
25
75
|
"hooks_additional_context",
|
|
26
76
|
];
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
description: "Read-only agent for searching and understanding the local codebase.",
|
|
35
|
-
},
|
|
77
|
+
/**
|
|
78
|
+
* Derived from the system-instructions rule on every materialization (see
|
|
79
|
+
* `skills.ts`); never kept on a frozen base.
|
|
80
|
+
*/
|
|
81
|
+
export const HOST_DUPLICATED_REQUEST_CONTEXT_KEYS = [
|
|
82
|
+
"agent_skills",
|
|
83
|
+
"agent_skills_info_complete",
|
|
36
84
|
];
|
|
37
85
|
/**
|
|
38
|
-
*
|
|
39
|
-
* to Cursor. `hostSubagents.complete` answers a narrow question: did the host's
|
|
40
|
-
* task tool itself exposes an exhaustive recipient list (schema enum or
|
|
41
|
-
* catalog marker)? OpenCode's task tool intentionally uses a plain string, so
|
|
42
|
-
* that raw flag is false even though we can still advertise a complete Cursor
|
|
43
|
-
* catalog by adding built-in defaults and discovered agent files.
|
|
44
|
-
*
|
|
45
|
-
* `custom_subagents_info_complete` must describe the final advertised list, not
|
|
46
|
-
* the raw extraction source. If there is no executor, the empty list is
|
|
47
|
-
* complete. If the host catalog is complete, use it verbatim. Otherwise we make
|
|
48
|
-
* the advertised set complete by augmenting with default host agents and all
|
|
49
|
-
* locally discovered agents.
|
|
86
|
+
* Advertise only what the host `task` / `subagent` catalog already named.
|
|
50
87
|
*/
|
|
51
|
-
function buildAdvertisedSubagentCatalog(hostSubagents
|
|
88
|
+
function buildAdvertisedSubagentCatalog(hostSubagents) {
|
|
52
89
|
if (!hostSubagents.executor)
|
|
53
90
|
return { agents: [], complete: true };
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
91
|
+
return { agents: hostSubagents.agents, complete: hostSubagents.complete };
|
|
92
|
+
}
|
|
93
|
+
function stripHostDuplicatedRequestContextFields(context) {
|
|
94
|
+
for (const key of HOST_DUPLICATED_REQUEST_CONTEXT_KEYS)
|
|
95
|
+
delete context[key];
|
|
96
|
+
keepSystemInstructionsRule(context);
|
|
60
97
|
}
|
|
61
98
|
/**
|
|
62
99
|
* Full RequestContext payload for live UMA + exec #10 reply.
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* user's own OpenCode config lists it, exactly as the host would read it.
|
|
100
|
+
* Workspace env/git/layout, the host system-instructions rule, and
|
|
101
|
+
* host-advertised tools and subagents. The provider never looks in Cursor's
|
|
102
|
+
* own directories.
|
|
67
103
|
*/
|
|
68
104
|
export async function buildRequestContext(input) {
|
|
69
105
|
const workspaceRoot = path.resolve(input.workspaceRoot || process.cwd());
|
|
70
|
-
const
|
|
106
|
+
const config = input.mergedConfig ?? await loadMergedConfig(workspaceRoot);
|
|
71
107
|
const [dynamic, git, layout] = await Promise.all([
|
|
72
|
-
buildDynamicRequestContextFromDiscovery(input, workspaceRoot,
|
|
108
|
+
buildDynamicRequestContextFromDiscovery(input, workspaceRoot, config),
|
|
73
109
|
collectGit(workspaceRoot),
|
|
74
110
|
collectProjectLayout(workspaceRoot),
|
|
75
111
|
]);
|
|
76
|
-
const
|
|
112
|
+
const workspace = {
|
|
77
113
|
env: buildEnv(workspaceRoot),
|
|
78
|
-
rules: rules.map((r) => ({
|
|
79
|
-
full_path: r.fullPath,
|
|
80
|
-
content: r.content,
|
|
81
|
-
})),
|
|
82
114
|
repository_info: git.repositoryInfo,
|
|
83
115
|
git_repos: git.gitRepos,
|
|
84
116
|
project_layouts: [layout],
|
|
@@ -88,47 +120,41 @@ export async function buildRequestContext(input) {
|
|
|
88
120
|
git_repo_info_complete: true,
|
|
89
121
|
git_status_info_complete: true,
|
|
90
122
|
};
|
|
91
|
-
const
|
|
123
|
+
const base = withSystemInstructions(workspace, input.systemInstructions);
|
|
124
|
+
const skillLocations = await resolveSkillLocations(input, workspaceRoot);
|
|
125
|
+
const ctx = materializeRequestContext(base, dynamic, {
|
|
126
|
+
tools: input.tools,
|
|
127
|
+
...skillLocations,
|
|
128
|
+
});
|
|
92
129
|
traceRequestContextPaths("buildRequestContext", ctx);
|
|
93
130
|
return ctx;
|
|
94
131
|
}
|
|
95
|
-
|
|
132
|
+
/** Locations for path-desc materialization (bridge, then remembered OC2 files). */
|
|
133
|
+
export async function resolveSkillLocations(input, workspaceRoot) {
|
|
134
|
+
if (!skillToolAdvertised(input.tools))
|
|
135
|
+
return {};
|
|
136
|
+
return {
|
|
137
|
+
bridgeSkills: await loadBridgeSkills({
|
|
138
|
+
workspaceRoot,
|
|
139
|
+
sessionID: input.sessionID,
|
|
140
|
+
}),
|
|
141
|
+
skillFiles: hostSkillFiles(workspaceRoot),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
async function buildDynamicRequestContextFromDiscovery(input, workspaceRoot, config) {
|
|
96
145
|
const providerIdentifier = input.providerIdentifier ?? "opencode";
|
|
97
146
|
const tools = input.tools ?? [];
|
|
98
|
-
const
|
|
99
|
-
collectSkills(workspaceRoot, worktree),
|
|
100
|
-
collectAgents(workspaceRoot),
|
|
101
|
-
collectPlugins(workspaceRoot, config),
|
|
102
|
-
]);
|
|
147
|
+
const plugins = await collectPlugins(workspaceRoot, config);
|
|
103
148
|
const mcpServerNames = Object.keys(config.mcp ?? {});
|
|
104
|
-
const
|
|
105
|
-
const nested = toolsToMcpDescriptors(tools, providerIdentifier, mcpServerNames);
|
|
149
|
+
const slim = toolsToMcpDescriptors(tools, providerIdentifier, mcpServerNames, { namesOnly: true });
|
|
106
150
|
const projectDir = ensureOpencodeProjectDir(workspaceRoot);
|
|
107
151
|
const hostSubagents = extractHostSubagentCatalog(tools);
|
|
108
|
-
const advertisedSubagents = buildAdvertisedSubagentCatalog(hostSubagents
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
const customSubagents = [...advertisedByName.values()].map((agent) => {
|
|
116
|
-
const discovered = discoveredByName.get(agent.name);
|
|
117
|
-
return {
|
|
118
|
-
full_path: discovered?.fullPath ?? "",
|
|
119
|
-
name: agent.name,
|
|
120
|
-
description: discovered?.description || agent.description || "Host-configured subagent.",
|
|
121
|
-
// The host applies the real configured prompt when Task/Actor executes.
|
|
122
|
-
// Cursor only needs enough context to select the recipient intentionally.
|
|
123
|
-
prompt: discovered?.prompt ||
|
|
124
|
-
`Delegate to the host-configured ${agent.name} subagent; its host instructions and tools apply.`,
|
|
125
|
-
};
|
|
126
|
-
});
|
|
127
|
-
const liveSkills = skills.map((s) => ({
|
|
128
|
-
id: s.name,
|
|
129
|
-
full_path: s.fullPath,
|
|
130
|
-
content: s.content,
|
|
131
|
-
description: s.description,
|
|
152
|
+
const advertisedSubagents = buildAdvertisedSubagentCatalog(hostSubagents);
|
|
153
|
+
const customSubagents = advertisedSubagents.agents.map((agent) => ({
|
|
154
|
+
full_path: "",
|
|
155
|
+
name: agent.name,
|
|
156
|
+
description: agent.description || "Host-configured subagent.",
|
|
157
|
+
prompt: `Delegate to the host-configured ${agent.name} subagent; its host instructions and tools apply.`,
|
|
132
158
|
}));
|
|
133
159
|
const livePlugins = plugins.map((p) => ({
|
|
134
160
|
id: p.id,
|
|
@@ -136,24 +162,19 @@ async function buildDynamicRequestContextFromDiscovery(input, workspaceRoot, wor
|
|
|
136
162
|
}));
|
|
137
163
|
const overlay = input.conversationId
|
|
138
164
|
? holdCapabilityOverlay(input.conversationId, {
|
|
139
|
-
skills: liveSkills,
|
|
140
165
|
subagents: customSubagents,
|
|
141
166
|
plugins: livePlugins,
|
|
142
167
|
})
|
|
143
|
-
: {
|
|
168
|
+
: { subagents: customSubagents, plugins: livePlugins };
|
|
144
169
|
const dynamic = {
|
|
145
|
-
tools: flat,
|
|
146
|
-
agent_skills: overlay.skills,
|
|
147
|
-
custom_subagents: overlay.subagents,
|
|
148
170
|
mcp_file_system_options: {
|
|
149
171
|
enabled: true,
|
|
150
172
|
// Cursor metadata root (mcps / agent-tools), not the git workspace.
|
|
151
173
|
workspace_project_dir: projectDir,
|
|
152
|
-
mcp_descriptors: nested,
|
|
153
174
|
},
|
|
154
175
|
mcp_meta_tool_options: {
|
|
155
176
|
enabled: true,
|
|
156
|
-
mcp_descriptors:
|
|
177
|
+
...(slim.length > 0 ? { mcp_descriptors: slim } : {}),
|
|
157
178
|
},
|
|
158
179
|
// This provider always rejects native web_search/web_fetch interaction
|
|
159
180
|
// queries with a headless-UI reason (see interactions.ts). Advertise that
|
|
@@ -161,11 +182,12 @@ async function buildDynamicRequestContextFromDiscovery(input, workspaceRoot, wor
|
|
|
161
182
|
// custom_web* aliases instead of routing through a query doomed to fail.
|
|
162
183
|
web_search_enabled: false,
|
|
163
184
|
web_fetch_enabled: false,
|
|
164
|
-
agent_skills_info_complete: true,
|
|
165
185
|
custom_subagents_info_complete: advertisedSubagents.complete,
|
|
166
186
|
mcp_file_system_info_complete: true,
|
|
167
187
|
mcp_info_complete: true,
|
|
168
188
|
};
|
|
189
|
+
if (overlay.subagents.length > 0)
|
|
190
|
+
dynamic.custom_subagents = overlay.subagents;
|
|
169
191
|
if (overlay.plugins.length > 0) {
|
|
170
192
|
dynamic.hooks_additional_context = overlay.plugins.map((p) => p.line).join("\n");
|
|
171
193
|
}
|
|
@@ -174,28 +196,38 @@ async function buildDynamicRequestContextFromDiscovery(input, workspaceRoot, wor
|
|
|
174
196
|
/** Rediscover only capability/plugin sections that may change during a chat. */
|
|
175
197
|
export async function buildDynamicRequestContext(input) {
|
|
176
198
|
const workspaceRoot = path.resolve(input.workspaceRoot || process.cwd());
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
: loadMergedConfig(workspaceRoot),
|
|
182
|
-
]);
|
|
183
|
-
return buildDynamicRequestContextFromDiscovery(input, workspaceRoot, worktree, config);
|
|
199
|
+
const config = input.mergedConfig
|
|
200
|
+
? input.mergedConfig
|
|
201
|
+
: await loadMergedConfig(workspaceRoot);
|
|
202
|
+
return buildDynamicRequestContextFromDiscovery(input, workspaceRoot, config);
|
|
184
203
|
}
|
|
185
|
-
/**
|
|
186
|
-
|
|
204
|
+
/**
|
|
205
|
+
* Keep expensive workspace state frozen while replacing every live capability
|
|
206
|
+
* field. Skill locations (bridge / OpenCode 2 files / OpenCode 1 catalog
|
|
207
|
+
* paths) turn the skill catalog in the system-instructions rule into Cursor's
|
|
208
|
+
* path-desc `agent_skills`.
|
|
209
|
+
*/
|
|
210
|
+
export function materializeRequestContext(base, dynamic, options) {
|
|
187
211
|
const context = structuredClone(base);
|
|
212
|
+
stripHostDuplicatedRequestContextFields(context);
|
|
188
213
|
for (const key of DYNAMIC_REQUEST_CONTEXT_KEYS)
|
|
189
214
|
delete context[key];
|
|
190
215
|
for (const key of DYNAMIC_REQUEST_CONTEXT_KEYS) {
|
|
191
216
|
if (Object.hasOwn(dynamic, key))
|
|
192
217
|
context[key] = structuredClone(dynamic[key]);
|
|
193
218
|
}
|
|
219
|
+
const skills = agentSkillsForCursor(systemInstructionsRuleText(context), {
|
|
220
|
+
skillToolAdvertised: skillToolAdvertised(options?.tools),
|
|
221
|
+
skillFiles: options?.skillFiles,
|
|
222
|
+
bridgeSkills: options?.bridgeSkills,
|
|
223
|
+
});
|
|
224
|
+
applyAgentSkillsToContext(context, skills);
|
|
194
225
|
return context;
|
|
195
226
|
}
|
|
196
227
|
/** Strip live capability fields before retaining/persisting a conversation base. */
|
|
197
228
|
export function requestContextBase(context) {
|
|
198
229
|
const base = structuredClone(context);
|
|
230
|
+
stripHostDuplicatedRequestContextFields(base);
|
|
199
231
|
for (const key of DYNAMIC_REQUEST_CONTEXT_KEYS)
|
|
200
232
|
delete base[key];
|
|
201
233
|
return base;
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
export type AgentSkillLike = {
|
|
2
|
-
/** OpenCode skill id (frontmatter `name`, else directory name). */
|
|
3
|
-
id?: unknown;
|
|
4
|
-
full_path?: unknown;
|
|
5
|
-
description?: unknown;
|
|
6
|
-
content?: unknown;
|
|
7
|
-
};
|
|
8
1
|
/**
|
|
9
2
|
* Configured MCP servers that own at least one advertised tool. Uses the same
|
|
10
3
|
* server resolution as the RequestContext descriptors, so guidance never names
|
|
@@ -12,63 +5,14 @@ export type AgentSkillLike = {
|
|
|
12
5
|
* `sourceName` when present).
|
|
13
6
|
*/
|
|
14
7
|
export declare function listAdvertisedMcpServers(toolNames: Iterable<string>, knownMcpServers?: Iterable<string>): string[];
|
|
15
|
-
/** Skill id OpenCode's `skill` tool expects: the discovered id, else `…/skills/<id>/SKILL.md`. */
|
|
16
|
-
export declare function skillNameFromAgentSkill(skill: AgentSkillLike): string | undefined;
|
|
17
8
|
/**
|
|
18
9
|
* Shared system-guidance line: name advertised dynamic-catalog tools and prefer
|
|
19
10
|
* them over Grep/Shell when skills or project rules apply. Concrete skill ids
|
|
20
|
-
* stay out of the frozen baseline (they live in
|
|
21
|
-
* and in
|
|
11
|
+
* stay out of the frozen baseline (they live in the host system prompt / skill
|
|
12
|
+
* tool, and in host `<system-update>` when the catalog changes — same split as
|
|
22
13
|
* OpenCode's SkillGuidance / SkillInstructions).
|
|
23
14
|
*/
|
|
24
15
|
export declare function buildDynamicCatalogRoutingInstruction(options: {
|
|
25
16
|
toolNames: Iterable<string>;
|
|
26
17
|
knownMcpServers?: Iterable<string>;
|
|
27
18
|
}): string | undefined;
|
|
28
|
-
/**
|
|
29
|
-
* OpenCode-shaped Mid-Conversation skill update.
|
|
30
|
-
*
|
|
31
|
-
* Pure: compare `previousSkillIds` (null = not yet admitted this conversation)
|
|
32
|
-
* to the current catalog. First admission returns undefined — baseline
|
|
33
|
-
* RequestContext + routing instruction already carry the list. Later turns
|
|
34
|
-
* only speak when the id set changes (agent switch / discovery growth), matching
|
|
35
|
-
* `SkillGuidance.update` / `SkillInstructions.update`.
|
|
36
|
-
*/
|
|
37
|
-
export declare function buildSkillCatalogChangeReminder(options: {
|
|
38
|
-
hasSkillTool: boolean;
|
|
39
|
-
skills: readonly AgentSkillLike[];
|
|
40
|
-
/** Sorted skill ids already admitted; `null` means first admission this conversation. */
|
|
41
|
-
previousSkillIds: readonly string[] | null;
|
|
42
|
-
/**
|
|
43
|
-
* Host skill parameter key from the advertised schema.
|
|
44
|
-
* OpenCode 1.x: `name`. OpenCode 2.0: `id`.
|
|
45
|
-
*/
|
|
46
|
-
skillArgKey?: "name" | "id";
|
|
47
|
-
}): {
|
|
48
|
-
text: string | undefined;
|
|
49
|
-
nextSkillIds: string[];
|
|
50
|
-
};
|
|
51
|
-
/**
|
|
52
|
-
* Admit the current skill catalog for a conversation and return a Mid-Conversation
|
|
53
|
-
* reminder only when OpenCode would (catalog changed after baseline admission).
|
|
54
|
-
*/
|
|
55
|
-
export declare function takeSkillCatalogChangeReminder(conversationId: string, options: {
|
|
56
|
-
hasSkillTool: boolean;
|
|
57
|
-
skills: readonly AgentSkillLike[];
|
|
58
|
-
skillArgKey?: "name" | "id";
|
|
59
|
-
}): string | undefined;
|
|
60
|
-
export declare function clearSkillCatalogAdmission(conversationId: string): void;
|
|
61
|
-
export declare function transferSkillCatalogAdmission(previousConversationId: string, nextConversationId: string): void;
|
|
62
|
-
export declare function resetSkillCatalogAdmissionsForTests(): void;
|
|
63
|
-
/**
|
|
64
|
-
* @deprecated Prefer `buildSkillCatalogChangeReminder` / `takeSkillCatalogChangeReminder`.
|
|
65
|
-
* Kept as a thin alias for callers that still expect the old name; always returns
|
|
66
|
-
* undefined for userText-based matching (OpenCode never did per-turn id nudges).
|
|
67
|
-
*/
|
|
68
|
-
export declare function buildSkillCatalogNudge(options: {
|
|
69
|
-
hasSkillTool: boolean;
|
|
70
|
-
skills: readonly AgentSkillLike[];
|
|
71
|
-
userText?: string;
|
|
72
|
-
skillArgKey?: "name" | "id";
|
|
73
|
-
previousSkillIds?: readonly string[] | null;
|
|
74
|
-
}): string | undefined;
|