@herbertgao/pi-extensions 2026.9.9 → 2026.9.11
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 +1 -0
- package/THIRD_PARTY_NOTICES.md +26 -0
- package/node_modules/@herbertgao/pi-bark/package.json +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -2
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +12 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +427 -120
- package/node_modules/@herbertgao/pi-subagents/docs/rpc.md +184 -0
- package/node_modules/@herbertgao/pi-subagents/docs/workflows.md +466 -0
- package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +6 -6
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/compose.js +52 -0
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/fan-out-audit.js +56 -0
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/gated-fix.js +60 -0
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/lib/count-child.js +30 -0
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/review-panel.js +68 -0
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/structured-findings.js +81 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +12 -10
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +52 -12
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +837 -146
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +213 -39
- package/node_modules/@herbertgao/pi-subagents/src/cross-extension-rpc.ts +73 -14
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +101 -47
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +2249 -914
- package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +13 -0
- package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +215 -0
- package/node_modules/@herbertgao/pi-subagents/src/mention.ts +147 -0
- package/node_modules/@herbertgao/pi-subagents/src/model-resolver.ts +9 -1
- package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +40 -26
- package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +18 -8
- package/node_modules/@herbertgao/pi-subagents/src/prompts.ts +46 -9
- package/node_modules/@herbertgao/pi-subagents/src/schedule.ts +21 -16
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +137 -7
- package/node_modules/@herbertgao/pi-subagents/src/structured-output.ts +136 -0
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +126 -8
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-mention.ts +274 -0
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +20 -5
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +10 -4
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +167 -22
- package/node_modules/@herbertgao/pi-subagents/src/ui/workflow-card.ts +555 -0
- package/node_modules/@herbertgao/pi-subagents/src/ui/workflow-dialog.ts +1304 -0
- package/node_modules/@herbertgao/pi-subagents/src/ui/workflow-menu.ts +226 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/collisions.ts +122 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/entry.ts +47 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/host.ts +463 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/journal.ts +164 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/json-schema.ts +142 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/meta.ts +401 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/progress.ts +622 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/runtime.ts +1399 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/saved.ts +230 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/task.ts +333 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/tool-description.ts +200 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/worker-source.ts +781 -0
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +97 -95
- package/node_modules/@herbertgao/pi-subagents/src/xml.ts +13 -0
- package/node_modules/@herbertgao/resume-from/package.json +1 -1
- package/node_modules/@herbertgao/sol-pi/README.md +3 -3
- package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +4 -4
- package/node_modules/@herbertgao/sol-pi/agents-install.md +4 -4
- package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +6 -6
- package/node_modules/@herbertgao/sol-pi/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/dist/index.ts +39 -89
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
- package/node_modules/@narumitw/pi-btw/package.json +4 -4
- package/node_modules/@narumitw/pi-btw/src/btw.ts +28 -87
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +8 -0
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +40 -37
- package/node_modules/@narumitw/pi-caffeinate/README.md +21 -66
- package/node_modules/@narumitw/pi-caffeinate/dist/index.ts +10 -41
- package/node_modules/@narumitw/pi-caffeinate/dist/index.ts.map +2 -2
- package/node_modules/@narumitw/pi-caffeinate/package.json +50 -51
- package/node_modules/@narumitw/pi-caffeinate/src/caffeinate.ts +637 -663
- package/node_modules/@narumitw/pi-caffeinate/src/dbus-inhibit.ts +114 -120
- package/node_modules/@narumitw/pi-caffeinate/src/inhibitor-process.ts +29 -29
- package/node_modules/@narumitw/pi-caffeinate/src/inhibitors.ts +108 -126
- package/node_modules/@narumitw/pi-caffeinate/src/settings.ts +124 -128
- package/node_modules/pi-multi-account/CHANGELOG.md +1209 -0
- package/node_modules/pi-multi-account/CONTRIBUTING.md +61 -0
- package/node_modules/pi-multi-account/LICENSE +21 -0
- package/node_modules/pi-multi-account/README.md +197 -0
- package/node_modules/pi-multi-account/SECURITY.md +27 -0
- package/node_modules/pi-multi-account/auth-file-transaction.ts +56 -0
- package/node_modules/pi-multi-account/child-usability.ts +233 -0
- package/node_modules/pi-multi-account/compaction-summary.ts +32 -0
- package/node_modules/pi-multi-account/completion-route-planner.ts +224 -0
- package/node_modules/pi-multi-account/context-guard.ts +420 -0
- package/node_modules/pi-multi-account/cursor/LICENSE +21 -0
- package/node_modules/pi-multi-account/cursor/NOTICE +2 -0
- package/node_modules/pi-multi-account/cursor/auth.ts +165 -0
- package/node_modules/pi-multi-account/cursor/bridge-handle.ts +155 -0
- package/node_modules/pi-multi-account/cursor/conversation-registry.ts +104 -0
- package/node_modules/pi-multi-account/cursor/cursor-models-raw.json +611 -0
- package/node_modules/pi-multi-account/cursor/cursor-shared.ts +192 -0
- package/node_modules/pi-multi-account/cursor/h2-bridge.mjs +175 -0
- package/node_modules/pi-multi-account/cursor/index.ts +572 -0
- package/node_modules/pi-multi-account/cursor/message-parsing.ts +323 -0
- package/node_modules/pi-multi-account/cursor/prompt-usage.ts +53 -0
- package/node_modules/pi-multi-account/cursor/proto/agent_pb.ts +15294 -0
- package/node_modules/pi-multi-account/cursor/proxy.ts +2510 -0
- package/node_modules/pi-multi-account/cursor/session-lifecycle.ts +40 -0
- package/node_modules/pi-multi-account/cursor/sse-keepalive.ts +24 -0
- package/node_modules/pi-multi-account/cursor/stream-lifecycle.ts +193 -0
- package/node_modules/pi-multi-account/cursor/upstream-watchdog.ts +88 -0
- package/node_modules/pi-multi-account/cursor-bridge.ts +240 -0
- package/node_modules/pi-multi-account/cursor-model-name.ts +12 -0
- package/node_modules/pi-multi-account/index.ts +11825 -0
- package/node_modules/pi-multi-account/model-catalog.ts +354 -0
- package/node_modules/pi-multi-account/package.json +101 -0
- package/node_modules/pi-multi-account/pi-contract.ts +281 -0
- package/node_modules/pi-multi-account/provider-payload-stream.ts +44 -0
- package/node_modules/pi-multi-account/provider-priority.ts +189 -0
- package/node_modules/pi-multi-account/slot-proxy-auth.ts +167 -0
- package/node_modules/pi-multi-account/slot-proxy.ts +344 -0
- package/node_modules/pi-multi-account/state-file-transaction.ts +67 -0
- package/node_modules/pi-multi-account/usage.ts +1099 -0
- package/node_modules/pi-typesafe/README.md +6 -2
- package/node_modules/pi-typesafe/dist/client.d.ts +11 -0
- package/node_modules/pi-typesafe/dist/client.js +45 -10
- package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
- package/node_modules/pi-typesafe/dist/index.js +1 -1
- package/node_modules/pi-typesafe/package.json +2 -2
- package/node_modules/pi-web-access/CHANGELOG.md +36 -0
- package/node_modules/pi-web-access/README.md +75 -18
- package/node_modules/pi-web-access/anysearch.ts +4 -15
- package/node_modules/pi-web-access/bocha.ts +3 -22
- package/node_modules/pi-web-access/brave.ts +3 -21
- package/node_modules/pi-web-access/brightdata.ts +5 -32
- package/node_modules/pi-web-access/content-find.ts +168 -53
- package/node_modules/pi-web-access/curator-page.ts +4 -1
- package/node_modules/pi-web-access/curator-run.ts +2 -1
- package/node_modules/pi-web-access/curator-server.ts +1 -0
- package/node_modules/pi-web-access/dist/index.js +24620 -0
- package/node_modules/pi-web-access/domain-filter-normalization.ts +14 -0
- package/node_modules/pi-web-access/duckduckgo.ts +3 -21
- package/node_modules/pi-web-access/extract.ts +3 -1
- package/node_modules/pi-web-access/firecrawl.ts +5 -29
- package/node_modules/pi-web-access/gemini-search.ts +81 -32
- package/node_modules/pi-web-access/index.ts +149 -148
- package/node_modules/pi-web-access/jina-search.ts +4 -15
- package/node_modules/pi-web-access/kagi.ts +4 -13
- package/node_modules/pi-web-access/kimi-search.ts +5 -30
- package/node_modules/pi-web-access/mistral-search.ts +1 -15
- package/node_modules/pi-web-access/ollama.ts +2 -7
- package/node_modules/pi-web-access/openai-search.ts +174 -36
- package/node_modules/pi-web-access/opencode-session-headers.ts +24 -0
- package/node_modules/pi-web-access/package.json +10 -4
- package/node_modules/pi-web-access/page-query.ts +10 -2
- package/node_modules/pi-web-access/parallel.ts +1 -15
- package/node_modules/pi-web-access/pdf-extract.ts +3 -0
- package/node_modules/pi-web-access/querit.ts +5 -29
- package/node_modules/pi-web-access/search-answer-formatting.ts +11 -0
- package/node_modules/pi-web-access/search-result-count-normalization.ts +4 -0
- package/node_modules/pi-web-access/search1api.ts +5 -29
- package/node_modules/pi-web-access/searchinfinity.ts +5 -29
- package/node_modules/pi-web-access/searxng.ts +3 -21
- package/node_modules/pi-web-access/serpapi.ts +5 -28
- package/node_modules/pi-web-access/serpbase.ts +3 -22
- package/node_modules/pi-web-access/serpdive.ts +3 -21
- package/node_modules/pi-web-access/serper.ts +5 -28
- package/node_modules/pi-web-access/serply.ts +197 -0
- package/node_modules/pi-web-access/source-check.ts +11 -47
- package/node_modules/pi-web-access/summary-review.ts +7 -3
- package/node_modules/pi-web-access/tavily.ts +3 -21
- package/node_modules/pi-web-access/tinyfish.ts +5 -29
- package/node_modules/pi-web-access/utils.ts +9 -1
- package/node_modules/pi-web-access/valyu.ts +5 -28
- package/node_modules/pi-web-access/xai-search.ts +1 -15
- package/node_modules/pi-web-access/xcrawl.ts +5 -32
- package/package.json +17 -11
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor Provider Extension for pi
|
|
3
|
+
*
|
|
4
|
+
* Provides access to Cursor models (Claude, GPT, Gemini, etc.) via:
|
|
5
|
+
* 1. Browser-based PKCE OAuth login to Cursor
|
|
6
|
+
* 2. Local proxy translating OpenAI format → Cursor gRPC protocol
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* /login cursor — authenticate via browser
|
|
10
|
+
* /model — select any Cursor model
|
|
11
|
+
*
|
|
12
|
+
* Based on https://github.com/ephraimduncan/opencode-cursor by Ephraim Duncan.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import rawFallbackModels from "./cursor-models-raw.json" with { type: "json" };
|
|
16
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import { cursorPayloadStream } from "../provider-payload-stream.ts";
|
|
18
|
+
import { cursorModelDisplayName } from "../cursor-model-name.ts";
|
|
19
|
+
import type { OAuthCredentials, OAuthLoginCallbacks } from "@earendil-works/pi-ai";
|
|
20
|
+
import { appendFileSync } from "node:fs";
|
|
21
|
+
import { tmpdir } from "node:os";
|
|
22
|
+
import { join as pathJoin } from "node:path";
|
|
23
|
+
import {
|
|
24
|
+
generateCursorAuthParams,
|
|
25
|
+
getTokenExpiry,
|
|
26
|
+
pollCursorAuth,
|
|
27
|
+
refreshCursorToken,
|
|
28
|
+
} from "./auth.ts";
|
|
29
|
+
import { cleanupSessionState, getCursorModels, resetConversationForSession, startProxy, type CursorModel } from "./proxy.ts";
|
|
30
|
+
import { registerSessionLifecycleHooks } from "./session-lifecycle.ts";
|
|
31
|
+
|
|
32
|
+
// ── Cost estimation ──
|
|
33
|
+
|
|
34
|
+
interface ModelCost {
|
|
35
|
+
input: number;
|
|
36
|
+
output: number;
|
|
37
|
+
cacheRead: number;
|
|
38
|
+
cacheWrite: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let extensionDebugLogFilePath: string | undefined;
|
|
42
|
+
|
|
43
|
+
function isExtensionDebugEnabled(): boolean {
|
|
44
|
+
const raw = process.env.PI_CURSOR_PROVIDER_DEBUG?.trim().toLowerCase();
|
|
45
|
+
return !!raw && raw !== "0" && raw !== "false" && raw !== "off";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getExtensionDebugLogFilePath(): string {
|
|
49
|
+
if (extensionDebugLogFilePath) return extensionDebugLogFilePath;
|
|
50
|
+
const configured = process.env.PI_CURSOR_PROVIDER_EXTENSION_DEBUG_FILE?.trim();
|
|
51
|
+
if (configured) {
|
|
52
|
+
extensionDebugLogFilePath = configured;
|
|
53
|
+
return extensionDebugLogFilePath;
|
|
54
|
+
}
|
|
55
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
56
|
+
extensionDebugLogFilePath = pathJoin(tmpdir(), `pi-cursor-provider-extension-debug-${stamp}-${process.pid}.log`);
|
|
57
|
+
return extensionDebugLogFilePath;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function truncateDebugValue(value: string, max = 240): string {
|
|
61
|
+
return value.length > max ? `${value.slice(0, max)}…<truncated ${value.length - max} chars>` : value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function summarizeContent(content: unknown): unknown {
|
|
65
|
+
if (typeof content === "string") return truncateDebugValue(content);
|
|
66
|
+
if (!Array.isArray(content)) return content;
|
|
67
|
+
return content.map((block) => {
|
|
68
|
+
if (!block || typeof block !== "object") return block;
|
|
69
|
+
const typed = block as Record<string, unknown>;
|
|
70
|
+
switch (typed.type) {
|
|
71
|
+
case "text":
|
|
72
|
+
return { type: "text", text: truncateDebugValue(String(typed.text ?? "")) };
|
|
73
|
+
case "thinking":
|
|
74
|
+
return { type: "thinking", thinking: truncateDebugValue(String(typed.thinking ?? "")) };
|
|
75
|
+
case "toolCall":
|
|
76
|
+
return {
|
|
77
|
+
type: "toolCall",
|
|
78
|
+
id: typed.id,
|
|
79
|
+
name: typed.name,
|
|
80
|
+
arguments: typed.arguments,
|
|
81
|
+
};
|
|
82
|
+
case "image":
|
|
83
|
+
return { type: "image", mimeType: typed.mimeType, data: `<redacted base64 ${String(typed.data ?? "").length} chars>` };
|
|
84
|
+
default:
|
|
85
|
+
return typed;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function summarizeMessage(message: unknown): unknown {
|
|
91
|
+
if (!message || typeof message !== "object") return message;
|
|
92
|
+
const typed = message as Record<string, unknown>;
|
|
93
|
+
return {
|
|
94
|
+
role: typed.role,
|
|
95
|
+
stopReason: typed.stopReason,
|
|
96
|
+
toolCallId: typed.toolCallId,
|
|
97
|
+
toolName: typed.toolName,
|
|
98
|
+
isError: typed.isError,
|
|
99
|
+
errorMessage: typed.errorMessage,
|
|
100
|
+
content: summarizeContent(typed.content),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function summarizeBranchTail(ctx: { sessionManager?: { getBranch?: () => unknown[]; getLeafId?: () => string; getSessionId?: () => string } }, limit = 6): unknown {
|
|
105
|
+
try {
|
|
106
|
+
const branch = ctx.sessionManager?.getBranch?.();
|
|
107
|
+
if (!Array.isArray(branch)) return undefined;
|
|
108
|
+
return {
|
|
109
|
+
sessionId: ctx.sessionManager?.getSessionId?.(),
|
|
110
|
+
leafId: ctx.sessionManager?.getLeafId?.(),
|
|
111
|
+
size: branch.length,
|
|
112
|
+
tail: branch.slice(-limit).map((entry) => {
|
|
113
|
+
if (!entry || typeof entry !== "object") return entry;
|
|
114
|
+
const typed = entry as Record<string, unknown>;
|
|
115
|
+
return {
|
|
116
|
+
type: typed.type,
|
|
117
|
+
id: typed.id,
|
|
118
|
+
parentId: typed.parentId,
|
|
119
|
+
customType: typed.customType,
|
|
120
|
+
message: summarizeMessage(typed.message),
|
|
121
|
+
};
|
|
122
|
+
}),
|
|
123
|
+
};
|
|
124
|
+
} catch (error) {
|
|
125
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function summarizeProviderPayload(payload: unknown): unknown {
|
|
130
|
+
if (!payload || typeof payload !== "object") return payload;
|
|
131
|
+
const typed = payload as Record<string, unknown>;
|
|
132
|
+
const messages = Array.isArray(typed.messages) ? typed.messages.map((message) => summarizeMessage(message)).slice(-8) : undefined;
|
|
133
|
+
return {
|
|
134
|
+
model: typed.model,
|
|
135
|
+
stream: typed.stream,
|
|
136
|
+
pi_session_id: typed.pi_session_id,
|
|
137
|
+
messageCount: Array.isArray(typed.messages) ? typed.messages.length : undefined,
|
|
138
|
+
messages,
|
|
139
|
+
toolCount: Array.isArray(typed.tools) ? typed.tools.length : undefined,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function debugExtensionLog(event: string, data?: Record<string, unknown>): void {
|
|
144
|
+
if (!isExtensionDebugEnabled()) return;
|
|
145
|
+
const payload = JSON.stringify({
|
|
146
|
+
ts: new Date().toISOString(),
|
|
147
|
+
pid: process.pid,
|
|
148
|
+
scope: "extension",
|
|
149
|
+
event,
|
|
150
|
+
...data,
|
|
151
|
+
});
|
|
152
|
+
appendFileSync(getExtensionDebugLogFilePath(), `${payload}\n`, "utf8");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const MODEL_COST_TABLE: Record<string, ModelCost> = {
|
|
156
|
+
"claude-4-sonnet": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
157
|
+
"claude-4.5-haiku": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
158
|
+
"claude-4.5-opus": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
159
|
+
"claude-4.5-sonnet": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
160
|
+
"claude-4.6-opus": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
161
|
+
"claude-4.6-sonnet": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
162
|
+
"composer-1": { input: 1.25, output: 10, cacheRead: 0.125, cacheWrite: 0 },
|
|
163
|
+
"composer-1.5": { input: 3.5, output: 17.5, cacheRead: 0.35, cacheWrite: 0 },
|
|
164
|
+
"composer-2": { input: 0.5, output: 2.5, cacheRead: 0.2, cacheWrite: 0 },
|
|
165
|
+
"gemini-2.5-flash": { input: 0.3, output: 2.5, cacheRead: 0.03, cacheWrite: 0 },
|
|
166
|
+
"gemini-3-flash": { input: 0.5, output: 3, cacheRead: 0.05, cacheWrite: 0 },
|
|
167
|
+
"gemini-3-pro": { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 },
|
|
168
|
+
"gemini-3.1-pro": { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 },
|
|
169
|
+
"gpt-5": { input: 1.25, output: 10, cacheRead: 0.125, cacheWrite: 0 },
|
|
170
|
+
"gpt-5-mini": { input: 0.25, output: 2, cacheRead: 0.025, cacheWrite: 0 },
|
|
171
|
+
"gpt-5.2": { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 },
|
|
172
|
+
"gpt-5.2-codex": { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 },
|
|
173
|
+
"gpt-5.3-codex": { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 },
|
|
174
|
+
"gpt-5.4": { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 },
|
|
175
|
+
"gpt-5.4-mini": { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },
|
|
176
|
+
"grok-4.20": { input: 2, output: 6, cacheRead: 0.2, cacheWrite: 0 },
|
|
177
|
+
"kimi-k2.5": { input: 0.6, output: 3, cacheRead: 0.1, cacheWrite: 0 },
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const MODEL_COST_PATTERNS: Array<{ match: (id: string) => boolean; cost: ModelCost }> = [
|
|
181
|
+
{ match: (id) => /claude.*opus.*fast/i.test(id), cost: { input: 30, output: 150, cacheRead: 3, cacheWrite: 37.5 } },
|
|
182
|
+
{ match: (id) => /claude.*opus/i.test(id), cost: MODEL_COST_TABLE["claude-4.6-opus"]! },
|
|
183
|
+
{ match: (id) => /claude.*haiku/i.test(id), cost: MODEL_COST_TABLE["claude-4.5-haiku"]! },
|
|
184
|
+
{ match: (id) => /claude.*sonnet/i.test(id), cost: MODEL_COST_TABLE["claude-4.6-sonnet"]! },
|
|
185
|
+
{ match: (id) => /composer/i.test(id), cost: MODEL_COST_TABLE["composer-1"]! },
|
|
186
|
+
{ match: (id) => /gpt-5\.4.*mini/i.test(id), cost: MODEL_COST_TABLE["gpt-5.4-mini"]! },
|
|
187
|
+
{ match: (id) => /gpt-5\.4/i.test(id), cost: MODEL_COST_TABLE["gpt-5.4"]! },
|
|
188
|
+
{ match: (id) => /gpt-5\.3/i.test(id), cost: MODEL_COST_TABLE["gpt-5.3-codex"]! },
|
|
189
|
+
{ match: (id) => /gpt-5\.2/i.test(id), cost: MODEL_COST_TABLE["gpt-5.2"]! },
|
|
190
|
+
{ match: (id) => /gpt-5.*mini/i.test(id), cost: MODEL_COST_TABLE["gpt-5-mini"]! },
|
|
191
|
+
{ match: (id) => /gpt-5/i.test(id), cost: MODEL_COST_TABLE["gpt-5"]! },
|
|
192
|
+
{ match: (id) => /gemini.*3\.1/i.test(id), cost: MODEL_COST_TABLE["gemini-3.1-pro"]! },
|
|
193
|
+
{ match: (id) => /gemini.*flash/i.test(id), cost: MODEL_COST_TABLE["gemini-2.5-flash"]! },
|
|
194
|
+
{ match: (id) => /gemini/i.test(id), cost: MODEL_COST_TABLE["gemini-3-pro"]! },
|
|
195
|
+
{ match: (id) => /grok/i.test(id), cost: MODEL_COST_TABLE["grok-4.20"]! },
|
|
196
|
+
{ match: (id) => /kimi/i.test(id), cost: MODEL_COST_TABLE["kimi-k2.5"]! },
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
const DEFAULT_COST: ModelCost = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 0 };
|
|
200
|
+
|
|
201
|
+
function estimateModelCost(modelId: string): ModelCost {
|
|
202
|
+
const normalized = modelId.toLowerCase();
|
|
203
|
+
const exact = MODEL_COST_TABLE[normalized];
|
|
204
|
+
if (exact) return exact;
|
|
205
|
+
const stripped = normalized.replace(/-(high|medium|low|preview|thinking|spark-preview|fast)$/g, "");
|
|
206
|
+
const strippedMatch = MODEL_COST_TABLE[stripped];
|
|
207
|
+
if (strippedMatch) return strippedMatch;
|
|
208
|
+
return MODEL_COST_PATTERNS.find((p) => p.match(normalized))?.cost ?? DEFAULT_COST;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
// ── Effort-level dedup ──
|
|
213
|
+
|
|
214
|
+
const EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh", "max", "none"]);
|
|
215
|
+
|
|
216
|
+
interface ParsedModelId {
|
|
217
|
+
base: string; // model ID with effort stripped
|
|
218
|
+
effort: string; // effort level, or "" if no effort suffix
|
|
219
|
+
fast: boolean; // has -fast suffix
|
|
220
|
+
thinking: boolean; // has -thinking suffix
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function parseModelId(id: string): ParsedModelId {
|
|
224
|
+
let remaining = id;
|
|
225
|
+
let fast = false;
|
|
226
|
+
let thinking = false;
|
|
227
|
+
|
|
228
|
+
if (remaining.endsWith("-fast")) {
|
|
229
|
+
fast = true;
|
|
230
|
+
remaining = remaining.slice(0, -5);
|
|
231
|
+
}
|
|
232
|
+
if (remaining.endsWith("-thinking")) {
|
|
233
|
+
thinking = true;
|
|
234
|
+
remaining = remaining.slice(0, -9);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const lastDash = remaining.lastIndexOf("-");
|
|
238
|
+
if (lastDash >= 0) {
|
|
239
|
+
const suffix = remaining.slice(lastDash + 1);
|
|
240
|
+
if (EFFORT_LEVELS.has(suffix)) {
|
|
241
|
+
return { base: remaining.slice(0, lastDash), effort: suffix, fast, thinking };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return { base: remaining, effort: "", fast, thinking };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
interface ProcessedModel extends CursorModel {
|
|
249
|
+
supportsEffort: boolean;
|
|
250
|
+
effortMap?: Record<string, string>;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function supportsReasoningModelId(id: string): boolean {
|
|
254
|
+
const { base, effort, thinking } = parseModelId(id);
|
|
255
|
+
if (effort || thinking) return true;
|
|
256
|
+
if (base === "default") return true;
|
|
257
|
+
// Cursor namespaces some families with a "cursor-" prefix (cursor-grok-4.6-high, …).
|
|
258
|
+
// Matching the raw base against the family list misclassified every such model as
|
|
259
|
+
// non-reasoning — and Pi clamps thinking to "off" for non-reasoning models, which also
|
|
260
|
+
// meant no effort ever reached the proxy.
|
|
261
|
+
const family = base.replace(/^cursor-/i, "");
|
|
262
|
+
return /^(claude|composer|gemini|gpt|grok|kimi)(-|$)/i.test(family);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Ordered effort levels from lowest to highest.
|
|
267
|
+
* "" = default (no effort suffix in model ID).
|
|
268
|
+
*/
|
|
269
|
+
const EFFORT_ORDER = ["none", "low", "", "medium", "high", "xhigh", "max"] as const;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Build a reasoning-effort map from the set of available effort suffixes.
|
|
273
|
+
* For each pi effort level (minimal/low/medium/high/xhigh), picks the closest
|
|
274
|
+
* available cursor effort, falling back to the lowest available.
|
|
275
|
+
*/
|
|
276
|
+
export function buildEffortMap(efforts: Set<string>): Record<string, string> {
|
|
277
|
+
const sorted = EFFORT_ORDER.filter(e => efforts.has(e));
|
|
278
|
+
if (sorted.length === 0) return {};
|
|
279
|
+
const lowest = sorted[0]!;
|
|
280
|
+
|
|
281
|
+
const pick = (...targets: string[]) => {
|
|
282
|
+
for (const t of targets) if (efforts.has(t)) return t;
|
|
283
|
+
return lowest;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
minimal: pick("none", "low", ""),
|
|
288
|
+
low: pick("low", "none", ""),
|
|
289
|
+
medium: pick("medium", "", "low"),
|
|
290
|
+
high: pick("high", "medium", ""),
|
|
291
|
+
xhigh: pick("max", "xhigh", "high"),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Dedup raw models: collapse effort variants into one entry with supportsReasoningEffort. */
|
|
296
|
+
export function processModels(raw: CursorModel[]): ProcessedModel[] {
|
|
297
|
+
// Group by (base, fast, thinking)
|
|
298
|
+
const groups = new Map<string, {
|
|
299
|
+
base: string; fast: boolean; thinking: boolean;
|
|
300
|
+
efforts: Map<string, CursorModel>;
|
|
301
|
+
}>();
|
|
302
|
+
|
|
303
|
+
for (const model of raw) {
|
|
304
|
+
const p = parseModelId(model.id);
|
|
305
|
+
const key = `${p.base}|${p.fast}|${p.thinking}`;
|
|
306
|
+
let g = groups.get(key);
|
|
307
|
+
if (!g) {
|
|
308
|
+
g = { base: p.base, fast: p.fast, thinking: p.thinking, efforts: new Map() };
|
|
309
|
+
groups.set(key, g);
|
|
310
|
+
}
|
|
311
|
+
g.efforts.set(p.effort, model);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const result: ProcessedModel[] = [];
|
|
315
|
+
|
|
316
|
+
for (const g of groups.values()) {
|
|
317
|
+
// Dedup when there are multiple effort variants, OR a single variant
|
|
318
|
+
// whose effort is non-empty (e.g. claude-4.5-opus-high — strip the
|
|
319
|
+
// mandatory effort suffix so the model appears as claude-4.5-opus
|
|
320
|
+
// with effort mapping).
|
|
321
|
+
const hasOnlyEffortVariants = g.efforts.size === 1 && !g.efforts.has("");
|
|
322
|
+
if (g.efforts.size >= 2 || hasOnlyEffortVariants) {
|
|
323
|
+
// Pick representative: prefer "medium" or default ("") for name/metadata
|
|
324
|
+
const rep = g.efforts.get("medium") ?? g.efforts.get("") ?? [...g.efforts.values()][0]!;
|
|
325
|
+
|
|
326
|
+
// Build deduped model ID: base + thinking/fast suffix (no effort)
|
|
327
|
+
let id = g.base;
|
|
328
|
+
if (g.thinking) id += "-thinking";
|
|
329
|
+
if (g.fast) id += "-fast";
|
|
330
|
+
|
|
331
|
+
const effortMap = buildEffortMap(new Set(g.efforts.keys()));
|
|
332
|
+
const named = g.efforts.get("") ?? g.efforts.get("high") ?? rep;
|
|
333
|
+
|
|
334
|
+
result.push({
|
|
335
|
+
...rep,
|
|
336
|
+
id,
|
|
337
|
+
name: cursorModelDisplayName(named.name),
|
|
338
|
+
supportsEffort: true,
|
|
339
|
+
effortMap,
|
|
340
|
+
});
|
|
341
|
+
} else {
|
|
342
|
+
// Keep single entries as-is (base model without effort variants)
|
|
343
|
+
for (const model of g.efforts.values()) {
|
|
344
|
+
result.push({ ...model, supportsEffort: false });
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return result.sort((a, b) => a.id.localeCompare(b.id));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function modelConfig(m: ProcessedModel) {
|
|
353
|
+
const thinkingLevelMap =
|
|
354
|
+
m.supportsEffort && m.effortMap
|
|
355
|
+
? { off: null, ...m.effortMap }
|
|
356
|
+
: undefined;
|
|
357
|
+
return {
|
|
358
|
+
id: m.id,
|
|
359
|
+
name: cursorModelDisplayName(m.name),
|
|
360
|
+
reasoning: supportsReasoningModelId(m.id),
|
|
361
|
+
input: ["text"] as ("text" | "image")[],
|
|
362
|
+
cost: estimateModelCost(m.id),
|
|
363
|
+
contextWindow: m.contextWindow,
|
|
364
|
+
maxTokens: m.maxTokens,
|
|
365
|
+
...(thinkingLevelMap ? { thinkingLevelMap } : {}),
|
|
366
|
+
compat: {
|
|
367
|
+
supportsDeveloperRole: false,
|
|
368
|
+
supportsReasoningEffort: m.supportsEffort,
|
|
369
|
+
...(m.supportsEffort && m.effortMap && {
|
|
370
|
+
reasoningEffortMap: m.effortMap,
|
|
371
|
+
}),
|
|
372
|
+
maxTokensField: "max_tokens" as const,
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
export const FALLBACK_MODELS: CursorModel[] = (rawFallbackModels as CursorModel[]).map((model) => ({
|
|
379
|
+
...model,
|
|
380
|
+
reasoning: supportsReasoningModelId(model.id),
|
|
381
|
+
}));
|
|
382
|
+
|
|
383
|
+
// ── Extension ──
|
|
384
|
+
|
|
385
|
+
export function registerSessionLifecycleCleanup(pi: ExtensionAPI) {
|
|
386
|
+
registerSessionLifecycleHooks(pi, {
|
|
387
|
+
cleanupSessionState,
|
|
388
|
+
resetConversationForSession,
|
|
389
|
+
debug: debugExtensionLog,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function registerExtensionDebugHooks(pi: ExtensionAPI) {
|
|
394
|
+
if (!isExtensionDebugEnabled()) return;
|
|
395
|
+
|
|
396
|
+
pi.on("message_start", async (event, ctx) => {
|
|
397
|
+
if (ctx.model?.provider !== "cursor") return;
|
|
398
|
+
debugExtensionLog("message.start", {
|
|
399
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
400
|
+
leafId: ctx.sessionManager.getLeafId?.(),
|
|
401
|
+
model: ctx.model?.id,
|
|
402
|
+
message: summarizeMessage((event as { message?: unknown }).message),
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
pi.on("message_update", async (event, ctx) => {
|
|
407
|
+
if (ctx.model?.provider !== "cursor") return;
|
|
408
|
+
const typedEvent = event as { message?: unknown; assistantMessageEvent?: Record<string, unknown> };
|
|
409
|
+
debugExtensionLog("message.update", {
|
|
410
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
411
|
+
leafId: ctx.sessionManager.getLeafId?.(),
|
|
412
|
+
model: ctx.model?.id,
|
|
413
|
+
assistantMessageEvent: typedEvent.assistantMessageEvent
|
|
414
|
+
? {
|
|
415
|
+
type: typedEvent.assistantMessageEvent.type,
|
|
416
|
+
delta: truncateDebugValue(String((typedEvent.assistantMessageEvent as Record<string, unknown>).delta ?? (typedEvent.assistantMessageEvent as Record<string, unknown>).content ?? "")),
|
|
417
|
+
}
|
|
418
|
+
: undefined,
|
|
419
|
+
message: summarizeMessage(typedEvent.message),
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
pi.on("message_end", async (event, ctx) => {
|
|
424
|
+
if (ctx.model?.provider !== "cursor") return;
|
|
425
|
+
debugExtensionLog("message.end", {
|
|
426
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
427
|
+
leafId: ctx.sessionManager.getLeafId?.(),
|
|
428
|
+
model: ctx.model?.id,
|
|
429
|
+
message: summarizeMessage((event as { message?: unknown }).message),
|
|
430
|
+
branch: summarizeBranchTail(ctx),
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
pi.on("context", async (event, ctx) => {
|
|
435
|
+
if (ctx.model?.provider !== "cursor") return;
|
|
436
|
+
const typedEvent = event as { messages?: unknown[] };
|
|
437
|
+
debugExtensionLog("context", {
|
|
438
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
439
|
+
leafId: ctx.sessionManager.getLeafId?.(),
|
|
440
|
+
model: ctx.model?.id,
|
|
441
|
+
messageCount: Array.isArray(typedEvent.messages) ? typedEvent.messages.length : undefined,
|
|
442
|
+
messages: Array.isArray(typedEvent.messages)
|
|
443
|
+
? typedEvent.messages.slice(-8).map((message) => summarizeMessage(message))
|
|
444
|
+
: undefined,
|
|
445
|
+
branch: summarizeBranchTail(ctx),
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
pi.on("turn_end", async (event, ctx) => {
|
|
450
|
+
if (ctx.model?.provider !== "cursor") return;
|
|
451
|
+
const typedEvent = event as { turnIndex?: number; message?: unknown; toolResults?: unknown[] };
|
|
452
|
+
debugExtensionLog("turn.end", {
|
|
453
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
454
|
+
leafId: ctx.sessionManager.getLeafId?.(),
|
|
455
|
+
model: ctx.model?.id,
|
|
456
|
+
turnIndex: typedEvent.turnIndex,
|
|
457
|
+
message: summarizeMessage(typedEvent.message),
|
|
458
|
+
toolResults: Array.isArray(typedEvent.toolResults)
|
|
459
|
+
? typedEvent.toolResults.map((message) => summarizeMessage(message))
|
|
460
|
+
: undefined,
|
|
461
|
+
branch: summarizeBranchTail(ctx),
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
debugExtensionLog("extension.debug_hooks_registered", { logFile: getExtensionDebugLogFilePath() });
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export default async function (pi: ExtensionAPI) {
|
|
469
|
+
// Current access token, updated by login/refresh/getApiKey
|
|
470
|
+
let currentToken = "";
|
|
471
|
+
|
|
472
|
+
// Start proxy eagerly — it just binds a port, no auth needed until a request arrives.
|
|
473
|
+
// The getAccessToken callback reads currentToken at request time.
|
|
474
|
+
const proxyReady = startProxy(async () => {
|
|
475
|
+
if (!currentToken) throw new Error("Not logged in to Cursor. Run /login cursor");
|
|
476
|
+
return currentToken;
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const skipDedup = !!process.env.PI_CURSOR_RAW_MODELS;
|
|
480
|
+
|
|
481
|
+
registerSessionLifecycleCleanup(pi);
|
|
482
|
+
registerExtensionDebugHooks(pi);
|
|
483
|
+
debugExtensionLog("extension.start", {
|
|
484
|
+
debugLogFile: isExtensionDebugEnabled() ? getExtensionDebugLogFilePath() : undefined,
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
488
|
+
const payload = event.payload as Record<string, unknown> | undefined;
|
|
489
|
+
if (payload && ctx.model?.provider === "cursor") {
|
|
490
|
+
payload.pi_session_id = ctx.sessionManager.getSessionId();
|
|
491
|
+
debugExtensionLog("before_provider_request", {
|
|
492
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
493
|
+
leafId: ctx.sessionManager.getLeafId?.(),
|
|
494
|
+
model: ctx.model?.id,
|
|
495
|
+
payload: summarizeProviderPayload(payload),
|
|
496
|
+
branch: summarizeBranchTail(ctx),
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
return payload;
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// Await proxy so models are registered before pi proceeds with model resolution.
|
|
503
|
+
const port = await proxyReady;
|
|
504
|
+
register(pi, port, FALLBACK_MODELS);
|
|
505
|
+
|
|
506
|
+
function register(pi: ExtensionAPI, port: number, rawModels: CursorModel[]) {
|
|
507
|
+
const baseUrl = `http://127.0.0.1:${port}/v1`;
|
|
508
|
+
const processed = skipDedup
|
|
509
|
+
? rawModels.map(m => ({ ...m, supportsEffort: false } as ProcessedModel))
|
|
510
|
+
: processModels(rawModels);
|
|
511
|
+
|
|
512
|
+
pi.registerProvider("cursor", {
|
|
513
|
+
baseUrl,
|
|
514
|
+
api: "openai-completions",
|
|
515
|
+
streamSimple: cursorPayloadStream,
|
|
516
|
+
models: processed.map(modelConfig),
|
|
517
|
+
oauth: {
|
|
518
|
+
name: "Cursor",
|
|
519
|
+
|
|
520
|
+
async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
|
521
|
+
const { verifier, uuid, loginUrl } = await generateCursorAuthParams();
|
|
522
|
+
callbacks.onAuth({ url: loginUrl });
|
|
523
|
+
const { accessToken, refreshToken } = await pollCursorAuth(uuid, verifier);
|
|
524
|
+
currentToken = accessToken;
|
|
525
|
+
|
|
526
|
+
// Discover real models and re-register
|
|
527
|
+
const realPort = await proxyReady;
|
|
528
|
+
try {
|
|
529
|
+
const discovered = await getCursorModels(accessToken);
|
|
530
|
+
if (discovered.length > 0) register(pi, realPort, discovered);
|
|
531
|
+
} catch {
|
|
532
|
+
// catalog discovery is best-effort; FALLBACK_MODELS are already registered
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return {
|
|
536
|
+
refresh: refreshToken,
|
|
537
|
+
access: accessToken,
|
|
538
|
+
expires: getTokenExpiry(accessToken),
|
|
539
|
+
};
|
|
540
|
+
},
|
|
541
|
+
|
|
542
|
+
async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
|
|
543
|
+
const refreshed = await refreshCursorToken(credentials.refresh);
|
|
544
|
+
currentToken = refreshed.access;
|
|
545
|
+
|
|
546
|
+
// Discover real models on refresh too
|
|
547
|
+
const realPort = await proxyReady;
|
|
548
|
+
try {
|
|
549
|
+
const discovered = await getCursorModels(refreshed.access);
|
|
550
|
+
if (discovered.length > 0) register(pi, realPort, discovered);
|
|
551
|
+
} catch {
|
|
552
|
+
// catalog discovery is best-effort; FALLBACK_MODELS are already registered
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return refreshed;
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
getApiKey(credentials: OAuthCredentials): string {
|
|
559
|
+
currentToken = credentials.access;
|
|
560
|
+
return "cursor-proxy";
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/** Drop controller-owned Cursor state after a credentialless task request. */
|
|
570
|
+
export function cleanupControllerSession(sessionId: string): void {
|
|
571
|
+
cleanupSessionState(sessionId);
|
|
572
|
+
}
|