cursor-opencode-provider 0.2.8 → 0.3.0
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 +143 -11
- package/dist/auth.d.ts +7 -0
- package/dist/auth.js +9 -0
- package/dist/compaction-marker.d.ts +14 -0
- package/dist/compaction-marker.js +36 -0
- package/dist/context/agents.d.ts +5 -1
- package/dist/context/agents.js +90 -30
- package/dist/context/build.js +57 -8
- package/dist/context/env.js +6 -2
- package/dist/context/paths.d.ts +10 -0
- package/dist/context/paths.js +24 -0
- package/dist/context/plugins.js +16 -12
- package/dist/context/rules.js +26 -23
- package/dist/context/skills.js +8 -3
- package/dist/index.d.ts +1 -3
- package/dist/index.js +2 -8
- package/dist/language-model.js +121 -35
- package/dist/model-config.d.ts +15 -0
- package/dist/model-config.js +188 -0
- package/dist/opencode2/catalog.d.ts +33 -0
- package/dist/opencode2/catalog.js +85 -0
- package/dist/opencode2/integration.d.ts +23 -0
- package/dist/opencode2/integration.js +124 -0
- package/dist/opencode2/types.d.ts +316 -0
- package/dist/opencode2/types.js +18 -0
- package/dist/plugin-core.d.ts +27 -0
- package/dist/plugin-core.js +31 -0
- package/dist/plugin-opencode2.d.ts +3 -0
- package/dist/plugin-opencode2.js +293 -0
- package/dist/plugin-v2.d.ts +7 -0
- package/dist/plugin-v2.js +1 -15
- package/dist/plugin.d.ts +1 -15
- package/dist/plugin.js +11 -191
- package/dist/protocol/messages.d.ts +10 -0
- package/dist/protocol/messages.js +76 -1
- package/dist/protocol/tool-call-bridge.js +22 -2
- package/dist/protocol/tools.d.ts +57 -1
- package/dist/protocol/tools.js +277 -17
- package/dist/session-directory.d.ts +17 -0
- package/dist/session-directory.js +35 -0
- package/dist/session.d.ts +16 -2
- package/dist/session.js +70 -1
- package/dist/shell-timeout.d.ts +3 -1
- package/dist/shell-timeout.js +6 -2
- package/dist/usage.d.ts +30 -0
- package/dist/usage.js +77 -0
- package/dist/web-search-tool.d.ts +33 -0
- package/dist/web-search-tool.js +22 -0
- package/dist/web-tools.d.ts +9 -25
- package/dist/web-tools.js +34 -36
- package/package.json +7 -2
package/dist/usage.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** Non-negative integer counter from a Cursor `turn_ended` field. */
|
|
2
|
+
export function turnEndedCounter(te, key) {
|
|
3
|
+
const value = te[key];
|
|
4
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
5
|
+
? Math.trunc(value)
|
|
6
|
+
: 0;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Map authoritative Cursor TurnEnded counters to AI SDK V3 usage (Kilo/OpenCode
|
|
10
|
+
* flatten via input total = noCache + cacheRead + cacheWrite).
|
|
11
|
+
*
|
|
12
|
+
* Contract: `input_tokens` is non-cached input; cache fields are separate.
|
|
13
|
+
* Cursor's agent wire currently sends `cache_write=0` even when later turns
|
|
14
|
+
* report large `cache_read`; we still forward the counter when non-zero.
|
|
15
|
+
* If production semantics differ (input_tokens already total), adjust here.
|
|
16
|
+
*/
|
|
17
|
+
export function buildLanguageModelV3UsageFromTurnEnded(te) {
|
|
18
|
+
const input_tokens = turnEndedCounter(te, "input_tokens");
|
|
19
|
+
const output_tokens = turnEndedCounter(te, "output_tokens");
|
|
20
|
+
const cache_read = turnEndedCounter(te, "cache_read");
|
|
21
|
+
const cache_write = turnEndedCounter(te, "cache_write");
|
|
22
|
+
const reasoning_tokens = turnEndedCounter(te, "reasoning_tokens");
|
|
23
|
+
return {
|
|
24
|
+
inputTokens: {
|
|
25
|
+
total: input_tokens + cache_read + cache_write,
|
|
26
|
+
noCache: input_tokens,
|
|
27
|
+
cacheRead: cache_read,
|
|
28
|
+
cacheWrite: cache_write,
|
|
29
|
+
},
|
|
30
|
+
outputTokens: {
|
|
31
|
+
total: output_tokens + reasoning_tokens,
|
|
32
|
+
text: output_tokens,
|
|
33
|
+
reasoning: reasoning_tokens,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Mid-turn finish (e.g. tool-calls) before `turn_ended` — keep non-zero estimates. */
|
|
38
|
+
export function buildLanguageModelV3UsageFromEstimate(est, promptTokens, outputChars, estimateCharsToTokens) {
|
|
39
|
+
const hasInputBreakdown = est.cacheRead > 0 || est.cacheWrite > 0 || est.inputTokens > 0;
|
|
40
|
+
const inputNoCache = hasInputBreakdown ? est.inputTokens : promptTokens;
|
|
41
|
+
const cacheRead = est.cacheRead;
|
|
42
|
+
const cacheWrite = est.cacheWrite;
|
|
43
|
+
const outputFromChars = estimateCharsToTokens(outputChars);
|
|
44
|
+
const outputText = est.outputTokens > 0 ? est.outputTokens : outputFromChars;
|
|
45
|
+
const reasoning = est.reasoningTokens;
|
|
46
|
+
const outputTotal = outputText + reasoning;
|
|
47
|
+
return {
|
|
48
|
+
inputTokens: {
|
|
49
|
+
total: inputNoCache + cacheRead + cacheWrite,
|
|
50
|
+
noCache: inputNoCache,
|
|
51
|
+
cacheRead,
|
|
52
|
+
cacheWrite,
|
|
53
|
+
},
|
|
54
|
+
outputTokens: {
|
|
55
|
+
total: outputTotal,
|
|
56
|
+
text: outputText,
|
|
57
|
+
reasoning: reasoning > 0 ? reasoning : undefined,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Mirror Kilo `Session.getUsage` / AI SDK flat fields from V3 nested usage. */
|
|
62
|
+
export function kiloShapedUsageFromV3(usage) {
|
|
63
|
+
const input = usage.inputTokens;
|
|
64
|
+
const output = usage.outputTokens;
|
|
65
|
+
const noCache = input.noCache ?? 0;
|
|
66
|
+
const cacheRead = input.cacheRead ?? 0;
|
|
67
|
+
const cacheWrite = input.cacheWrite ?? 0;
|
|
68
|
+
const reasoning = output.reasoning ?? 0;
|
|
69
|
+
const text = output.text ?? Math.max(0, (output.total ?? 0) - reasoning);
|
|
70
|
+
return {
|
|
71
|
+
inputTokens: noCache,
|
|
72
|
+
outputTokens: text,
|
|
73
|
+
reasoningTokens: reasoning,
|
|
74
|
+
cacheReadInputTokens: cacheRead,
|
|
75
|
+
cacheWriteInputTokens: cacheWrite,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classic-plugin web search tool registration.
|
|
3
|
+
*
|
|
4
|
+
* Kept apart from `./web-tools.ts` on purpose: this is a *value* import from
|
|
5
|
+
* `@opencode-ai/plugin`, and the OpenCode 2.0 entrypoint shares the search
|
|
6
|
+
* implementation in that module. 2.0's package root exports no `tool`, so
|
|
7
|
+
* importing it from a module on the 2.0 graph would throw during plugin load.
|
|
8
|
+
* Only `plugin.ts` (classic hooks) may import this file.
|
|
9
|
+
*/
|
|
10
|
+
export declare const openCodeWebSearchTool: {
|
|
11
|
+
description: string;
|
|
12
|
+
args: {
|
|
13
|
+
query: import("zod").ZodString;
|
|
14
|
+
numResults: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
15
|
+
livecrawl: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
16
|
+
fallback: "fallback";
|
|
17
|
+
preferred: "preferred";
|
|
18
|
+
}>>;
|
|
19
|
+
type: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
20
|
+
fast: "fast";
|
|
21
|
+
auto: "auto";
|
|
22
|
+
deep: "deep";
|
|
23
|
+
}>>;
|
|
24
|
+
contextMaxCharacters: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
25
|
+
};
|
|
26
|
+
execute(args: {
|
|
27
|
+
query: string;
|
|
28
|
+
numResults?: number | undefined;
|
|
29
|
+
livecrawl?: "fallback" | "preferred" | undefined;
|
|
30
|
+
type?: "fast" | "auto" | "deep" | undefined;
|
|
31
|
+
contextMaxCharacters?: number | undefined;
|
|
32
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
|
|
33
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
import { executeOpenCodeWebSearch } from "./web-tools.js";
|
|
3
|
+
/**
|
|
4
|
+
* Classic-plugin web search tool registration.
|
|
5
|
+
*
|
|
6
|
+
* Kept apart from `./web-tools.ts` on purpose: this is a *value* import from
|
|
7
|
+
* `@opencode-ai/plugin`, and the OpenCode 2.0 entrypoint shares the search
|
|
8
|
+
* implementation in that module. 2.0's package root exports no `tool`, so
|
|
9
|
+
* importing it from a module on the 2.0 graph would throw during plugin load.
|
|
10
|
+
* Only `plugin.ts` (classic hooks) may import this file.
|
|
11
|
+
*/
|
|
12
|
+
export const openCodeWebSearchTool = tool({
|
|
13
|
+
description: "Search the web for current information using OpenCode's web search backend.",
|
|
14
|
+
args: {
|
|
15
|
+
query: tool.schema.string().describe("Web search query"),
|
|
16
|
+
numResults: tool.schema.number().int().min(1).max(20).optional(),
|
|
17
|
+
livecrawl: tool.schema.enum(["fallback", "preferred"]).optional(),
|
|
18
|
+
type: tool.schema.enum(["auto", "fast", "deep"]).optional(),
|
|
19
|
+
contextMaxCharacters: tool.schema.number().int().positive().optional(),
|
|
20
|
+
},
|
|
21
|
+
execute: executeOpenCodeWebSearch,
|
|
22
|
+
});
|
package/dist/web-tools.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ToolContext, ToolResult } from "@opencode-ai/plugin";
|
|
2
2
|
export type OpenCodeWebSearchArgs = {
|
|
3
3
|
query: string;
|
|
4
4
|
numResults?: number;
|
|
@@ -7,28 +7,12 @@ export type OpenCodeWebSearchArgs = {
|
|
|
7
7
|
contextMaxCharacters?: number;
|
|
8
8
|
};
|
|
9
9
|
export declare function parseOpenCodeWebSearchResponse(raw: string): string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Raw Exa web-search call: no host tool context, no permission prompt.
|
|
12
|
+
*
|
|
13
|
+
* Split out so both the classic plugin's `custom_websearch` tool and the
|
|
14
|
+
* OpenCode 2.0 plugin's tool registration share one implementation — 2.0's
|
|
15
|
+
* ToolContext has no `ask`, permissions being handled by the host instead.
|
|
16
|
+
*/
|
|
17
|
+
export declare function fetchOpenCodeWebSearchText(args: OpenCodeWebSearchArgs, signal: AbortSignal | undefined, fetchImpl?: typeof fetch): Promise<string>;
|
|
10
18
|
export declare function executeOpenCodeWebSearch(args: OpenCodeWebSearchArgs, context: ToolContext, fetchImpl?: typeof fetch): Promise<ToolResult>;
|
|
11
|
-
export declare const openCodeWebSearchTool: {
|
|
12
|
-
description: string;
|
|
13
|
-
args: {
|
|
14
|
-
query: import("zod").ZodString;
|
|
15
|
-
numResults: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
16
|
-
livecrawl: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
17
|
-
fallback: "fallback";
|
|
18
|
-
preferred: "preferred";
|
|
19
|
-
}>>;
|
|
20
|
-
type: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
21
|
-
fast: "fast";
|
|
22
|
-
auto: "auto";
|
|
23
|
-
deep: "deep";
|
|
24
|
-
}>>;
|
|
25
|
-
contextMaxCharacters: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
26
|
-
};
|
|
27
|
-
execute(args: {
|
|
28
|
-
query: string;
|
|
29
|
-
numResults?: number | undefined;
|
|
30
|
-
livecrawl?: "fallback" | "preferred" | undefined;
|
|
31
|
-
type?: "fast" | "auto" | "deep" | undefined;
|
|
32
|
-
contextMaxCharacters?: number | undefined;
|
|
33
|
-
}, context: ToolContext): Promise<ToolResult>;
|
|
34
|
-
};
|
package/dist/web-tools.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin";
|
|
2
1
|
const EXA_MCP_URL = "https://mcp.exa.ai/mcp";
|
|
3
2
|
const WEB_SEARCH_TIMEOUT_MS = 25_000;
|
|
4
3
|
function exaMcpUrl() {
|
|
@@ -54,26 +53,20 @@ export function parseOpenCodeWebSearchResponse(raw) {
|
|
|
54
53
|
}
|
|
55
54
|
return undefined;
|
|
56
55
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
livecrawl: args.livecrawl,
|
|
66
|
-
type: args.type,
|
|
67
|
-
contextMaxCharacters: args.contextMaxCharacters,
|
|
68
|
-
provider: "exa",
|
|
69
|
-
},
|
|
70
|
-
});
|
|
56
|
+
/**
|
|
57
|
+
* Raw Exa web-search call: no host tool context, no permission prompt.
|
|
58
|
+
*
|
|
59
|
+
* Split out so both the classic plugin's `custom_websearch` tool and the
|
|
60
|
+
* OpenCode 2.0 plugin's tool registration share one implementation — 2.0's
|
|
61
|
+
* ToolContext has no `ask`, permissions being handled by the host instead.
|
|
62
|
+
*/
|
|
63
|
+
export async function fetchOpenCodeWebSearchText(args, signal, fetchImpl = fetch) {
|
|
71
64
|
const controller = new AbortController();
|
|
72
|
-
const abort = () => controller.abort(
|
|
73
|
-
if (
|
|
65
|
+
const abort = () => controller.abort(signal?.reason);
|
|
66
|
+
if (signal?.aborted)
|
|
74
67
|
abort();
|
|
75
68
|
else
|
|
76
|
-
|
|
69
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
77
70
|
const timeout = setTimeout(() => controller.abort(new Error("Web search timed out")), WEB_SEARCH_TIMEOUT_MS);
|
|
78
71
|
try {
|
|
79
72
|
const response = await fetchImpl(exaMcpUrl(), {
|
|
@@ -102,26 +95,31 @@ export async function executeOpenCodeWebSearch(args, context, fetchImpl = fetch)
|
|
|
102
95
|
const raw = await response.text();
|
|
103
96
|
if (!response.ok)
|
|
104
97
|
throw new Error(`Web search failed (${response.status}): ${raw.slice(0, 500)}`);
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
title: `Exa Web Search: ${args.query}`,
|
|
108
|
-
output,
|
|
109
|
-
metadata: { provider: "exa" },
|
|
110
|
-
};
|
|
98
|
+
return parseOpenCodeWebSearchResponse(raw) ?? "No search results found. Please try a different query.";
|
|
111
99
|
}
|
|
112
100
|
finally {
|
|
113
101
|
clearTimeout(timeout);
|
|
114
|
-
|
|
102
|
+
signal?.removeEventListener("abort", abort);
|
|
115
103
|
}
|
|
116
104
|
}
|
|
117
|
-
export
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
105
|
+
export async function executeOpenCodeWebSearch(args, context, fetchImpl = fetch) {
|
|
106
|
+
await context.ask({
|
|
107
|
+
permission: "websearch",
|
|
108
|
+
patterns: [args.query],
|
|
109
|
+
always: ["*"],
|
|
110
|
+
metadata: {
|
|
111
|
+
query: args.query,
|
|
112
|
+
numResults: args.numResults,
|
|
113
|
+
livecrawl: args.livecrawl,
|
|
114
|
+
type: args.type,
|
|
115
|
+
contextMaxCharacters: args.contextMaxCharacters,
|
|
116
|
+
provider: "exa",
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
const output = await fetchOpenCodeWebSearchText(args, context.abort, fetchImpl);
|
|
120
|
+
return {
|
|
121
|
+
title: `Exa Web Search: ${args.query}`,
|
|
122
|
+
output,
|
|
123
|
+
metadata: { provider: "exa" },
|
|
124
|
+
};
|
|
125
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursor-opencode-provider",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Use Cursor subscription models from OpenCode via Cursor's Connect-RPC agent protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,6 +36,10 @@
|
|
|
36
36
|
"types": "./dist/plugin-v2.d.ts",
|
|
37
37
|
"import": "./dist/plugin-v2.js"
|
|
38
38
|
},
|
|
39
|
+
"./plugin/opencode2": {
|
|
40
|
+
"types": "./dist/plugin-opencode2.d.ts",
|
|
41
|
+
"import": "./dist/plugin-opencode2.js"
|
|
42
|
+
},
|
|
39
43
|
"./errors": {
|
|
40
44
|
"types": "./dist/errors.d.ts",
|
|
41
45
|
"import": "./dist/errors.js"
|
|
@@ -50,7 +54,7 @@
|
|
|
50
54
|
],
|
|
51
55
|
"scripts": {
|
|
52
56
|
"build": "tsc",
|
|
53
|
-
"typecheck": "tsc --noEmit",
|
|
57
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
|
|
54
58
|
"test": "bun test",
|
|
55
59
|
"test:node-http2": "bun run build && node test/node-http2-cleanup.mjs",
|
|
56
60
|
"test:watch": "bun test --watch",
|
|
@@ -71,6 +75,7 @@
|
|
|
71
75
|
},
|
|
72
76
|
"devDependencies": {
|
|
73
77
|
"@opencode-ai/plugin": "^1.17.13",
|
|
78
|
+
"@opencode-ai/plugin-next": "^0.0.0-next-16281",
|
|
74
79
|
"@tsconfig/node22": "^22.0.1",
|
|
75
80
|
"@types/node": "^22.15.3",
|
|
76
81
|
"typescript": "^5.8.3"
|