@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,118 @@
1
+ import {
2
+ extractText,
3
+ extractThinking,
4
+ inferToolName,
5
+ isAssistantText,
6
+ isThinking,
7
+ isToolCall,
8
+ type StreamJsonEvent,
9
+ type StreamJsonToolCallEvent,
10
+ } from "./types.js";
11
+ import { MixedDeltaTracker } from "./delta-tracker.js";
12
+
13
+ type OpenAiToolCall = {
14
+ index: number;
15
+ id: string;
16
+ type: "function";
17
+ function: {
18
+ name: string;
19
+ arguments: string;
20
+ };
21
+ };
22
+
23
+ type OpenAiDelta = {
24
+ content?: string;
25
+ reasoning_content?: string;
26
+ tool_calls?: OpenAiToolCall[];
27
+ };
28
+
29
+ type OpenAiChunk = {
30
+ id: string;
31
+ object: "chat.completion.chunk";
32
+ created: number;
33
+ model: string;
34
+ choices: Array<{
35
+ index: number;
36
+ delta: OpenAiDelta;
37
+ finish_reason: string | null;
38
+ }>;
39
+ };
40
+
41
+ const createChunk = (id: string, created: number, model: string, delta: OpenAiDelta): OpenAiChunk => ({
42
+ id,
43
+ object: "chat.completion.chunk",
44
+ created,
45
+ model,
46
+ choices: [
47
+ {
48
+ index: 0,
49
+ delta,
50
+ finish_reason: null,
51
+ },
52
+ ],
53
+ });
54
+
55
+ export const formatSseChunk = (payload: object) => `data: ${JSON.stringify(payload)}\n\n`;
56
+
57
+ export const formatSseDone = () => "data: [DONE]\n\n";
58
+
59
+ export class StreamToSseConverter {
60
+ private readonly id: string;
61
+ private readonly created: number;
62
+ private readonly model: string;
63
+ private readonly tracker = new MixedDeltaTracker();
64
+
65
+ constructor(model: string, options?: { id?: string; created?: number }) {
66
+ this.model = model;
67
+ this.id = options?.id ?? `cursor-acp-${Date.now()}`;
68
+ this.created = options?.created ?? Math.floor(Date.now() / 1000);
69
+ }
70
+
71
+ handleEvent(event: StreamJsonEvent): string[] {
72
+ if (isAssistantText(event)) {
73
+ const text = extractText(event);
74
+ if (!text) return [];
75
+ const delta = this.tracker.nextText(text);
76
+ return delta ? [this.chunkWith({ content: delta })] : [];
77
+ }
78
+
79
+ if (isThinking(event)) {
80
+ const text = extractThinking(event);
81
+ if (!text) return [];
82
+ const delta = this.tracker.nextThinking(text);
83
+ return delta ? [this.chunkWith({ reasoning_content: delta })] : [];
84
+ }
85
+
86
+ if (isToolCall(event)) {
87
+ return [this.chunkWith(this.toolCallDelta(event))];
88
+ }
89
+
90
+ return [];
91
+ }
92
+
93
+ private chunkWith(delta: OpenAiDelta): string {
94
+ return formatSseChunk(createChunk(this.id, this.created, this.model, delta));
95
+ }
96
+
97
+ private toolCallDelta(event: StreamJsonToolCallEvent): OpenAiDelta {
98
+ const id = event.call_id ?? "unknown";
99
+ const toolName = inferToolName(event) || "tool";
100
+ const toolKey = Object.keys(event.tool_call ?? {})[0];
101
+ const args = toolKey ? event.tool_call[toolKey]?.args : undefined;
102
+ const argumentsText = args ? JSON.stringify(args) : "";
103
+
104
+ return {
105
+ tool_calls: [
106
+ {
107
+ index: 0,
108
+ id,
109
+ type: "function",
110
+ function: {
111
+ name: toolName,
112
+ arguments: argumentsText,
113
+ },
114
+ },
115
+ ],
116
+ };
117
+ }
118
+ }
@@ -0,0 +1,22 @@
1
+ import type { StreamJsonEvent } from "./types.js";
2
+ import { createLogger } from "../utils/logger.js";
3
+
4
+ const log = createLogger("streaming:parser");
5
+
6
+ export const parseStreamJsonLine = (line: string): StreamJsonEvent | null => {
7
+ const trimmed = line.trim();
8
+ if (!trimmed) {
9
+ return null;
10
+ }
11
+
12
+ try {
13
+ const parsed = JSON.parse(trimmed) as unknown;
14
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
15
+ return null;
16
+ }
17
+ return parsed as StreamJsonEvent;
18
+ } catch {
19
+ log.debug("Failed to parse NDJSON line", { line: trimmed.substring(0, 100) });
20
+ return null;
21
+ }
22
+ };
@@ -0,0 +1,158 @@
1
+ export type StreamJsonTextContent = {
2
+ type: "text";
3
+ text: string;
4
+ };
5
+
6
+ export type StreamJsonThinkingContent = {
7
+ type: "thinking";
8
+ thinking: string;
9
+ };
10
+
11
+ export type StreamJsonContent = StreamJsonTextContent | StreamJsonThinkingContent;
12
+
13
+ export type StreamJsonSystemEvent = {
14
+ type: "system";
15
+ subtype?: string;
16
+ timestamp?: number;
17
+ session_id?: string;
18
+ cwd?: string;
19
+ model?: string;
20
+ permissionMode?: string;
21
+ tools?: Array<{ name: string; description?: string }>;
22
+ message?: string;
23
+ };
24
+
25
+ export type StreamJsonUserEvent = {
26
+ type: "user";
27
+ timestamp?: number;
28
+ session_id?: string;
29
+ message: {
30
+ role: "user";
31
+ content: StreamJsonContent[];
32
+ };
33
+ };
34
+
35
+ export type StreamJsonAssistantEvent = {
36
+ type: "assistant";
37
+ timestamp?: number;
38
+ timestamp_ms?: number;
39
+ session_id?: string;
40
+ message: {
41
+ role: "assistant";
42
+ content: StreamJsonContent[];
43
+ };
44
+ };
45
+
46
+ /**
47
+ * Thinking event from cursor-agent stream-json output.
48
+ * Real format: {"type":"thinking","subtype":"delta","text":"...","session_id":"...","timestamp_ms":...}
49
+ * OR: {"type":"thinking","subtype":"completed","session_id":"...","timestamp_ms":...}
50
+ */
51
+ export type StreamJsonThinkingEvent = {
52
+ type: "thinking";
53
+ subtype?: "delta" | "completed" | string;
54
+ text?: string;
55
+ timestamp?: number;
56
+ timestamp_ms?: number;
57
+ session_id?: string;
58
+ };
59
+
60
+ export type StreamJsonToolCallPayload = {
61
+ args?: Record<string, unknown>;
62
+ result?: Record<string, unknown>;
63
+ };
64
+
65
+ export type StreamJsonToolCallEvent = {
66
+ type: "tool_call";
67
+ subtype?: string;
68
+ timestamp?: number;
69
+ session_id?: string;
70
+ call_id?: string;
71
+ tool_call: Record<string, StreamJsonToolCallPayload>;
72
+ };
73
+
74
+ export type StreamJsonResultEvent = {
75
+ type: "result";
76
+ subtype?: "success" | "error" | string;
77
+ timestamp?: number;
78
+ duration_ms?: number;
79
+ duration_api_ms?: number;
80
+ session_id?: string;
81
+ request_id?: string;
82
+ result?: string;
83
+ is_error?: boolean;
84
+ usage?: Record<string, unknown>;
85
+ error?: {
86
+ message?: string;
87
+ code?: number | string;
88
+ details?: string;
89
+ };
90
+ };
91
+
92
+ export type StreamJsonEvent =
93
+ | StreamJsonSystemEvent
94
+ | StreamJsonUserEvent
95
+ | StreamJsonAssistantEvent
96
+ | StreamJsonThinkingEvent
97
+ | StreamJsonToolCallEvent
98
+ | StreamJsonResultEvent;
99
+
100
+ const hasTextContent = (event: StreamJsonAssistantEvent) =>
101
+ event.message.content.some((content) => content.type === "text");
102
+
103
+ const hasThinkingContent = (event: StreamJsonAssistantEvent) =>
104
+ event.message.content.some((content) => content.type === "thinking");
105
+
106
+ export const isAssistantText = (event: StreamJsonEvent): event is StreamJsonAssistantEvent =>
107
+ event.type === "assistant" && hasTextContent(event);
108
+
109
+ export const isThinking = (
110
+ event: StreamJsonEvent,
111
+ ): event is StreamJsonAssistantEvent | StreamJsonThinkingEvent => {
112
+ if (event.type === "thinking") {
113
+ return true;
114
+ }
115
+
116
+ return event.type === "assistant" && hasThinkingContent(event);
117
+ };
118
+
119
+ export const isToolCall = (event: StreamJsonEvent): event is StreamJsonToolCallEvent =>
120
+ event.type === "tool_call";
121
+
122
+ export const isResult = (event: StreamJsonEvent): event is StreamJsonResultEvent =>
123
+ event.type === "result";
124
+
125
+ export const extractText = (event: StreamJsonAssistantEvent) =>
126
+ event.message.content
127
+ .filter((content): content is StreamJsonTextContent => content.type === "text")
128
+ .map((content) => content.text)
129
+ .join("");
130
+
131
+ export const extractThinking = (
132
+ event: StreamJsonAssistantEvent | StreamJsonThinkingEvent,
133
+ ): string => {
134
+ if (event.type === "thinking") {
135
+ return event.text ?? "";
136
+ }
137
+
138
+ return event.message.content
139
+ .filter(
140
+ (content): content is StreamJsonThinkingContent => content.type === "thinking",
141
+ )
142
+ .map((content) => content.thinking)
143
+ .join("");
144
+ };
145
+
146
+ export const inferToolName = (event: StreamJsonToolCallEvent) => {
147
+ const [key] = Object.keys(event.tool_call ?? {});
148
+ if (!key) {
149
+ return "";
150
+ }
151
+
152
+ if (key.endsWith("ToolCall")) {
153
+ const base = key.slice(0, -"ToolCall".length);
154
+ return base.charAt(0).toLowerCase() + base.slice(1);
155
+ }
156
+
157
+ return key;
158
+ };
@@ -0,0 +1,25 @@
1
+ import type { IToolExecutor, ExecutionResult } from "./types.js";
2
+ import { createLogger } from "../../utils/logger.js";
3
+
4
+ const log = createLogger("tools:executor:chain");
5
+
6
+ /**
7
+ * Executes using the first executor that declares it can handle the toolId.
8
+ */
9
+ export async function executeWithChain(
10
+ executors: IToolExecutor[],
11
+ toolId: string,
12
+ args: Record<string, unknown>
13
+ ): Promise<ExecutionResult> {
14
+ for (const ex of executors) {
15
+ if (ex.canExecute(toolId)) {
16
+ try {
17
+ return await ex.execute(toolId, args);
18
+ } catch (err: any) {
19
+ log.warn("Executor threw unexpected error", { toolId, error: String(err?.message || err) });
20
+ return { status: "error", error: String(err?.message || err) };
21
+ }
22
+ }
23
+ }
24
+ return { status: "error", error: `No executor available for ${toolId}` };
25
+ }
@@ -0,0 +1,27 @@
1
+ import type { ToolHandler, Tool } from "./types.js";
2
+
3
+ interface RegisteredTool {
4
+ tool: Tool;
5
+ handler: ToolHandler;
6
+ }
7
+
8
+ export class ToolRegistry {
9
+ private tools = new Map<string, RegisteredTool>();
10
+
11
+ register(tool: Tool, handler: ToolHandler): void {
12
+ this.tools.set(tool.name, { tool, handler });
13
+ }
14
+
15
+ getHandler(name: string): ToolHandler | undefined {
16
+ return this.tools.get(name)?.handler;
17
+ }
18
+
19
+ getTool(name: string): Tool | undefined {
20
+ return this.tools.get(name)?.tool;
21
+ }
22
+
23
+ list(): Tool[] {
24
+ return Array.from(this.tools.values()).map((t) => t.tool);
25
+ }
26
+ }
27
+
@@ -0,0 +1,31 @@
1
+ export interface ExecutionResult {
2
+ status: "success" | "error";
3
+ output?: string;
4
+ error?: string;
5
+ errorType?: "recoverable" | "fatal";
6
+ }
7
+
8
+ export interface IToolExecutor {
9
+ canExecute(toolId: string): boolean;
10
+ execute(toolId: string, args: Record<string, unknown>): Promise<ExecutionResult>;
11
+ }
12
+
13
+ export interface ToolHandler {
14
+ (args: Record<string, unknown>): Promise<string>;
15
+ }
16
+
17
+ export interface Tool {
18
+ id: string;
19
+ name: string;
20
+ description: string;
21
+ parameters: any;
22
+ source: "sdk" | "cli" | "local" | "mcp";
23
+ }
24
+
25
+ export interface Skill extends Tool {
26
+ aliases?: string[];
27
+ category?: string;
28
+ triggers?: string[];
29
+ prerequisites?: string[];
30
+ }
31
+