@playwo/opencode-cursor-oauth 0.0.0-dev.e3644b4a140d → 0.0.0-dev.eb79b6283515
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/auth.js +1 -2
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/cursor/bidi-session.d.ts +12 -0
- package/dist/cursor/bidi-session.js +164 -0
- package/dist/cursor/config.d.ts +4 -0
- package/dist/cursor/config.js +4 -0
- package/dist/cursor/connect-framing.d.ts +10 -0
- package/dist/cursor/connect-framing.js +80 -0
- package/dist/cursor/headers.d.ts +6 -0
- package/dist/cursor/headers.js +16 -0
- package/dist/cursor/index.d.ts +5 -0
- package/dist/cursor/index.js +5 -0
- package/dist/cursor/unary-rpc.d.ts +12 -0
- package/dist/cursor/unary-rpc.js +124 -0
- package/dist/index.d.ts +2 -14
- package/dist/index.js +2 -306
- package/dist/logger.js +7 -2
- package/dist/models.js +1 -23
- package/dist/openai/index.d.ts +3 -0
- package/dist/openai/index.js +3 -0
- package/dist/openai/messages.d.ts +39 -0
- package/dist/openai/messages.js +228 -0
- package/dist/openai/tools.d.ts +7 -0
- package/dist/openai/tools.js +58 -0
- package/dist/openai/types.d.ts +41 -0
- package/dist/openai/types.js +1 -0
- package/dist/plugin/cursor-auth-plugin.d.ts +3 -0
- package/dist/plugin/cursor-auth-plugin.js +139 -0
- package/dist/proto/agent_pb.js +637 -319
- package/dist/provider/index.d.ts +2 -0
- package/dist/provider/index.js +2 -0
- package/dist/provider/model-cost.d.ts +9 -0
- package/dist/provider/model-cost.js +206 -0
- package/dist/provider/models.d.ts +8 -0
- package/dist/provider/models.js +86 -0
- package/dist/proxy/bridge-non-streaming.d.ts +3 -0
- package/dist/proxy/bridge-non-streaming.js +119 -0
- package/dist/proxy/bridge-session.d.ts +5 -0
- package/dist/proxy/bridge-session.js +11 -0
- package/dist/proxy/bridge-streaming.d.ts +5 -0
- package/dist/proxy/bridge-streaming.js +317 -0
- package/dist/proxy/bridge.d.ts +3 -0
- package/dist/proxy/bridge.js +3 -0
- package/dist/proxy/chat-completion.d.ts +2 -0
- package/dist/proxy/chat-completion.js +114 -0
- package/dist/proxy/conversation-meta.d.ts +12 -0
- package/dist/proxy/conversation-meta.js +1 -0
- package/dist/proxy/conversation-state.d.ts +35 -0
- package/dist/proxy/conversation-state.js +95 -0
- package/dist/proxy/cursor-request.d.ts +6 -0
- package/dist/proxy/cursor-request.js +104 -0
- package/dist/proxy/index.d.ts +12 -0
- package/dist/proxy/index.js +12 -0
- package/dist/proxy/server.d.ts +6 -0
- package/dist/proxy/server.js +107 -0
- package/dist/proxy/sse.d.ts +5 -0
- package/dist/proxy/sse.js +5 -0
- package/dist/proxy/state-sync.d.ts +2 -0
- package/dist/proxy/state-sync.js +17 -0
- package/dist/proxy/stream-dispatch.d.ts +42 -0
- package/dist/proxy/stream-dispatch.js +619 -0
- package/dist/proxy/stream-state.d.ts +9 -0
- package/dist/proxy/stream-state.js +1 -0
- package/dist/proxy/title.d.ts +1 -0
- package/dist/proxy/title.js +103 -0
- package/dist/proxy/types.d.ts +27 -0
- package/dist/proxy/types.js +1 -0
- package/dist/proxy.d.ts +2 -20
- package/dist/proxy.js +2 -1689
- package/package.json +1 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { AgentClientMessageSchema, AgentRunRequestSchema, ConversationActionSchema, ConversationStateStructureSchema, ConversationStepSchema, AgentConversationTurnStructureSchema, ConversationTurnStructureSchema, AssistantMessageSchema, ModelDetailsSchema, ResumeActionSchema, UserMessageActionSchema, UserMessageSchema, } from "../proto/agent_pb";
|
|
4
|
+
export function buildCursorRequest(modelId, systemPrompt, userText, turns, conversationId, checkpoint, existingBlobStore) {
|
|
5
|
+
const blobStore = new Map(existingBlobStore ?? []);
|
|
6
|
+
// System prompt → blob store (Cursor requests it back via KV handshake)
|
|
7
|
+
const systemJson = JSON.stringify({ role: "system", content: systemPrompt });
|
|
8
|
+
const systemBytes = new TextEncoder().encode(systemJson);
|
|
9
|
+
const systemBlobId = new Uint8Array(createHash("sha256").update(systemBytes).digest());
|
|
10
|
+
blobStore.set(Buffer.from(systemBlobId).toString("hex"), systemBytes);
|
|
11
|
+
let conversationState;
|
|
12
|
+
if (checkpoint) {
|
|
13
|
+
conversationState = fromBinary(ConversationStateStructureSchema, checkpoint);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
const turnBytes = [];
|
|
17
|
+
for (const turn of turns) {
|
|
18
|
+
const userMsg = create(UserMessageSchema, {
|
|
19
|
+
text: turn.userText,
|
|
20
|
+
messageId: crypto.randomUUID(),
|
|
21
|
+
});
|
|
22
|
+
const userMsgBytes = toBinary(UserMessageSchema, userMsg);
|
|
23
|
+
const stepBytes = [];
|
|
24
|
+
if (turn.assistantText) {
|
|
25
|
+
const step = create(ConversationStepSchema, {
|
|
26
|
+
message: {
|
|
27
|
+
case: "assistantMessage",
|
|
28
|
+
value: create(AssistantMessageSchema, { text: turn.assistantText }),
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
stepBytes.push(toBinary(ConversationStepSchema, step));
|
|
32
|
+
}
|
|
33
|
+
const agentTurn = create(AgentConversationTurnStructureSchema, {
|
|
34
|
+
userMessage: userMsgBytes,
|
|
35
|
+
steps: stepBytes,
|
|
36
|
+
});
|
|
37
|
+
const turnStructure = create(ConversationTurnStructureSchema, {
|
|
38
|
+
turn: { case: "agentConversationTurn", value: agentTurn },
|
|
39
|
+
});
|
|
40
|
+
turnBytes.push(toBinary(ConversationTurnStructureSchema, turnStructure));
|
|
41
|
+
}
|
|
42
|
+
conversationState = create(ConversationStateStructureSchema, {
|
|
43
|
+
rootPromptMessagesJson: [systemBlobId],
|
|
44
|
+
turns: turnBytes,
|
|
45
|
+
todos: [],
|
|
46
|
+
pendingToolCalls: [],
|
|
47
|
+
previousWorkspaceUris: [],
|
|
48
|
+
fileStates: {},
|
|
49
|
+
fileStatesV2: {},
|
|
50
|
+
summaryArchives: [],
|
|
51
|
+
turnTimings: [],
|
|
52
|
+
subagentStates: {},
|
|
53
|
+
selfSummaryCount: 0,
|
|
54
|
+
readPaths: [],
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const userMessage = create(UserMessageSchema, {
|
|
58
|
+
text: userText,
|
|
59
|
+
messageId: crypto.randomUUID(),
|
|
60
|
+
});
|
|
61
|
+
const action = create(ConversationActionSchema, {
|
|
62
|
+
action: {
|
|
63
|
+
case: "userMessageAction",
|
|
64
|
+
value: create(UserMessageActionSchema, { userMessage }),
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
return buildRunRequest(modelId, conversationId, conversationState, action, blobStore);
|
|
68
|
+
}
|
|
69
|
+
export function buildCursorResumeRequest(modelId, systemPrompt, conversationId, checkpoint, existingBlobStore) {
|
|
70
|
+
const blobStore = new Map(existingBlobStore ?? []);
|
|
71
|
+
const systemJson = JSON.stringify({ role: "system", content: systemPrompt });
|
|
72
|
+
const systemBytes = new TextEncoder().encode(systemJson);
|
|
73
|
+
const systemBlobId = new Uint8Array(createHash("sha256").update(systemBytes).digest());
|
|
74
|
+
blobStore.set(Buffer.from(systemBlobId).toString("hex"), systemBytes);
|
|
75
|
+
const conversationState = fromBinary(ConversationStateStructureSchema, checkpoint);
|
|
76
|
+
const action = create(ConversationActionSchema, {
|
|
77
|
+
action: {
|
|
78
|
+
case: "resumeAction",
|
|
79
|
+
value: create(ResumeActionSchema, {}),
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
return buildRunRequest(modelId, conversationId, conversationState, action, blobStore);
|
|
83
|
+
}
|
|
84
|
+
function buildRunRequest(modelId, conversationId, conversationState, action, blobStore) {
|
|
85
|
+
const modelDetails = create(ModelDetailsSchema, {
|
|
86
|
+
modelId,
|
|
87
|
+
displayModelId: modelId,
|
|
88
|
+
displayName: modelId,
|
|
89
|
+
});
|
|
90
|
+
const runRequest = create(AgentRunRequestSchema, {
|
|
91
|
+
conversationState,
|
|
92
|
+
action,
|
|
93
|
+
modelDetails,
|
|
94
|
+
conversationId,
|
|
95
|
+
});
|
|
96
|
+
const clientMessage = create(AgentClientMessageSchema, {
|
|
97
|
+
message: { case: "runRequest", value: runRequest },
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
requestBytes: toBinary(AgentClientMessageSchema, clientMessage),
|
|
101
|
+
blobStore,
|
|
102
|
+
mcpTools: [],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./server";
|
|
2
|
+
export * from "./bridge";
|
|
3
|
+
export * from "./chat-completion";
|
|
4
|
+
export * from "./conversation-meta";
|
|
5
|
+
export * from "./conversation-state";
|
|
6
|
+
export * from "./cursor-request";
|
|
7
|
+
export * from "./sse";
|
|
8
|
+
export * from "./state-sync";
|
|
9
|
+
export * from "./stream-dispatch";
|
|
10
|
+
export * from "./stream-state";
|
|
11
|
+
export * from "./title";
|
|
12
|
+
export * from "./types";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./server";
|
|
2
|
+
export * from "./bridge";
|
|
3
|
+
export * from "./chat-completion";
|
|
4
|
+
export * from "./conversation-meta";
|
|
5
|
+
export * from "./conversation-state";
|
|
6
|
+
export * from "./cursor-request";
|
|
7
|
+
export * from "./sse";
|
|
8
|
+
export * from "./state-sync";
|
|
9
|
+
export * from "./stream-dispatch";
|
|
10
|
+
export * from "./stream-state";
|
|
11
|
+
export * from "./title";
|
|
12
|
+
export * from "./types";
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { errorDetails, logPluginError, logPluginWarn } from "../logger";
|
|
2
|
+
import { handleChatCompletion } from "./chat-completion";
|
|
3
|
+
import { activeBridges, conversationStates } from "./conversation-state";
|
|
4
|
+
let proxyServer;
|
|
5
|
+
let proxyPort;
|
|
6
|
+
let proxyAccessTokenProvider;
|
|
7
|
+
let proxyModels = [];
|
|
8
|
+
function buildOpenAIModelList(models) {
|
|
9
|
+
return models.map((model) => ({
|
|
10
|
+
id: model.id,
|
|
11
|
+
object: "model",
|
|
12
|
+
created: 0,
|
|
13
|
+
owned_by: "cursor",
|
|
14
|
+
}));
|
|
15
|
+
}
|
|
16
|
+
export function getProxyPort() {
|
|
17
|
+
return proxyPort;
|
|
18
|
+
}
|
|
19
|
+
export async function startProxy(getAccessToken, models = []) {
|
|
20
|
+
proxyAccessTokenProvider = getAccessToken;
|
|
21
|
+
proxyModels = models.map((model) => ({
|
|
22
|
+
id: model.id,
|
|
23
|
+
name: model.name,
|
|
24
|
+
}));
|
|
25
|
+
if (proxyServer && proxyPort)
|
|
26
|
+
return proxyPort;
|
|
27
|
+
proxyServer = Bun.serve({
|
|
28
|
+
port: 0,
|
|
29
|
+
idleTimeout: 255, // max — Cursor responses can take 30s+
|
|
30
|
+
async fetch(req) {
|
|
31
|
+
const url = new URL(req.url);
|
|
32
|
+
if (req.method === "GET" && url.pathname === "/v1/models") {
|
|
33
|
+
return new Response(JSON.stringify({
|
|
34
|
+
object: "list",
|
|
35
|
+
data: buildOpenAIModelList(proxyModels),
|
|
36
|
+
}), { headers: { "Content-Type": "application/json" } });
|
|
37
|
+
}
|
|
38
|
+
if (req.method === "POST" && url.pathname === "/v1/chat/completions") {
|
|
39
|
+
try {
|
|
40
|
+
const body = (await req.json());
|
|
41
|
+
if (!proxyAccessTokenProvider) {
|
|
42
|
+
throw new Error("Cursor proxy access token provider not configured");
|
|
43
|
+
}
|
|
44
|
+
const accessToken = await proxyAccessTokenProvider();
|
|
45
|
+
const sessionId = req.headers.get("x-session-id") ?? undefined;
|
|
46
|
+
const agentKey = req.headers.get("x-opencode-agent") ?? undefined;
|
|
47
|
+
const response = await handleChatCompletion(body, accessToken, {
|
|
48
|
+
sessionId,
|
|
49
|
+
agentKey,
|
|
50
|
+
});
|
|
51
|
+
if (response.status >= 400) {
|
|
52
|
+
let responseBody = "";
|
|
53
|
+
try {
|
|
54
|
+
responseBody = await response.clone().text();
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
responseBody = `Failed to read rejected response body: ${error instanceof Error ? error.message : String(error)}`;
|
|
58
|
+
}
|
|
59
|
+
logPluginWarn("Rejected Cursor chat completion", {
|
|
60
|
+
path: url.pathname,
|
|
61
|
+
method: req.method,
|
|
62
|
+
sessionId,
|
|
63
|
+
agentKey,
|
|
64
|
+
status: response.status,
|
|
65
|
+
requestBody: body,
|
|
66
|
+
requestBodyText: JSON.stringify(body),
|
|
67
|
+
responseBody,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return response;
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
74
|
+
logPluginError("Cursor proxy request failed", {
|
|
75
|
+
path: url.pathname,
|
|
76
|
+
method: req.method,
|
|
77
|
+
...errorDetails(err),
|
|
78
|
+
});
|
|
79
|
+
return new Response(JSON.stringify({
|
|
80
|
+
error: { message, type: "server_error", code: "internal_error" },
|
|
81
|
+
}), { status: 500, headers: { "Content-Type": "application/json" } });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return new Response("Not Found", { status: 404 });
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
proxyPort = proxyServer.port;
|
|
88
|
+
if (!proxyPort)
|
|
89
|
+
throw new Error("Failed to bind proxy to a port");
|
|
90
|
+
return proxyPort;
|
|
91
|
+
}
|
|
92
|
+
export function stopProxy() {
|
|
93
|
+
if (proxyServer) {
|
|
94
|
+
proxyServer.stop();
|
|
95
|
+
proxyServer = undefined;
|
|
96
|
+
proxyPort = undefined;
|
|
97
|
+
proxyAccessTokenProvider = undefined;
|
|
98
|
+
proxyModels = [];
|
|
99
|
+
}
|
|
100
|
+
// Clean up any lingering bridges
|
|
101
|
+
for (const active of activeBridges.values()) {
|
|
102
|
+
clearInterval(active.heartbeatTimer);
|
|
103
|
+
active.bridge.end();
|
|
104
|
+
}
|
|
105
|
+
activeBridges.clear();
|
|
106
|
+
conversationStates.clear();
|
|
107
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { conversationStates } from "./conversation-state";
|
|
2
|
+
export function updateConversationCheckpoint(convKey, checkpointBytes) {
|
|
3
|
+
const stored = conversationStates.get(convKey);
|
|
4
|
+
if (!stored)
|
|
5
|
+
return;
|
|
6
|
+
stored.checkpoint = checkpointBytes;
|
|
7
|
+
stored.lastAccessMs = Date.now();
|
|
8
|
+
}
|
|
9
|
+
export function syncStoredBlobStore(convKey, blobStore) {
|
|
10
|
+
const stored = conversationStates.get(convKey);
|
|
11
|
+
if (!stored)
|
|
12
|
+
return;
|
|
13
|
+
for (const [key, value] of blobStore) {
|
|
14
|
+
stored.blobStore.set(key, value);
|
|
15
|
+
}
|
|
16
|
+
stored.lastAccessMs = Date.now();
|
|
17
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type AgentServerMessage, type McpToolDefinition } from "../proto/agent_pb";
|
|
2
|
+
import type { CursorSession } from "../cursor/bidi-session";
|
|
3
|
+
import type { StreamState } from "./stream-state";
|
|
4
|
+
import type { PendingExec } from "./types";
|
|
5
|
+
export interface UnhandledExecInfo {
|
|
6
|
+
execCase: string;
|
|
7
|
+
execId: string;
|
|
8
|
+
execMsgId: number;
|
|
9
|
+
}
|
|
10
|
+
export interface UnsupportedServerMessageInfo {
|
|
11
|
+
category: "agentMessage" | "interactionUpdate" | "interactionQuery" | "execServerControl" | "toolCall";
|
|
12
|
+
caseName: string;
|
|
13
|
+
detail?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function parseConnectEndStream(data: Uint8Array): Error | null;
|
|
16
|
+
export declare function makeHeartbeatBytes(): Uint8Array;
|
|
17
|
+
export declare function scheduleBridgeEnd(bridge: CursorSession): void;
|
|
18
|
+
/**
|
|
19
|
+
* Create a stateful parser for Connect protocol frames.
|
|
20
|
+
* Handles buffering partial data across chunks.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createConnectFrameParser(onMessage: (bytes: Uint8Array) => void, onEndStream: (bytes: Uint8Array) => void): (incoming: Buffer) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Strip thinking tags from streamed text, routing tagged content to reasoning.
|
|
25
|
+
* Buffers partial tags across chunk boundaries.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createThinkingTagFilter(): {
|
|
28
|
+
process(text: string): {
|
|
29
|
+
content: string;
|
|
30
|
+
reasoning: string;
|
|
31
|
+
};
|
|
32
|
+
flush(): {
|
|
33
|
+
content: string;
|
|
34
|
+
reasoning: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export declare function computeUsage(state: StreamState): {
|
|
38
|
+
prompt_tokens: number;
|
|
39
|
+
completion_tokens: number;
|
|
40
|
+
total_tokens: number;
|
|
41
|
+
};
|
|
42
|
+
export declare function processServerMessage(msg: AgentServerMessage, blobStore: Map<string, Uint8Array>, mcpTools: McpToolDefinition[], sendFrame: (data: Uint8Array) => void, state: StreamState, onText: (text: string, isThinking?: boolean) => void, onMcpExec: (exec: PendingExec) => void, onCheckpoint?: (checkpointBytes: Uint8Array) => void, onTurnEnded?: () => void, onUnsupportedMessage?: (info: UnsupportedServerMessageInfo) => void, onUnhandledExec?: (info: UnhandledExecInfo) => void): void;
|