@rama_nigg/open-cursor 2.3.3 → 2.3.4
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/dist/index.js +307 -210
- package/dist/plugin-entry.js +301 -204
- package/package.json +1 -1
- package/src/plugin.ts +33 -0
- package/src/provider/boundary.ts +3 -3
- package/src/provider/passthrough-tracker.ts +38 -0
- package/src/provider/runtime-interception.ts +175 -123
- package/src/provider/tool-loop-guard.ts +0 -63
- package/src/proxy/tool-loop.ts +42 -28
- package/src/services/toast-service.ts +81 -0
package/src/proxy/tool-loop.ts
CHANGED
|
@@ -12,6 +12,13 @@ export interface OpenAiToolCall {
|
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export interface ToolCallExtractionResult {
|
|
16
|
+
action: "intercept" | "passthrough" | "skip";
|
|
17
|
+
toolCall?: OpenAiToolCall;
|
|
18
|
+
passthroughName?: string;
|
|
19
|
+
skipReason?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
export interface ToolLoopMeta {
|
|
16
23
|
id: string;
|
|
17
24
|
created: number;
|
|
@@ -106,48 +113,55 @@ export function extractAllowedToolNames(tools: Array<any>): Set<string> {
|
|
|
106
113
|
export function extractOpenAiToolCall(
|
|
107
114
|
event: StreamJsonToolCallEvent,
|
|
108
115
|
allowedToolNames: Set<string>,
|
|
109
|
-
):
|
|
116
|
+
): ToolCallExtractionResult {
|
|
110
117
|
if (allowedToolNames.size === 0) {
|
|
111
|
-
return
|
|
118
|
+
return { action: "skip", skipReason: "no_allowed_tools" };
|
|
112
119
|
}
|
|
113
120
|
|
|
114
121
|
const { name, args, skipped } = extractToolNameAndArgs(event);
|
|
115
122
|
if (skipped) {
|
|
116
|
-
return
|
|
123
|
+
return { action: "skip", skipReason: "event_skipped" };
|
|
117
124
|
}
|
|
118
125
|
if (!name) {
|
|
119
|
-
return
|
|
126
|
+
return { action: "skip", skipReason: "no_name" };
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
const resolvedName = resolveAllowedToolName(name, allowedToolNames);
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
if (resolvedName) {
|
|
131
|
+
// Known tool → intercept and forward to OpenCode
|
|
132
|
+
if (args === undefined && event.subtype === "started") {
|
|
133
|
+
log.debug("Tool call args extraction returned undefined", {
|
|
134
|
+
toolName: name,
|
|
135
|
+
subtype: event.subtype ?? "none",
|
|
136
|
+
payloadKeys: Object.entries(event.tool_call || {}).map(([k, v]) =>
|
|
137
|
+
`${k}:[${isRecord(v) ? Object.keys(v).join(",") : typeof v}]`),
|
|
138
|
+
hasCallId: Boolean(event.call_id),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
132
141
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
142
|
+
const callId = event.call_id || (event as any).tool_call_id || "call_unknown";
|
|
143
|
+
return {
|
|
144
|
+
action: "intercept",
|
|
145
|
+
toolCall: {
|
|
146
|
+
id: callId,
|
|
147
|
+
type: "function",
|
|
148
|
+
function: {
|
|
149
|
+
name: resolvedName,
|
|
150
|
+
arguments: toOpenAiArguments(args),
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
};
|
|
141
154
|
}
|
|
142
155
|
|
|
143
|
-
|
|
156
|
+
// Unknown tool → pass through to cursor-agent
|
|
157
|
+
log.debug("Tool call not in allowlist; passing through to cursor-agent", {
|
|
158
|
+
name,
|
|
159
|
+
normalized: normalizeAliasKey(name),
|
|
160
|
+
allowedToolCount: allowedToolNames.size,
|
|
161
|
+
});
|
|
144
162
|
return {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
function: {
|
|
148
|
-
name: resolvedName,
|
|
149
|
-
arguments: toOpenAiArguments(args),
|
|
150
|
-
},
|
|
163
|
+
action: "passthrough",
|
|
164
|
+
passthroughName: name,
|
|
151
165
|
};
|
|
152
166
|
}
|
|
153
167
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToastService - OpenCode toast notification integration
|
|
3
|
+
*
|
|
4
|
+
* Provides toast notifications for MCP tool pass-through visibility.
|
|
5
|
+
* Gracefully degrades when client.tui.showToast is unavailable.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createLogger } from "../utils/logger.js";
|
|
9
|
+
|
|
10
|
+
const log = createLogger("services:toast");
|
|
11
|
+
|
|
12
|
+
export interface ToastOptions {
|
|
13
|
+
title?: string;
|
|
14
|
+
message: string;
|
|
15
|
+
variant: "info" | "success" | "warning" | "error";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface OpenCodeTuiClient {
|
|
19
|
+
showToast: (options: { body: { title?: string; message: string; variant: string } }) => Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface OpenCodeClientWithTui {
|
|
23
|
+
tui?: OpenCodeTuiClient;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class ToastService {
|
|
27
|
+
private client: OpenCodeClientWithTui | null = null;
|
|
28
|
+
|
|
29
|
+
setClient(client: OpenCodeClientWithTui): void {
|
|
30
|
+
this.client = client;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async show(options: ToastOptions): Promise<void> {
|
|
34
|
+
if (!this.client?.tui?.showToast) {
|
|
35
|
+
log.debug("Toast not available; client.tui.showToast missing", { message: options.message });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
await this.client.tui.showToast({
|
|
41
|
+
body: {
|
|
42
|
+
title: options.title,
|
|
43
|
+
message: options.message,
|
|
44
|
+
variant: options.variant,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
} catch (error) {
|
|
48
|
+
log.debug("Toast failed", { error, message: options.message });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async showPassThroughSummary(tools: string[]): Promise<void> {
|
|
53
|
+
if (tools.length === 0) return;
|
|
54
|
+
|
|
55
|
+
const toolList = tools.length <= 3
|
|
56
|
+
? tools.join(", ")
|
|
57
|
+
: `${tools.slice(0, 3).join(", ")} +${tools.length - 3} more`;
|
|
58
|
+
|
|
59
|
+
await this.show({
|
|
60
|
+
title: "MCP Tools",
|
|
61
|
+
message: `🎭 ${tools.length} tool${tools.length > 1 ? "s" : ""} handled by cursor-agent: ${toolList}`,
|
|
62
|
+
variant: "info",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async showErrorSummary(errors: string[]): Promise<void> {
|
|
67
|
+
if (errors.length === 0) return;
|
|
68
|
+
|
|
69
|
+
const errorList = errors.length <= 2
|
|
70
|
+
? errors.join("; ")
|
|
71
|
+
: `${errors.slice(0, 2).join("; ")} +${errors.length - 2} more`;
|
|
72
|
+
|
|
73
|
+
await this.show({
|
|
74
|
+
title: "MCP Errors",
|
|
75
|
+
message: `⚠️ ${errors.length} MCP tool${errors.length > 1 ? "s" : ""} failed: ${errorList}`,
|
|
76
|
+
variant: "warning",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const toastService = new ToastService();
|