cursor-opencode-provider 0.3.0 → 0.3.2
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 +13 -3
- package/dist/agent-url.js +2 -0
- package/dist/context/paths.js +3 -2
- package/dist/language-model.js +14 -2
- package/dist/model-config.js +25 -5
- package/dist/opencode2/catalog.js +2 -1
- package/dist/opencode2/types.d.ts +13 -1
- package/dist/pricing-data.d.ts +194 -0
- package/dist/pricing-data.js +194 -0
- package/dist/pricing.d.ts +62 -0
- package/dist/pricing.js +127 -0
- package/dist/protocol/apply-patch.d.ts +109 -0
- package/dist/protocol/apply-patch.js +163 -0
- package/dist/protocol/messages.js +27 -1
- package/dist/protocol/tools.d.ts +16 -0
- package/dist/protocol/tools.js +253 -15
- package/dist/shell-timeout.js +6 -2
- package/package.json +12 -9
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ Add the package to OpenCode config. OpenCode installs npm plugins with Bun at st
|
|
|
47
47
|
}
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
Pin a version if you want: `"cursor-opencode-provider@0.2
|
|
50
|
+
Pin a version if you want: `"cursor-opencode-provider@0.3.2"`.
|
|
51
51
|
|
|
52
52
|
You can also install it yourself first:
|
|
53
53
|
|
|
@@ -117,7 +117,7 @@ browser login or an API key. `CURSOR_API_KEY` is also picked up automatically.
|
|
|
117
117
|
}
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
Pin a version if you want: `"cursor-opencode-provider@0.2
|
|
120
|
+
Pin a version if you want: `"cursor-opencode-provider@0.3.2/plugin/opencode2"`.
|
|
121
121
|
|
|
122
122
|
OpenCode 2.0 installs the published package into its host cache and loads the AI SDK
|
|
123
123
|
entry from there (`aisdk:cursor-opencode-provider`). No extra env vars are required.
|
|
@@ -337,6 +337,7 @@ OpenCode
|
|
|
337
337
|
| `src/opencode2/` | 2.0-only catalog mapping, integration/auth, and local API types |
|
|
338
338
|
| `src/plugin-core.ts` | Host-neutral SDK factory, package matching, API base/telemetry resolution |
|
|
339
339
|
| `src/model-config.ts` | Cursor model → OpenCode model mapping shared by every plugin surface |
|
|
340
|
+
| `src/pricing.ts` / `src/pricing-data.ts` | Cursor docs token rates → classic `cost` and OpenCode 2.0 cost tiers |
|
|
340
341
|
| `src/index.ts` | `createCursor` factory; default export is `CursorPlugin` |
|
|
341
342
|
| `src/language-model.ts` | AI SDK `LanguageModelV3` adapter (`doStream`, `doGenerate`) |
|
|
342
343
|
| `src/session.ts` | Held-open agent Run session and pending exec correlation |
|
|
@@ -350,10 +351,18 @@ OpenCode
|
|
|
350
351
|
|
|
351
352
|
### Injected system guidance
|
|
352
353
|
|
|
353
|
-
The provider adds OpenCode-specific system guidance to normal tool-capable conversations, including tool availability, canonical workspace-path grounding, and preferring `edit` / `write` over shell-based file mutation when those tools are available. Compaction keeps its dedicated prompt unchanged.
|
|
354
|
+
The provider adds OpenCode-specific system guidance to normal tool-capable conversations, including tool availability, canonical workspace-path grounding, and preferring `edit` / `write` (or `apply_patch`, when the host advertises that instead) over shell-based file mutation when those tools are available. Compaction keeps its dedicated prompt unchanged.
|
|
354
355
|
|
|
355
356
|
If this guidance causes issues, update `buildOpenCodeInteractionGuidance` in [`src/language-model.ts`](src/language-model.ts) and its focused coverage in [`test/prompt-history.test.ts`](test/prompt-history.test.ts).
|
|
356
357
|
|
|
358
|
+
### `apply_patch` models (GPT-5 and friends)
|
|
359
|
+
|
|
360
|
+
OpenCode 1.x does not simply add `apply_patch` for GPT-series models — it **removes** `edit` and `write` from the tool catalog and advertises `apply_patch` in their place (`ToolRegistry.tools`; the model id must contain `gpt-` and neither `oss` nor `gpt-4`). Cursor keeps sending its native write/edit requests regardless, so without translation every file change on a `gpt-5*` model is refused as an unavailable tool.
|
|
361
|
+
|
|
362
|
+
The provider translates transparently: a native write becomes an `*** Add File:` patch, and a Pi edit becomes a minimal `*** Update File:` chunk widened to whole lines. This is keyed purely off what the host advertises — it is inert whenever `edit`/`write` are offered normally, and inert again when the host offers neither those nor `apply_patch`. An edit that cannot be expressed faithfully (target unreadable, or the text to replace is absent or ambiguous) is refused with a specific message rather than applied to the wrong region.
|
|
363
|
+
|
|
364
|
+
OpenCode 2.0 does not perform this substitution, so the path is 1.x-only in practice. See [`src/protocol/apply-patch.ts`](src/protocol/apply-patch.ts) for the upstream references.
|
|
365
|
+
|
|
357
366
|
### Native subagent routing
|
|
358
367
|
|
|
359
368
|
The provider reads the permission-filtered subagent catalog from the current host `task` or `actor` definition and advertises those recipients to Cursor as custom subagents. Exact configured names are preserved; unknown future Cursor subtype strings fall back to `general`. If a complete catalog omits every compatible recipient, the request fails on Cursor's typed subagent channel instead of selecting an arbitrary specialist.
|
|
@@ -412,6 +421,7 @@ Project `instructions` may reference absolute or `~/` paths (OpenCode parity). S
|
|
|
412
421
|
- **Compaction resets Cursor conversation state** — the classic plugin marks OpenCode's `compaction` agent explicitly. On those turns the provider mints an isolated Cursor `conversation_id`, drops the prior checkpoint + KV blobs, preserves real tool outputs as OpenCode-host observations in the seed history, and re-advertises the session's last tool catalog while refusing execution during the summary itself. The first normal turn then rebases once more onto a fresh conversation seeded with OpenCode's compacted prompt and normal system instructions, so the summary-agent checkpoint cannot suppress later tool calls. Ordinary no-tool / `toolChoice:none` calls do not reset conversation state.
|
|
413
422
|
- **Conversation bindings and compaction catalogs are bounded** — process-global per-session bindings, prior tool catalogs, and pending post-compaction rebases use a 256-session LRU bound. Evicting a conversation binding also drops its checkpoint and KV blobs.
|
|
414
423
|
- **Interrupted Runs resume from checkpoints** — a remote EOF, Connect end-stream, or trailer error is never emitted as a successful `stop`. When the failed Run produced an eligible checkpoint, the provider opens a new RPC for the same conversation and sends that state with `ResumeAction`, so completed text and tool work are not replayed. Before any stateful output, an interruption without a checkpoint can still rebase from OpenCode history. Stateful interruptions without a checkpoint are surfaced because replay would be ambiguous; retry exhaustion remains explicit. A transport closure after `turn_ended` is treated as successful completion.
|
|
424
|
+
- **Large reads are capped at 50 KB by OpenCode, and the cap is reported** — OpenCode's `read` tool stops at `MAX_BYTES = 50 * 1024`, cutting on a whole-line boundary, and appends `(Output capped at 50 KB. Showing lines X-Y. Use offset=N to continue.)`. The provider strips that envelope before returning content to Cursor so the model cannot echo wrappers into a later write, but it re-states the cap: a capped native read appends a `[Partial read: …]` marker after the content, a capped MCP read gets a separate notice content item, and a Pi read carries structured `PiReadExecSuccess.truncation`. The structured `truncated` flag alone is not enough — verified against live `gpt-5.4-mini` and `grok-4.5` sessions, both of which asserted "highly confident" that partial content was the whole file until the textual marker was added. Deliberately paged reads (explicit `offset`/`limit`) are not marked. Without that signal the model treats a capped read as the whole file and rewrites it truncated, discarding everything past the cap. The cap is hardcoded in OpenCode's read tool — it is not the configurable `tool_output.max_bytes`, which governs `Truncate.Service` and not read's line accumulation — so the only lever is paging with `offset`. Prefer `edit` over whole-file `write` on files above 50 KB.
|
|
415
425
|
- **No fallback models** — if Cursor’s `AvailableModels` API is unreachable and there is no local cache, the provider exposes no models.
|
|
416
426
|
|
|
417
427
|
## License
|
package/dist/agent-url.js
CHANGED
|
@@ -21,6 +21,8 @@ function normalizeApiBaseURL(baseURL) {
|
|
|
21
21
|
return new URL(baseURL).origin;
|
|
22
22
|
}
|
|
23
23
|
function resolveCacheKey(token, options) {
|
|
24
|
+
// codeql[js/insufficient-password-hash] -- The token is a high-entropy JWT/API key,
|
|
25
|
+
// and this digest is only an ephemeral memoization key, never a password verifier.
|
|
24
26
|
const tokenHash = createHash("sha256").update(token).digest("hex").slice(0, 16);
|
|
25
27
|
return `${tokenHash}|${normalizeApiBaseURL(options.apiBaseURL ?? options.baseURL)}|telem:${options.telemetryEnabled === true}`;
|
|
26
28
|
}
|
package/dist/context/paths.js
CHANGED
|
@@ -145,8 +145,9 @@ export function slugifyWorkspacePath(workspaceRoot) {
|
|
|
145
145
|
const resolved = path.resolve(workspaceRoot);
|
|
146
146
|
return resolved
|
|
147
147
|
.replace(/[^a-zA-Z0-9]/g, "-")
|
|
148
|
-
.
|
|
149
|
-
.
|
|
148
|
+
.split("-")
|
|
149
|
+
.filter(Boolean)
|
|
150
|
+
.join("-");
|
|
150
151
|
}
|
|
151
152
|
/**
|
|
152
153
|
* Cursor-style project metadata root for a workspace.
|
package/dist/language-model.js
CHANGED
|
@@ -6,7 +6,7 @@ import { trace, traceRequestContextPaths } from "./debug.js";
|
|
|
6
6
|
import { buildRunRequest, buildHeartbeat } from "./protocol/request.js";
|
|
7
7
|
import { decodeFramePayload } from "./protocol/framing.js";
|
|
8
8
|
import { debugWalkTurnEnded, decodeMessage } from "./protocol/messages.js";
|
|
9
|
-
import { parseExecServerMessage, buildToolCallPart, buildExecClientMessages, buildReadRejectionMessages, classifyMissingReadTarget, resolveReadTargetPath, parseExecIdFromToolCallId, detectExecVariantField, buildRequestContextResult, buildMcpStateResult, buildCustomWebToolAliases, extractHostSubagentCatalog, resolveCustomWebToolAlias, remapNativeSubagentForCatalog, CUSTOM_WEBFETCH_TOOL, CUSTOM_WEBSEARCH_TOOL, } from "./protocol/tools.js";
|
|
9
|
+
import { parseExecServerMessage, buildToolCallPart, buildExecClientMessages, buildReadRejectionMessages, classifyMissingReadTarget, resolveReadTargetPath, parseExecIdFromToolCallId, detectExecVariantField, buildRequestContextResult, buildMcpStateResult, buildCustomWebToolAliases, extractHostSubagentCatalog, resolveCustomWebToolAlias, remapNativeSubagentForCatalog, remapEditToolsForCatalog, CUSTOM_WEBFETCH_TOOL, CUSTOM_WEBSEARCH_TOOL, } from "./protocol/tools.js";
|
|
10
10
|
import { describeCursorExecVariant } from "./protocol/exec-variants.js";
|
|
11
11
|
import { advertisedToolNamesFromDescriptors, extractExecDisplayCallId, extractProtobufSubmessage, listProtobufFieldNumbers, parseDisplayToolCall, resolveBridgedOpenCodeToolCall, } from "./protocol/tool-call-bridge.js";
|
|
12
12
|
import { handleKvServerMessage } from "./protocol/kv.js";
|
|
@@ -954,7 +954,9 @@ export async function pump(session, controller, ids, abortSignal) {
|
|
|
954
954
|
if (!displayCallId ||
|
|
955
955
|
parsed.resultField !== "read_result" ||
|
|
956
956
|
parsed.toolName !== "read" ||
|
|
957
|
-
|
|
957
|
+
// `apply_patch` is the substitute this host offers when it withholds
|
|
958
|
+
// `write` — the follow-up write_args is remapped onto it either way.
|
|
959
|
+
!(advertisedToolNameSet.has("write") || advertisedToolNameSet.has("apply_patch")))
|
|
958
960
|
return false;
|
|
959
961
|
const stored = session.displayToolCalls.get(displayCallId);
|
|
960
962
|
const display = parseDisplayToolCall(displayCallId, stored);
|
|
@@ -1377,6 +1379,10 @@ export async function pump(session, controller, ids, abortSignal) {
|
|
|
1377
1379
|
parsed.toolName = executableToolName;
|
|
1378
1380
|
}
|
|
1379
1381
|
remapNativeSubagentForCatalog(parsed, advertisedToolNameSet, session.subagentCatalog);
|
|
1382
|
+
// Hosts that advertise `apply_patch` in place of `edit`/`write` (see
|
|
1383
|
+
// protocol/apply-patch.ts) still receive Cursor's native write/edit
|
|
1384
|
+
// exec requests. Translate before the unavailable-tool check below.
|
|
1385
|
+
remapEditToolsForCatalog(parsed, advertisedToolNameSet, workspaceRootFromRequestContext(session.requestContext));
|
|
1380
1386
|
}
|
|
1381
1387
|
const displayCallId = extractExecDisplayCallId(esm);
|
|
1382
1388
|
trace(`exec: id=${parsed?.id} variant=${parsed ? Object.keys(parsed).join(",") : "none"} toolName=${parsed?.toolName} resultField=${parsed?.resultField}`);
|
|
@@ -1663,6 +1669,12 @@ export function buildOpenCodeInteractionGuidance(tools, isCompaction, workspaceR
|
|
|
1663
1669
|
? "- For file changes, use OpenCode `edit` for targeted changes to existing files and `write` to create files or intentionally replace complete contents; do not use shell, Python, or heredocs to change file content while these tools are available."
|
|
1664
1670
|
: "- Use OpenCode `write` for file-content changes; do not use shell, Python, or heredocs to change file content while it is available.");
|
|
1665
1671
|
}
|
|
1672
|
+
else if (names.has("apply_patch")) {
|
|
1673
|
+
// This host withheld `edit`/`write` and offers `apply_patch` instead. Native
|
|
1674
|
+
// Cursor write/edit requests are translated into it, so say so rather than
|
|
1675
|
+
// leaving the turn with no file-editing guidance at all.
|
|
1676
|
+
instructions.push("- Use OpenCode `apply_patch` for file-content changes; do not use shell, Python, or heredocs to change file content while it is available. Cursor-native write and edit requests are accepted and converted to `apply_patch` automatically.");
|
|
1677
|
+
}
|
|
1666
1678
|
return [
|
|
1667
1679
|
`OpenCode exposes exactly these executable tools for this turn: ${[...names].map((name) => `\`${name}\``).join(", ")}.`,
|
|
1668
1680
|
`Workspace root: ${JSON.stringify(workspaceRoot)}. Resolve workspace paths against exactly this root; never invent an absolute prefix, and verify uncertain paths with an available tool before using them.`,
|
package/dist/model-config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CURSOR_VARIANT_PARAMETERS_KEY, CURSOR_WIRE_MODEL_ID_KEY, parseCursorContextLimit, } from "./models.js";
|
|
2
|
+
import { applyCursorModelCost } from "./pricing.js";
|
|
2
3
|
/**
|
|
3
4
|
* Cursor `ModelInfo` → OpenCode model-config mapping.
|
|
4
5
|
*
|
|
@@ -19,9 +20,27 @@ import { CURSOR_VARIANT_PARAMETERS_KEY, CURSOR_WIRE_MODEL_ID_KEY, parseCursorCon
|
|
|
19
20
|
* Dots and unicode letters are preserved so names stay readable.
|
|
20
21
|
* Fixes https://github.com/oakimov/cursor-opencode-provider/issues/2.
|
|
21
22
|
*/
|
|
23
|
+
function stripMarkupTags(value) {
|
|
24
|
+
const chunks = [];
|
|
25
|
+
let cursor = 0;
|
|
26
|
+
while (cursor < value.length) {
|
|
27
|
+
const start = value.indexOf("<", cursor);
|
|
28
|
+
if (start === -1) {
|
|
29
|
+
chunks.push(value.slice(cursor));
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
chunks.push(value.slice(cursor, start));
|
|
33
|
+
const end = value.indexOf(">", start + 1);
|
|
34
|
+
if (end === -1) {
|
|
35
|
+
chunks.push(value.slice(start));
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
cursor = end + 1;
|
|
39
|
+
}
|
|
40
|
+
return chunks.join("");
|
|
41
|
+
}
|
|
22
42
|
function safeLabel(value) {
|
|
23
|
-
return (value
|
|
24
|
-
.replace(/<[^>]+>/g, "")
|
|
43
|
+
return (stripMarkupTags(value)
|
|
25
44
|
.replace(/[()<>&"'`]/g, "")
|
|
26
45
|
.replace(/\s+/g, " ")
|
|
27
46
|
.trim() || "default");
|
|
@@ -169,12 +188,13 @@ export function modelsToConfig(models) {
|
|
|
169
188
|
const baseVariants = variantsForTier(m, "base");
|
|
170
189
|
const longVariants = variantsForTier(m, "long");
|
|
171
190
|
if (baseVariants.length > 0 || longVariants.length === 0) {
|
|
172
|
-
out[m.id] = modelInfoToConfig(m, { thinkingSuffix, contextTier: "base" });
|
|
191
|
+
out[m.id] = applyCursorModelCost(m.id, modelInfoToConfig(m, { thinkingSuffix, contextTier: "base" }));
|
|
173
192
|
}
|
|
174
193
|
if (longVariants.length === 0)
|
|
175
194
|
continue;
|
|
176
195
|
if (baseVariants.length === 0) {
|
|
177
|
-
|
|
196
|
+
// No separate base tier — the wire id itself is the long-context entry.
|
|
197
|
+
out[m.id] = applyCursorModelCost(`${m.id}-1m`, modelInfoToConfig(m, { thinkingSuffix, contextTier: "long" }));
|
|
178
198
|
continue;
|
|
179
199
|
}
|
|
180
200
|
let longId = `${m.id}-1m`;
|
|
@@ -182,7 +202,7 @@ export function modelsToConfig(models) {
|
|
|
182
202
|
while (usedIds.has(longId))
|
|
183
203
|
longId = `${m.id}-1m-${suffix++}`;
|
|
184
204
|
usedIds.add(longId);
|
|
185
|
-
out[longId] = modelInfoToConfig(m, { thinkingSuffix, contextTier: "long" });
|
|
205
|
+
out[longId] = applyCursorModelCost(longId, modelInfoToConfig(m, { thinkingSuffix, contextTier: "long" }));
|
|
186
206
|
}
|
|
187
207
|
return out;
|
|
188
208
|
}
|
|
@@ -2,6 +2,7 @@ import { pathToFileURL } from "node:url";
|
|
|
2
2
|
import { CURSOR_PROVIDER_ID } from "../shared.js";
|
|
3
3
|
import { CURSOR_WIRE_MODEL_ID_KEY } from "../models.js";
|
|
4
4
|
import { modelsToConfig } from "../model-config.js";
|
|
5
|
+
import { toOpenCode2Costs } from "../pricing.js";
|
|
5
6
|
/**
|
|
6
7
|
* Catalog registration for the OpenCode 2.0 plugin — the replacement for the
|
|
7
8
|
* classic plugin's `config` hook.
|
|
@@ -69,7 +70,7 @@ function applyModelEntry(draft, id, entry) {
|
|
|
69
70
|
model.status = "active";
|
|
70
71
|
model.enabled = true;
|
|
71
72
|
model.time = { released: 0 };
|
|
72
|
-
model.cost =
|
|
73
|
+
model.cost = toOpenCode2Costs(entry.cost);
|
|
73
74
|
// Carries the default variant parameters (and the wire id) through to
|
|
74
75
|
// doStream as provider options, matching the classic plugin.
|
|
75
76
|
if (options)
|
|
@@ -53,7 +53,19 @@ export type ModelInfo2 = {
|
|
|
53
53
|
time: {
|
|
54
54
|
released: number;
|
|
55
55
|
};
|
|
56
|
-
|
|
56
|
+
/** OpenCode 2.0 tiered $/1M rates; empty when Cursor does not publish a price. */
|
|
57
|
+
cost: readonly {
|
|
58
|
+
tier?: {
|
|
59
|
+
type: "context";
|
|
60
|
+
size: number;
|
|
61
|
+
};
|
|
62
|
+
input: number;
|
|
63
|
+
output: number;
|
|
64
|
+
cache: {
|
|
65
|
+
read: number;
|
|
66
|
+
write: number;
|
|
67
|
+
};
|
|
68
|
+
}[];
|
|
57
69
|
status: "alpha" | "beta" | "deprecated" | "active";
|
|
58
70
|
enabled: boolean;
|
|
59
71
|
limit: {
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTO-GENERATED by scripts/generate-cursor-pricing.ts — do not edit by hand.
|
|
3
|
+
* Source: https://cursor.com/docs/models-and-pricing.md
|
|
4
|
+
* Regenerate: bun run generate:pricing
|
|
5
|
+
*/
|
|
6
|
+
export declare const CURSOR_PRICING_SOURCE: "https://cursor.com/docs/models-and-pricing.md";
|
|
7
|
+
export declare const CURSOR_MODEL_COSTS: {
|
|
8
|
+
readonly "claude-fable-5": {
|
|
9
|
+
readonly input: 10;
|
|
10
|
+
readonly output: 50;
|
|
11
|
+
readonly cache_read: 1;
|
|
12
|
+
readonly cache_write: 12.5;
|
|
13
|
+
};
|
|
14
|
+
readonly "claude-haiku-4-5": {
|
|
15
|
+
readonly input: 1;
|
|
16
|
+
readonly output: 5;
|
|
17
|
+
readonly cache_read: 0.1;
|
|
18
|
+
readonly cache_write: 1.25;
|
|
19
|
+
};
|
|
20
|
+
readonly "claude-opus-4-5": {
|
|
21
|
+
readonly input: 5;
|
|
22
|
+
readonly output: 25;
|
|
23
|
+
readonly cache_read: 0.5;
|
|
24
|
+
readonly cache_write: 6.25;
|
|
25
|
+
};
|
|
26
|
+
readonly "claude-opus-4-6": {
|
|
27
|
+
readonly input: 5;
|
|
28
|
+
readonly output: 25;
|
|
29
|
+
readonly cache_read: 0.5;
|
|
30
|
+
readonly cache_write: 6.25;
|
|
31
|
+
};
|
|
32
|
+
readonly "claude-opus-4-7": {
|
|
33
|
+
readonly input: 5;
|
|
34
|
+
readonly output: 25;
|
|
35
|
+
readonly cache_read: 0.5;
|
|
36
|
+
readonly cache_write: 6.25;
|
|
37
|
+
};
|
|
38
|
+
readonly "claude-opus-4-8": {
|
|
39
|
+
readonly input: 5;
|
|
40
|
+
readonly output: 25;
|
|
41
|
+
readonly cache_read: 0.5;
|
|
42
|
+
readonly cache_write: 6.25;
|
|
43
|
+
};
|
|
44
|
+
readonly "claude-opus-5": {
|
|
45
|
+
readonly input: 5;
|
|
46
|
+
readonly output: 25;
|
|
47
|
+
readonly cache_read: 0.5;
|
|
48
|
+
readonly cache_write: 6.25;
|
|
49
|
+
};
|
|
50
|
+
readonly "claude-sonnet-4": {
|
|
51
|
+
readonly input: 3;
|
|
52
|
+
readonly output: 15;
|
|
53
|
+
readonly cache_read: 0.3;
|
|
54
|
+
readonly cache_write: 3.75;
|
|
55
|
+
readonly context_over_200k: {
|
|
56
|
+
readonly input: 6;
|
|
57
|
+
readonly output: 22.5;
|
|
58
|
+
readonly cache_read: 0.6;
|
|
59
|
+
readonly cache_write: 7.5;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
readonly "claude-sonnet-4-5": {
|
|
63
|
+
readonly input: 3;
|
|
64
|
+
readonly output: 15;
|
|
65
|
+
readonly cache_read: 0.3;
|
|
66
|
+
readonly cache_write: 3.75;
|
|
67
|
+
};
|
|
68
|
+
readonly "claude-sonnet-4-6": {
|
|
69
|
+
readonly input: 3;
|
|
70
|
+
readonly output: 15;
|
|
71
|
+
readonly cache_read: 0.3;
|
|
72
|
+
readonly cache_write: 3.75;
|
|
73
|
+
};
|
|
74
|
+
readonly "claude-sonnet-5": {
|
|
75
|
+
readonly input: 3;
|
|
76
|
+
readonly output: 15;
|
|
77
|
+
readonly cache_read: 0.3;
|
|
78
|
+
readonly cache_write: 3.75;
|
|
79
|
+
};
|
|
80
|
+
readonly "gemini-2.5-flash": {
|
|
81
|
+
readonly input: 0.3;
|
|
82
|
+
readonly output: 2.5;
|
|
83
|
+
readonly cache_read: 0.03;
|
|
84
|
+
};
|
|
85
|
+
readonly "gemini-3-flash": {
|
|
86
|
+
readonly input: 0.5;
|
|
87
|
+
readonly output: 3;
|
|
88
|
+
readonly cache_read: 0.05;
|
|
89
|
+
};
|
|
90
|
+
readonly "gemini-3.1-pro": {
|
|
91
|
+
readonly input: 2;
|
|
92
|
+
readonly output: 12;
|
|
93
|
+
readonly cache_read: 0.2;
|
|
94
|
+
};
|
|
95
|
+
readonly "gemini-3.5-flash": {
|
|
96
|
+
readonly input: 1.5;
|
|
97
|
+
readonly output: 9;
|
|
98
|
+
readonly cache_read: 0.15;
|
|
99
|
+
};
|
|
100
|
+
readonly "gemini-3.6-flash": {
|
|
101
|
+
readonly input: 1.5;
|
|
102
|
+
readonly output: 7.5;
|
|
103
|
+
readonly cache_read: 0.15;
|
|
104
|
+
};
|
|
105
|
+
readonly "glm-5.2": {
|
|
106
|
+
readonly input: 1.4;
|
|
107
|
+
readonly output: 4.4;
|
|
108
|
+
readonly cache_read: 0.26;
|
|
109
|
+
};
|
|
110
|
+
readonly "gpt-5-mini": {
|
|
111
|
+
readonly input: 0.25;
|
|
112
|
+
readonly output: 2;
|
|
113
|
+
readonly cache_read: 0.025;
|
|
114
|
+
};
|
|
115
|
+
readonly "gpt-5.1": {
|
|
116
|
+
readonly input: 1.25;
|
|
117
|
+
readonly output: 10;
|
|
118
|
+
readonly cache_read: 0.125;
|
|
119
|
+
};
|
|
120
|
+
readonly "gpt-5.2": {
|
|
121
|
+
readonly input: 1.75;
|
|
122
|
+
readonly output: 14;
|
|
123
|
+
readonly cache_read: 0.175;
|
|
124
|
+
};
|
|
125
|
+
readonly "gpt-5.3-codex": {
|
|
126
|
+
readonly input: 1.75;
|
|
127
|
+
readonly output: 14;
|
|
128
|
+
readonly cache_read: 0.175;
|
|
129
|
+
};
|
|
130
|
+
readonly "gpt-5.4": {
|
|
131
|
+
readonly input: 2.5;
|
|
132
|
+
readonly output: 15;
|
|
133
|
+
readonly cache_read: 0.25;
|
|
134
|
+
readonly context_over_200k: {
|
|
135
|
+
readonly input: 5;
|
|
136
|
+
readonly output: 15;
|
|
137
|
+
readonly cache_read: 0.5;
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
readonly "gpt-5.4-mini": {
|
|
141
|
+
readonly input: 0.75;
|
|
142
|
+
readonly output: 4.5;
|
|
143
|
+
readonly cache_read: 0.075;
|
|
144
|
+
};
|
|
145
|
+
readonly "gpt-5.4-nano": {
|
|
146
|
+
readonly input: 0.2;
|
|
147
|
+
readonly output: 1.25;
|
|
148
|
+
readonly cache_read: 0.02;
|
|
149
|
+
};
|
|
150
|
+
readonly "gpt-5.5": {
|
|
151
|
+
readonly input: 5;
|
|
152
|
+
readonly output: 30;
|
|
153
|
+
readonly cache_read: 0.5;
|
|
154
|
+
readonly context_over_200k: {
|
|
155
|
+
readonly input: 10;
|
|
156
|
+
readonly output: 30;
|
|
157
|
+
readonly cache_read: 1;
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
readonly "gpt-5.6-luna": {
|
|
161
|
+
readonly input: 0.2;
|
|
162
|
+
readonly output: 1.2;
|
|
163
|
+
readonly cache_read: 0.02;
|
|
164
|
+
readonly cache_write: 0.25;
|
|
165
|
+
};
|
|
166
|
+
readonly "gpt-5.6-sol": {
|
|
167
|
+
readonly input: 5;
|
|
168
|
+
readonly output: 30;
|
|
169
|
+
readonly cache_read: 0.5;
|
|
170
|
+
readonly cache_write: 6.25;
|
|
171
|
+
readonly context_over_200k: {
|
|
172
|
+
readonly input: 10;
|
|
173
|
+
readonly output: 30;
|
|
174
|
+
readonly cache_read: 1;
|
|
175
|
+
readonly cache_write: 12.5;
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
readonly "gpt-5.6-terra": {
|
|
179
|
+
readonly input: 2;
|
|
180
|
+
readonly output: 12;
|
|
181
|
+
readonly cache_read: 0.2;
|
|
182
|
+
readonly cache_write: 2.5;
|
|
183
|
+
};
|
|
184
|
+
readonly "kimi-k2.7-code": {
|
|
185
|
+
readonly input: 0.95;
|
|
186
|
+
readonly output: 4;
|
|
187
|
+
readonly cache_read: 0.19;
|
|
188
|
+
};
|
|
189
|
+
readonly "kimi-k3": {
|
|
190
|
+
readonly input: 3;
|
|
191
|
+
readonly output: 15;
|
|
192
|
+
readonly cache_read: 0.3;
|
|
193
|
+
};
|
|
194
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTO-GENERATED by scripts/generate-cursor-pricing.ts — do not edit by hand.
|
|
3
|
+
* Source: https://cursor.com/docs/models-and-pricing.md
|
|
4
|
+
* Regenerate: bun run generate:pricing
|
|
5
|
+
*/
|
|
6
|
+
export const CURSOR_PRICING_SOURCE = "https://cursor.com/docs/models-and-pricing.md";
|
|
7
|
+
export const CURSOR_MODEL_COSTS = {
|
|
8
|
+
"claude-fable-5": {
|
|
9
|
+
"input": 10,
|
|
10
|
+
"output": 50,
|
|
11
|
+
"cache_read": 1,
|
|
12
|
+
"cache_write": 12.5
|
|
13
|
+
},
|
|
14
|
+
"claude-haiku-4-5": {
|
|
15
|
+
"input": 1,
|
|
16
|
+
"output": 5,
|
|
17
|
+
"cache_read": 0.1,
|
|
18
|
+
"cache_write": 1.25
|
|
19
|
+
},
|
|
20
|
+
"claude-opus-4-5": {
|
|
21
|
+
"input": 5,
|
|
22
|
+
"output": 25,
|
|
23
|
+
"cache_read": 0.5,
|
|
24
|
+
"cache_write": 6.25
|
|
25
|
+
},
|
|
26
|
+
"claude-opus-4-6": {
|
|
27
|
+
"input": 5,
|
|
28
|
+
"output": 25,
|
|
29
|
+
"cache_read": 0.5,
|
|
30
|
+
"cache_write": 6.25
|
|
31
|
+
},
|
|
32
|
+
"claude-opus-4-7": {
|
|
33
|
+
"input": 5,
|
|
34
|
+
"output": 25,
|
|
35
|
+
"cache_read": 0.5,
|
|
36
|
+
"cache_write": 6.25
|
|
37
|
+
},
|
|
38
|
+
"claude-opus-4-8": {
|
|
39
|
+
"input": 5,
|
|
40
|
+
"output": 25,
|
|
41
|
+
"cache_read": 0.5,
|
|
42
|
+
"cache_write": 6.25
|
|
43
|
+
},
|
|
44
|
+
"claude-opus-5": {
|
|
45
|
+
"input": 5,
|
|
46
|
+
"output": 25,
|
|
47
|
+
"cache_read": 0.5,
|
|
48
|
+
"cache_write": 6.25
|
|
49
|
+
},
|
|
50
|
+
"claude-sonnet-4": {
|
|
51
|
+
"input": 3,
|
|
52
|
+
"output": 15,
|
|
53
|
+
"cache_read": 0.3,
|
|
54
|
+
"cache_write": 3.75,
|
|
55
|
+
"context_over_200k": {
|
|
56
|
+
"input": 6,
|
|
57
|
+
"output": 22.5,
|
|
58
|
+
"cache_read": 0.6,
|
|
59
|
+
"cache_write": 7.5
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"claude-sonnet-4-5": {
|
|
63
|
+
"input": 3,
|
|
64
|
+
"output": 15,
|
|
65
|
+
"cache_read": 0.3,
|
|
66
|
+
"cache_write": 3.75
|
|
67
|
+
},
|
|
68
|
+
"claude-sonnet-4-6": {
|
|
69
|
+
"input": 3,
|
|
70
|
+
"output": 15,
|
|
71
|
+
"cache_read": 0.3,
|
|
72
|
+
"cache_write": 3.75
|
|
73
|
+
},
|
|
74
|
+
"claude-sonnet-5": {
|
|
75
|
+
"input": 3,
|
|
76
|
+
"output": 15,
|
|
77
|
+
"cache_read": 0.3,
|
|
78
|
+
"cache_write": 3.75
|
|
79
|
+
},
|
|
80
|
+
"gemini-2.5-flash": {
|
|
81
|
+
"input": 0.3,
|
|
82
|
+
"output": 2.5,
|
|
83
|
+
"cache_read": 0.03
|
|
84
|
+
},
|
|
85
|
+
"gemini-3-flash": {
|
|
86
|
+
"input": 0.5,
|
|
87
|
+
"output": 3,
|
|
88
|
+
"cache_read": 0.05
|
|
89
|
+
},
|
|
90
|
+
"gemini-3.1-pro": {
|
|
91
|
+
"input": 2,
|
|
92
|
+
"output": 12,
|
|
93
|
+
"cache_read": 0.2
|
|
94
|
+
},
|
|
95
|
+
"gemini-3.5-flash": {
|
|
96
|
+
"input": 1.5,
|
|
97
|
+
"output": 9,
|
|
98
|
+
"cache_read": 0.15
|
|
99
|
+
},
|
|
100
|
+
"gemini-3.6-flash": {
|
|
101
|
+
"input": 1.5,
|
|
102
|
+
"output": 7.5,
|
|
103
|
+
"cache_read": 0.15
|
|
104
|
+
},
|
|
105
|
+
"glm-5.2": {
|
|
106
|
+
"input": 1.4,
|
|
107
|
+
"output": 4.4,
|
|
108
|
+
"cache_read": 0.26
|
|
109
|
+
},
|
|
110
|
+
"gpt-5-mini": {
|
|
111
|
+
"input": 0.25,
|
|
112
|
+
"output": 2,
|
|
113
|
+
"cache_read": 0.025
|
|
114
|
+
},
|
|
115
|
+
"gpt-5.1": {
|
|
116
|
+
"input": 1.25,
|
|
117
|
+
"output": 10,
|
|
118
|
+
"cache_read": 0.125
|
|
119
|
+
},
|
|
120
|
+
"gpt-5.2": {
|
|
121
|
+
"input": 1.75,
|
|
122
|
+
"output": 14,
|
|
123
|
+
"cache_read": 0.175
|
|
124
|
+
},
|
|
125
|
+
"gpt-5.3-codex": {
|
|
126
|
+
"input": 1.75,
|
|
127
|
+
"output": 14,
|
|
128
|
+
"cache_read": 0.175
|
|
129
|
+
},
|
|
130
|
+
"gpt-5.4": {
|
|
131
|
+
"input": 2.5,
|
|
132
|
+
"output": 15,
|
|
133
|
+
"cache_read": 0.25,
|
|
134
|
+
"context_over_200k": {
|
|
135
|
+
"input": 5,
|
|
136
|
+
"output": 15,
|
|
137
|
+
"cache_read": 0.5
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"gpt-5.4-mini": {
|
|
141
|
+
"input": 0.75,
|
|
142
|
+
"output": 4.5,
|
|
143
|
+
"cache_read": 0.075
|
|
144
|
+
},
|
|
145
|
+
"gpt-5.4-nano": {
|
|
146
|
+
"input": 0.2,
|
|
147
|
+
"output": 1.25,
|
|
148
|
+
"cache_read": 0.02
|
|
149
|
+
},
|
|
150
|
+
"gpt-5.5": {
|
|
151
|
+
"input": 5,
|
|
152
|
+
"output": 30,
|
|
153
|
+
"cache_read": 0.5,
|
|
154
|
+
"context_over_200k": {
|
|
155
|
+
"input": 10,
|
|
156
|
+
"output": 30,
|
|
157
|
+
"cache_read": 1
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
"gpt-5.6-luna": {
|
|
161
|
+
"input": 0.2,
|
|
162
|
+
"output": 1.2,
|
|
163
|
+
"cache_read": 0.02,
|
|
164
|
+
"cache_write": 0.25
|
|
165
|
+
},
|
|
166
|
+
"gpt-5.6-sol": {
|
|
167
|
+
"input": 5,
|
|
168
|
+
"output": 30,
|
|
169
|
+
"cache_read": 0.5,
|
|
170
|
+
"cache_write": 6.25,
|
|
171
|
+
"context_over_200k": {
|
|
172
|
+
"input": 10,
|
|
173
|
+
"output": 30,
|
|
174
|
+
"cache_read": 1,
|
|
175
|
+
"cache_write": 12.5
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
"gpt-5.6-terra": {
|
|
179
|
+
"input": 2,
|
|
180
|
+
"output": 12,
|
|
181
|
+
"cache_read": 0.2,
|
|
182
|
+
"cache_write": 2.5
|
|
183
|
+
},
|
|
184
|
+
"kimi-k2.7-code": {
|
|
185
|
+
"input": 0.95,
|
|
186
|
+
"output": 4,
|
|
187
|
+
"cache_read": 0.19
|
|
188
|
+
},
|
|
189
|
+
"kimi-k3": {
|
|
190
|
+
"input": 3,
|
|
191
|
+
"output": 15,
|
|
192
|
+
"cache_read": 0.3
|
|
193
|
+
}
|
|
194
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor model token pricing for OpenCode cost reporting.
|
|
3
|
+
*
|
|
4
|
+
* Rates come from Cursor's public docs (Other Models pool = provider API price).
|
|
5
|
+
* Generated data lives in `pricing-data.ts`; regenerate with
|
|
6
|
+
* `bun run generate:pricing`. Unknown / unpublished models get no `cost`.
|
|
7
|
+
*/
|
|
8
|
+
/** Classic OpenCode / models.dev cost shape used in provider config. */
|
|
9
|
+
export type OpenCodeModelCost = {
|
|
10
|
+
input: number;
|
|
11
|
+
output: number;
|
|
12
|
+
cache_read?: number;
|
|
13
|
+
cache_write?: number;
|
|
14
|
+
context_over_200k?: OpenCodeModelCost;
|
|
15
|
+
};
|
|
16
|
+
/** OpenCode 2.0 `Model.Info.cost` entry (array of tiered rates). */
|
|
17
|
+
export type OpenCode2ModelCost = {
|
|
18
|
+
tier?: {
|
|
19
|
+
type: "context";
|
|
20
|
+
size: number;
|
|
21
|
+
};
|
|
22
|
+
input: number;
|
|
23
|
+
output: number;
|
|
24
|
+
cache: {
|
|
25
|
+
read: number;
|
|
26
|
+
write: number;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export type CursorPricingCoverage = {
|
|
30
|
+
priced: string[];
|
|
31
|
+
missing: string[];
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Models we expose that Cursor does not currently publish numeric rates for
|
|
35
|
+
* on the Other Models table (Auto / first-party Cursor Models pool).
|
|
36
|
+
*/
|
|
37
|
+
export declare const CURSOR_UNPRICED_MODEL_IDS: readonly ["default", "composer-2.5", "grok-4.5"];
|
|
38
|
+
export declare function validateOpenCodeModelCost(value: unknown, path?: string): {
|
|
39
|
+
valid: boolean;
|
|
40
|
+
errors: string[];
|
|
41
|
+
};
|
|
42
|
+
export declare function isOpenCodeModelCost(value: unknown): value is OpenCodeModelCost;
|
|
43
|
+
/** Strip synthetic OpenCode long-context suffix (`…-1m`, `…-1m-2`, …). */
|
|
44
|
+
export declare function wireModelIdForPricing(modelId: string): {
|
|
45
|
+
baseId: string;
|
|
46
|
+
longContextEntry: boolean;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Look up classic OpenCode cost for a catalog / wire model id.
|
|
50
|
+
* Synthetic `-1m` entries retain both base and documented long-context rates.
|
|
51
|
+
*/
|
|
52
|
+
export declare function getCursorModelCost(modelId: string): OpenCodeModelCost | undefined;
|
|
53
|
+
export declare function applyCursorModelCost<T extends Record<string, unknown>>(modelId: string, entry: T): T & {
|
|
54
|
+
cost?: OpenCodeModelCost;
|
|
55
|
+
};
|
|
56
|
+
/** Convert classic cost → OpenCode 2.0 `Model.Info.cost` array. */
|
|
57
|
+
export declare function toOpenCode2Costs(cost: OpenCodeModelCost | undefined): OpenCode2ModelCost[];
|
|
58
|
+
/**
|
|
59
|
+
* Coverage helper for CI / maintainer checks.
|
|
60
|
+
* Intentionally unpriced first-party ids are counted as priced (covered).
|
|
61
|
+
*/
|
|
62
|
+
export declare function checkCursorPricingCoverage(modelIds: string[]): CursorPricingCoverage;
|