@evanovation/open-cursor 2.4.15

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.
Files changed (80) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +270 -0
  3. package/dist/cli/discover.js +527 -0
  4. package/dist/cli/mcptool.js +10339 -0
  5. package/dist/cli/opencode-cursor.js +2989 -0
  6. package/dist/index.js +20588 -0
  7. package/dist/plugin-entry.js +19848 -0
  8. package/package.json +82 -0
  9. package/scripts/cursor-agent-runner.mjs +272 -0
  10. package/scripts/sdk-runner.mjs +412 -0
  11. package/src/acp/metrics.ts +83 -0
  12. package/src/acp/sessions.ts +107 -0
  13. package/src/acp/tools.ts +209 -0
  14. package/src/auth.ts +175 -0
  15. package/src/cli/discover.ts +53 -0
  16. package/src/cli/mcptool.ts +133 -0
  17. package/src/cli/model-discovery.ts +71 -0
  18. package/src/cli/opencode-cursor.ts +1195 -0
  19. package/src/client/cursor-agent-child.ts +459 -0
  20. package/src/client/sdk-child.ts +550 -0
  21. package/src/client/simple.ts +293 -0
  22. package/src/commands/status.ts +39 -0
  23. package/src/index.ts +39 -0
  24. package/src/mcp/client-manager.ts +166 -0
  25. package/src/mcp/config.ts +169 -0
  26. package/src/mcp/tool-bridge.ts +133 -0
  27. package/src/models/config.ts +64 -0
  28. package/src/models/discovery.ts +105 -0
  29. package/src/models/index.ts +3 -0
  30. package/src/models/pricing.ts +196 -0
  31. package/src/models/sync.ts +247 -0
  32. package/src/models/types.ts +11 -0
  33. package/src/models/variants.ts +446 -0
  34. package/src/plugin-entry.ts +28 -0
  35. package/src/plugin-toggle.ts +81 -0
  36. package/src/plugin.ts +2802 -0
  37. package/src/provider/backend.ts +71 -0
  38. package/src/provider/boundary.ts +168 -0
  39. package/src/provider/passthrough-tracker.ts +38 -0
  40. package/src/provider/runtime-interception.ts +818 -0
  41. package/src/provider/tool-loop-guard.ts +644 -0
  42. package/src/provider/tool-schema-compat.ts +800 -0
  43. package/src/provider.ts +268 -0
  44. package/src/proxy/formatter.ts +60 -0
  45. package/src/proxy/handler.ts +29 -0
  46. package/src/proxy/incremental-prompt.ts +74 -0
  47. package/src/proxy/prompt-builder.ts +204 -0
  48. package/src/proxy/server.ts +207 -0
  49. package/src/proxy/session-resume.ts +312 -0
  50. package/src/proxy/tool-loop.ts +359 -0
  51. package/src/proxy/types.ts +13 -0
  52. package/src/services/toast-service.ts +81 -0
  53. package/src/streaming/ai-sdk-parts.ts +109 -0
  54. package/src/streaming/delta-tracker.ts +89 -0
  55. package/src/streaming/line-buffer.ts +44 -0
  56. package/src/streaming/openai-sse.ts +118 -0
  57. package/src/streaming/parser.ts +22 -0
  58. package/src/streaming/types.ts +158 -0
  59. package/src/tools/core/executor.ts +25 -0
  60. package/src/tools/core/registry.ts +27 -0
  61. package/src/tools/core/types.ts +31 -0
  62. package/src/tools/defaults.ts +954 -0
  63. package/src/tools/discovery.ts +140 -0
  64. package/src/tools/executors/cli.ts +59 -0
  65. package/src/tools/executors/local.ts +25 -0
  66. package/src/tools/executors/mcp.ts +39 -0
  67. package/src/tools/executors/sdk.ts +39 -0
  68. package/src/tools/index.ts +8 -0
  69. package/src/tools/registry.ts +34 -0
  70. package/src/tools/router.ts +123 -0
  71. package/src/tools/schema.ts +58 -0
  72. package/src/tools/skills/loader.ts +61 -0
  73. package/src/tools/skills/resolver.ts +21 -0
  74. package/src/tools/types.ts +29 -0
  75. package/src/types.ts +8 -0
  76. package/src/usage.ts +112 -0
  77. package/src/utils/binary.ts +71 -0
  78. package/src/utils/errors.ts +224 -0
  79. package/src/utils/logger.ts +191 -0
  80. package/src/utils/perf.ts +76 -0
@@ -0,0 +1,71 @@
1
+ export {
2
+ isUsableSdkApiKey,
3
+ normalizeAuthorizationHeader,
4
+ resolveSdkApiKey,
5
+ type ResolveSdkApiKeyInput,
6
+ } from "../auth.js";
7
+ import { isUsableSdkApiKey } from "../auth.js";
8
+
9
+ export type CursorBackendPreference = "auto" | "cursor-agent" | "sdk";
10
+ export type CursorRuntimeBackend = "cursor-agent" | "sdk";
11
+
12
+ export interface BackendPreferenceParseResult {
13
+ preference: CursorBackendPreference;
14
+ valid: boolean;
15
+ }
16
+
17
+ export interface SelectBackendForRequestInput {
18
+ preference: CursorBackendPreference;
19
+ cursorAgentAvailable: boolean;
20
+ sdkApiKey?: string;
21
+ }
22
+
23
+ export function parseCursorBackendPreference(
24
+ value: string | undefined,
25
+ ): BackendPreferenceParseResult {
26
+ if (value === undefined || value.trim() === "") {
27
+ return { preference: "auto", valid: true };
28
+ }
29
+
30
+ const normalized = value.trim().toLowerCase();
31
+ if (
32
+ normalized === "auto" ||
33
+ normalized === "cursor-agent" ||
34
+ normalized === "sdk"
35
+ ) {
36
+ return { preference: normalized, valid: true };
37
+ }
38
+
39
+ return { preference: "auto", valid: false };
40
+ }
41
+
42
+ export function selectInitialBackend(
43
+ preference: CursorBackendPreference,
44
+ ): CursorRuntimeBackend {
45
+ return preference === "sdk" ? "sdk" : "cursor-agent";
46
+ }
47
+
48
+ export function shouldFallbackToSdk(
49
+ preference: CursorBackendPreference,
50
+ sdkApiKey: string | undefined,
51
+ ): boolean {
52
+ return preference === "auto" && isUsableSdkApiKey(sdkApiKey);
53
+ }
54
+
55
+ export function selectBackendForRequest(
56
+ input: SelectBackendForRequestInput,
57
+ ): CursorRuntimeBackend {
58
+ if (input.preference === "sdk") {
59
+ return "sdk";
60
+ }
61
+
62
+ if (
63
+ input.preference === "auto" &&
64
+ !input.cursorAgentAvailable &&
65
+ isUsableSdkApiKey(input.sdkApiKey)
66
+ ) {
67
+ return "sdk";
68
+ }
69
+
70
+ return "cursor-agent";
71
+ }
@@ -0,0 +1,168 @@
1
+ import type { OpenAiToolCall, ToolLoopMeta, ToolCallExtractionResult } from "../proxy/tool-loop.js";
2
+ import {
3
+ createToolCallCompletionResponse,
4
+ createToolCallStreamChunks,
5
+ extractOpenAiToolCall,
6
+ } from "../proxy/tool-loop.js";
7
+ import type { StreamJsonToolCallEvent } from "../streaming/types.js";
8
+
9
+ export type ToolLoopMode = "opencode" | "proxy-exec" | "off";
10
+
11
+ export type ProviderBoundaryMode = "legacy" | "v1";
12
+
13
+ export type ToolOptionResolution = {
14
+ tools: unknown;
15
+ action: "preserve" | "override" | "none";
16
+ };
17
+
18
+ export interface ToolLoopFlags {
19
+ proxyExecuteToolCalls: boolean;
20
+ suppressConverterToolEvents: boolean;
21
+ shouldEmitToolUpdates: boolean;
22
+ }
23
+
24
+ export interface ProviderBoundary {
25
+ readonly mode: ProviderBoundaryMode;
26
+ readonly providerId: string;
27
+ resolveChatParamTools(
28
+ toolLoopMode: ToolLoopMode,
29
+ existingTools: unknown,
30
+ refreshedTools: Array<any>,
31
+ ): ToolOptionResolution;
32
+ computeToolLoopFlags(
33
+ toolLoopMode: ToolLoopMode,
34
+ forwardToolCalls: boolean,
35
+ emitToolUpdates: boolean,
36
+ ): ToolLoopFlags;
37
+ matchesProvider(inputModel: any): boolean;
38
+ normalizeRuntimeModel(model: unknown): string;
39
+ resolveRuntimeModel(model: unknown, cursorModel: unknown): string;
40
+ applyChatParamDefaults(
41
+ output: any,
42
+ proxyBaseURL: string | undefined,
43
+ defaultBaseURL: string,
44
+ defaultApiKey: string,
45
+ ): void;
46
+ maybeExtractToolCall(
47
+ event: StreamJsonToolCallEvent,
48
+ allowedToolNames: Set<string>,
49
+ toolLoopMode: ToolLoopMode,
50
+ ): ToolCallExtractionResult;
51
+ createNonStreamToolCallResponse(meta: ToolLoopMeta, toolCall: OpenAiToolCall): any;
52
+ createStreamToolCallChunks(meta: ToolLoopMeta, toolCall: OpenAiToolCall): Array<any>;
53
+ }
54
+
55
+ export function parseProviderBoundaryMode(
56
+ value: string | undefined,
57
+ ): { mode: ProviderBoundaryMode; valid: boolean } {
58
+ const normalized = (value ?? "v1").trim().toLowerCase();
59
+ if (normalized === "legacy" || normalized === "v1") {
60
+ return { mode: normalized, valid: true };
61
+ }
62
+ return { mode: "v1", valid: false };
63
+ }
64
+
65
+ export function createProviderBoundary(
66
+ mode: ProviderBoundaryMode,
67
+ providerId: string,
68
+ ): ProviderBoundary {
69
+ const shared = createSharedBoundary(providerId);
70
+ if (mode === "v1") {
71
+ return { ...shared, mode: "v1" };
72
+ }
73
+ return { ...shared, mode: "legacy" };
74
+ }
75
+
76
+ function createSharedBoundary(
77
+ providerId: string,
78
+ ): Omit<ProviderBoundary, "mode"> {
79
+ return {
80
+ providerId,
81
+
82
+ resolveChatParamTools(toolLoopMode, existingTools, refreshedTools) {
83
+ if (toolLoopMode === "proxy-exec") {
84
+ if (refreshedTools.length > 0) {
85
+ return { tools: refreshedTools, action: "override" };
86
+ }
87
+ return { tools: existingTools, action: "none" };
88
+ }
89
+
90
+ if (toolLoopMode === "opencode") {
91
+ if (existingTools != null) {
92
+ return { tools: existingTools, action: "preserve" };
93
+ }
94
+ return { tools: existingTools, action: "none" };
95
+ }
96
+
97
+ return { tools: existingTools, action: "none" };
98
+ },
99
+
100
+ computeToolLoopFlags(toolLoopMode, forwardToolCalls, emitToolUpdates) {
101
+ const proxyExec = toolLoopMode === "proxy-exec";
102
+ return {
103
+ proxyExecuteToolCalls: proxyExec && forwardToolCalls,
104
+ suppressConverterToolEvents: proxyExec && !forwardToolCalls,
105
+ shouldEmitToolUpdates: proxyExec && emitToolUpdates,
106
+ };
107
+ },
108
+
109
+ matchesProvider(inputModel: any) {
110
+ if (!inputModel || typeof inputModel !== "object") {
111
+ return false;
112
+ }
113
+
114
+ const modelProviderId =
115
+ (typeof inputModel.providerID === "string" && inputModel.providerID)
116
+ || (typeof inputModel.providerId === "string" && inputModel.providerId)
117
+ || (typeof inputModel.provider === "string" && inputModel.provider)
118
+ || "";
119
+
120
+ return modelProviderId === providerId;
121
+ },
122
+
123
+ normalizeRuntimeModel(model) {
124
+ const raw = typeof model === "string" ? model.trim() : "";
125
+ if (raw.length === 0) {
126
+ return "auto";
127
+ }
128
+
129
+ const prefix = `${providerId}/`;
130
+ if (raw.startsWith(prefix)) {
131
+ const stripped = raw.slice(prefix.length).trim();
132
+ return stripped.length > 0 ? stripped : "auto";
133
+ }
134
+
135
+ return raw;
136
+ },
137
+
138
+ resolveRuntimeModel(model, cursorModel) {
139
+ const rawCursorModel = typeof cursorModel === "string" ? cursorModel.trim() : "";
140
+ if (rawCursorModel.length > 0) {
141
+ return this.normalizeRuntimeModel(rawCursorModel);
142
+ }
143
+
144
+ return this.normalizeRuntimeModel(model);
145
+ },
146
+
147
+ applyChatParamDefaults(output, proxyBaseURL, defaultBaseURL, defaultApiKey) {
148
+ output.options = output.options || {};
149
+ output.options.baseURL = proxyBaseURL || defaultBaseURL;
150
+ output.options.apiKey = output.options.apiKey || defaultApiKey;
151
+ },
152
+
153
+ maybeExtractToolCall(event, allowedToolNames, toolLoopMode) {
154
+ if (toolLoopMode !== "opencode") {
155
+ return { action: "skip" as const, skipReason: "tool_loop_mode_not_opencode" };
156
+ }
157
+ return extractOpenAiToolCall(event, allowedToolNames);
158
+ },
159
+
160
+ createNonStreamToolCallResponse(meta, toolCall) {
161
+ return createToolCallCompletionResponse(meta, toolCall);
162
+ },
163
+
164
+ createStreamToolCallChunks(meta, toolCall) {
165
+ return createToolCallStreamChunks(meta, toolCall);
166
+ },
167
+ };
168
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * PassThroughTracker - Tracks MCP tools passed through to cursor-agent
3
+ *
4
+ * Used to collect tool names for end-of-response toast notifications
5
+ * when cursor-agent MCP tools (like Playwright) are invoked.
6
+ */
7
+
8
+ export interface PassThroughSummary {
9
+ tools: string[];
10
+ errors: string[];
11
+ hasActivity: boolean;
12
+ }
13
+
14
+ export class PassThroughTracker {
15
+ private tools: Set<string> = new Set();
16
+ private errors: string[] = [];
17
+
18
+ trackTool(name: string): void {
19
+ this.tools.add(name);
20
+ }
21
+
22
+ trackError(toolName: string, message: string): void {
23
+ this.errors.push(`${toolName}: ${message}`);
24
+ }
25
+
26
+ getSummary(): PassThroughSummary {
27
+ return {
28
+ tools: Array.from(this.tools),
29
+ errors: [...this.errors],
30
+ hasActivity: this.tools.size > 0,
31
+ };
32
+ }
33
+
34
+ reset(): void {
35
+ this.tools.clear();
36
+ this.errors.length = 0;
37
+ }
38
+ }