cursor-opencode-provider 0.6.3 → 0.6.6

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.
@@ -1,5 +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
+ import { applyCursorModelCost, hasCursorFastPricing } from "./pricing.js";
3
3
  import { getDocumentedCursorModelContext, resolveCursorModelSupportsImages, } from "./model-metadata.js";
4
4
  /**
5
5
  * Cursor `ModelInfo` → OpenCode model-config mapping.
@@ -109,6 +109,9 @@ function modelInfoVariants(mi, variants) {
109
109
  function isLongContextVariant(v) {
110
110
  return v.parameterValues.some((p) => p.id === "context" && parseCursorContextLimit(p.value) === 1_000_000);
111
111
  }
112
+ function isFastVariant(v) {
113
+ return v.parameterValues.some((p) => p.id === "fast" && p.value === "true");
114
+ }
112
115
  function variantsForTier(mi, tier) {
113
116
  return mi.variants.filter((v) => isLongContextVariant(v) === (tier === "long"));
114
117
  }
@@ -139,10 +142,12 @@ export function thinkingSuffixBaseNames(models) {
139
142
  }
140
143
  export function modelInfoToConfig(mi, options = {}) {
141
144
  const contextTier = options.contextTier ?? "base";
142
- const variants = variantsForTier(mi, contextTier);
145
+ const variants = options.variants ?? variantsForTier(mi, contextTier);
143
146
  let name = baseName(mi);
144
147
  if (options.thinkingSuffix)
145
148
  name += " Thinking";
149
+ if (options.fast)
150
+ name += " Fast";
146
151
  if (contextTier === "long")
147
152
  name += " 1M";
148
153
  // OpenCode's context limit is static per model entry, while Cursor's context
@@ -176,8 +181,10 @@ export function modelInfoToConfig(mi, options = {}) {
176
181
  const variantConfig = modelInfoVariants(mi, variants);
177
182
  if (variantConfig)
178
183
  config.variants = variantConfig;
179
- if (contextTier === "long") {
180
- const defaultVariant = variants.find((v) => v.isDefaultMax) ?? variants[0];
184
+ if (contextTier === "long" || options.fast) {
185
+ const defaultVariant = contextTier === "long"
186
+ ? (variants.find((v) => v.isDefaultMax) ?? variants[0])
187
+ : (variants.find((v) => v.isDefaultNonMax) ?? variants[0]);
181
188
  if (defaultVariant) {
182
189
  config.options = {
183
190
  [CURSOR_WIRE_MODEL_ID_KEY]: mi.id,
@@ -187,30 +194,87 @@ export function modelInfoToConfig(mi, options = {}) {
187
194
  }
188
195
  return config;
189
196
  }
197
+ function speedGroups(variants, splitFast) {
198
+ if (!splitFast)
199
+ return variants.length > 0 ? [{ fast: false, variants }] : [];
200
+ const slow = variants.filter((variant) => !isFastVariant(variant));
201
+ const fast = variants.filter((variant) => isFastVariant(variant));
202
+ const groups = [];
203
+ if (slow.length > 0)
204
+ groups.push({ fast: false, variants: slow });
205
+ if (fast.length > 0)
206
+ groups.push({ fast: true, variants: fast });
207
+ return groups;
208
+ }
209
+ function uniqueCatalogId(usedIds, modelId, suffix) {
210
+ let id = `${modelId}${suffix}`;
211
+ if (!usedIds.has(id)) {
212
+ usedIds.add(id);
213
+ return id;
214
+ }
215
+ let n = 2;
216
+ while (true) {
217
+ const candidate = suffix === "-fast"
218
+ ? `${modelId}-fast-${n}`
219
+ : suffix === "-1m-fast"
220
+ ? `${modelId}-1m-${n}-fast`
221
+ : `${modelId}-1m-${n}`;
222
+ if (!usedIds.has(candidate)) {
223
+ usedIds.add(candidate);
224
+ return candidate;
225
+ }
226
+ n++;
227
+ }
228
+ }
190
229
  export function modelsToConfig(models) {
191
230
  const ambiguous = thinkingSuffixBaseNames(models);
192
231
  const out = {};
193
232
  const usedIds = new Set(models.map((m) => m.id));
194
233
  for (const m of models) {
195
234
  const thinkingSuffix = !!m.supportsThinking && ambiguous.has(baseName(m));
196
- const baseVariants = variantsForTier(m, "base");
197
- const longVariants = variantsForTier(m, "long");
198
- if (baseVariants.length > 0 || longVariants.length === 0) {
199
- out[m.id] = applyCursorModelCost(m.id, modelInfoToConfig(m, { thinkingSuffix, contextTier: "base" }));
235
+ const splitFast = hasCursorFastPricing(m.id);
236
+ const groups = [];
237
+ for (const contextTier of ["base", "long"]) {
238
+ const suffixFor = (fast) => contextTier === "long" ? (fast ? "-1m-fast" : "-1m") : (fast ? "-fast" : "");
239
+ for (const group of speedGroups(variantsForTier(m, contextTier), splitFast)) {
240
+ groups.push({
241
+ contextTier,
242
+ fast: group.fast,
243
+ variants: group.variants,
244
+ suffix: suffixFor(group.fast),
245
+ });
246
+ }
200
247
  }
201
- if (longVariants.length === 0)
202
- continue;
203
- if (baseVariants.length === 0) {
204
- // No separate base tier — the wire id itself is the long-context entry.
205
- out[m.id] = applyCursorModelCost(`${m.id}-1m`, modelInfoToConfig(m, { thinkingSuffix, contextTier: "long" }));
248
+ if (groups.length === 0) {
249
+ out[m.id] = applyCursorModelCost(m.id, modelInfoToConfig(m, { thinkingSuffix, contextTier: "base" }));
206
250
  continue;
207
251
  }
208
- let longId = `${m.id}-1m`;
209
- let suffix = 2;
210
- while (usedIds.has(longId))
211
- longId = `${m.id}-1m-${suffix++}`;
212
- usedIds.add(longId);
213
- out[longId] = applyCursorModelCost(longId, modelInfoToConfig(m, { thinkingSuffix, contextTier: "long" }));
252
+ const primaryIndex = groups.findIndex((group) => group.suffix === "");
253
+ const primary = primaryIndex >= 0 ? primaryIndex : 0;
254
+ for (const [index, group] of groups.entries()) {
255
+ const catalogId = index === primary
256
+ ? m.id
257
+ : uniqueCatalogId(usedIds, m.id, group.suffix || "-fast");
258
+ const pricingId = group.fast ? `${m.id}-fast` : (group.contextTier === "long" ? `${m.id}-1m` : m.id);
259
+ const config = modelInfoToConfig(m, {
260
+ thinkingSuffix,
261
+ contextTier: group.contextTier,
262
+ variants: group.variants,
263
+ fast: group.fast,
264
+ });
265
+ if (catalogId !== m.id && !config.options) {
266
+ const defaultVariant = (group.contextTier === "long"
267
+ ? group.variants.find((v) => v.isDefaultMax)
268
+ : group.variants.find((v) => v.isDefaultNonMax)) ?? group.variants[0];
269
+ if (defaultVariant) {
270
+ config.options = {
271
+ [CURSOR_WIRE_MODEL_ID_KEY]: m.id,
272
+ [CURSOR_VARIANT_PARAMETERS_KEY]: defaultVariant.parameterValues.map((p) => ({ ...p })),
273
+ };
274
+ }
275
+ }
276
+ out[catalogId] = applyCursorModelCost(pricingId, config);
277
+ }
214
278
  }
215
279
  return out;
216
280
  }
package/dist/models.d.ts CHANGED
@@ -47,6 +47,14 @@ export declare function extractCursorVariantParameters(providerOptions: Record<s
47
47
  export declare function resolveCursorWireModelId(providerOptions: Record<string, unknown> | undefined, fallback: string): string;
48
48
  /** True when resolved params select the long-context (1m) tier. */
49
49
  export declare function paramsImplyMaxMode(params: ModelParameterValue[]): boolean;
50
+ /**
51
+ * Resolve the request-level max_mode bit. An explicit variant pick owns the
52
+ * context tier; otherwise a standalone maxMode hint may select max mode.
53
+ */
54
+ export declare function resolveVariantMaxMode(params: ModelParameterValue[], opts?: {
55
+ picked?: ModelParameterValue[];
56
+ maxMode?: boolean;
57
+ }): boolean;
50
58
  export declare function parseCursorContextLimit(value: unknown): number | undefined;
51
59
  export declare function isCacheFresh(cache: ModelCache, ttlMs?: number): boolean;
52
60
  export declare function cacheFilePath(cacheDir: string): string;
package/dist/models.js CHANGED
@@ -181,6 +181,13 @@ export function paramsImplyMaxMode(params) {
181
181
  return params.some((parameter) => parameter.id === "context" &&
182
182
  parseCursorContextLimit(parameter.value) === 1_000_000);
183
183
  }
184
+ /**
185
+ * Resolve the request-level max_mode bit. An explicit variant pick owns the
186
+ * context tier; otherwise a standalone maxMode hint may select max mode.
187
+ */
188
+ export function resolveVariantMaxMode(params, opts = {}) {
189
+ return paramsImplyMaxMode(params) || (opts.picked === undefined && opts.maxMode === true);
190
+ }
184
191
  // Cursor encodes a model's context window as a variant parameter `id: "context"`
185
192
  // whose value is a tier string — the same 200k / 272k / 300k / 1m the IDE's
186
193
  // picker shows. The base tier rides on the default non-max variant; the 1M
@@ -352,10 +359,7 @@ export function resolveVariantParameters(model, opts = {}) {
352
359
  pickedById.size === picked.length &&
353
360
  v.parameterValues.every((parameter) => pickedById.get(parameter.id) === parameter.value));
354
361
  if (exact) {
355
- return buildRequestedModelParams(exact.parameterValues, {
356
- reasoningEffort: opts.reasoningEffort,
357
- maxMode: wantMax,
358
- });
362
+ return buildRequestedModelParams(exact.parameterValues);
359
363
  }
360
364
  throw new CursorVariantSelectionError(`is stale for model ${JSON.stringify(model.id)}: its exact parameter tuple is unavailable`);
361
365
  }
@@ -46,9 +46,10 @@ export function applyCursorProvider(draft) {
46
46
  /** Translate one `modelsToConfig` entry into the 2.0 `Model.Info` shape. */
47
47
  function applyModelEntry(draft, id, entry) {
48
48
  const options = entry.options;
49
- // Long-context entries get a synthetic OpenCode id (`<id>-1m`) while still
50
- // addressing the same Cursor model on the wire. V1 smuggled that through
51
- // provider options; 2.0 has a first-class `modelID` for exactly this.
49
+ // Long-context and Fast entries get synthetic OpenCode ids (`<id>-1m`,
50
+ // `<id>-fast`, `<id>-1m-fast`) while still addressing the same Cursor model
51
+ // on the wire. V1 smuggled that through provider options; 2.0 has a
52
+ // first-class `modelID` for exactly this.
52
53
  const wireId = typeof options?.[CURSOR_WIRE_MODEL_ID_KEY] === "string"
53
54
  ? options[CURSOR_WIRE_MODEL_ID_KEY]
54
55
  : id;
@@ -13,6 +13,12 @@ export declare const CURSOR_MODEL_COSTS: {
13
13
  readonly cache_read: 1;
14
14
  readonly cache_write: 12.5;
15
15
  };
16
+ readonly "claude-fable-5-1": {
17
+ readonly input: 10;
18
+ readonly output: 50;
19
+ readonly cache_read: 0.25;
20
+ readonly cache_write: 12.5;
21
+ };
16
22
  readonly "claude-haiku-4-5": {
17
23
  readonly input: 1;
18
24
  readonly output: 5;
@@ -79,6 +85,16 @@ export declare const CURSOR_MODEL_COSTS: {
79
85
  readonly cache_read: 0.2;
80
86
  readonly cache_write: 2.5;
81
87
  };
88
+ readonly "composer-2.5": {
89
+ readonly input: 0.5;
90
+ readonly output: 2.5;
91
+ readonly cache_read: 0.2;
92
+ };
93
+ readonly "composer-2.5-fast": {
94
+ readonly input: 3;
95
+ readonly output: 15;
96
+ readonly cache_read: 0.5;
97
+ };
82
98
  readonly "gemini-2.5-flash": {
83
99
  readonly input: 0.3;
84
100
  readonly output: 2.5;
@@ -171,15 +187,15 @@ export declare const CURSOR_MODEL_COSTS: {
171
187
  readonly cache_write: 0.25;
172
188
  };
173
189
  readonly "gpt-5.6-sol": {
174
- readonly input: 5;
175
- readonly output: 30;
176
- readonly cache_read: 0.5;
177
- readonly cache_write: 6.25;
190
+ readonly input: 4;
191
+ readonly output: 20;
192
+ readonly cache_read: 0.4;
193
+ readonly cache_write: 5;
178
194
  readonly context_over_200k: {
179
- readonly input: 10;
180
- readonly output: 30;
181
- readonly cache_read: 1;
182
- readonly cache_write: 12.5;
195
+ readonly input: 8;
196
+ readonly output: 20;
197
+ readonly cache_read: 0.8;
198
+ readonly cache_write: 10;
183
199
  };
184
200
  };
185
201
  readonly "gpt-5.6-terra": {
@@ -188,6 +204,26 @@ export declare const CURSOR_MODEL_COSTS: {
188
204
  readonly cache_read: 0.2;
189
205
  readonly cache_write: 2.5;
190
206
  };
207
+ readonly "grok-4.5": {
208
+ readonly input: 2;
209
+ readonly output: 6;
210
+ readonly cache_read: 0.5;
211
+ };
212
+ readonly "grok-4.5-fast": {
213
+ readonly input: 4;
214
+ readonly output: 18;
215
+ readonly cache_read: 1;
216
+ };
217
+ readonly "grok-4.6": {
218
+ readonly input: 2;
219
+ readonly output: 6;
220
+ readonly cache_read: 0.5;
221
+ };
222
+ readonly "grok-4.6-fast": {
223
+ readonly input: 4;
224
+ readonly output: 12;
225
+ readonly cache_read: 1;
226
+ };
191
227
  readonly "kimi-k2.7-code": {
192
228
  readonly input: 0.95;
193
229
  readonly output: 4;
@@ -204,6 +240,10 @@ export declare const CURSOR_MODEL_CONTEXTS: {
204
240
  readonly maxContext: 300000;
205
241
  readonly maxContextForMaxMode: 1000000;
206
242
  };
243
+ readonly "claude-fable-5-1": {
244
+ readonly maxContext: 300000;
245
+ readonly maxContextForMaxMode: 1000000;
246
+ };
207
247
  readonly "claude-haiku-4-5": {
208
248
  readonly maxContext: 200000;
209
249
  };
@@ -327,6 +367,9 @@ export declare const CURSOR_MODEL_CAPABILITIES: {
327
367
  readonly "claude-fable-5": {
328
368
  readonly supportsImages: true;
329
369
  };
370
+ readonly "claude-fable-5-1": {
371
+ readonly supportsImages: true;
372
+ };
330
373
  readonly "claude-haiku-4-5": {
331
374
  readonly supportsImages: true;
332
375
  };
@@ -360,9 +403,6 @@ export declare const CURSOR_MODEL_CAPABILITIES: {
360
403
  readonly "composer-2.5": {
361
404
  readonly supportsImages: true;
362
405
  };
363
- readonly default: {
364
- readonly supportsImages: false;
365
- };
366
406
  readonly "gemini-2.5-flash": {
367
407
  readonly supportsImages: true;
368
408
  };
@@ -13,6 +13,12 @@ export const CURSOR_MODEL_COSTS = {
13
13
  "cache_read": 1,
14
14
  "cache_write": 12.5
15
15
  },
16
+ "claude-fable-5-1": {
17
+ "input": 10,
18
+ "output": 50,
19
+ "cache_read": 0.25,
20
+ "cache_write": 12.5
21
+ },
16
22
  "claude-haiku-4-5": {
17
23
  "input": 1,
18
24
  "output": 5,
@@ -79,6 +85,16 @@ export const CURSOR_MODEL_COSTS = {
79
85
  "cache_read": 0.2,
80
86
  "cache_write": 2.5
81
87
  },
88
+ "composer-2.5": {
89
+ "input": 0.5,
90
+ "output": 2.5,
91
+ "cache_read": 0.2
92
+ },
93
+ "composer-2.5-fast": {
94
+ "input": 3,
95
+ "output": 15,
96
+ "cache_read": 0.5
97
+ },
82
98
  "gemini-2.5-flash": {
83
99
  "input": 0.3,
84
100
  "output": 2.5,
@@ -171,15 +187,15 @@ export const CURSOR_MODEL_COSTS = {
171
187
  "cache_write": 0.25
172
188
  },
173
189
  "gpt-5.6-sol": {
174
- "input": 5,
175
- "output": 30,
176
- "cache_read": 0.5,
177
- "cache_write": 6.25,
190
+ "input": 4,
191
+ "output": 20,
192
+ "cache_read": 0.4,
193
+ "cache_write": 5,
178
194
  "context_over_200k": {
179
- "input": 10,
180
- "output": 30,
181
- "cache_read": 1,
182
- "cache_write": 12.5
195
+ "input": 8,
196
+ "output": 20,
197
+ "cache_read": 0.8,
198
+ "cache_write": 10
183
199
  }
184
200
  },
185
201
  "gpt-5.6-terra": {
@@ -188,6 +204,26 @@ export const CURSOR_MODEL_COSTS = {
188
204
  "cache_read": 0.2,
189
205
  "cache_write": 2.5
190
206
  },
207
+ "grok-4.5": {
208
+ "input": 2,
209
+ "output": 6,
210
+ "cache_read": 0.5
211
+ },
212
+ "grok-4.5-fast": {
213
+ "input": 4,
214
+ "output": 18,
215
+ "cache_read": 1
216
+ },
217
+ "grok-4.6": {
218
+ "input": 2,
219
+ "output": 6,
220
+ "cache_read": 0.5
221
+ },
222
+ "grok-4.6-fast": {
223
+ "input": 4,
224
+ "output": 12,
225
+ "cache_read": 1
226
+ },
191
227
  "kimi-k2.7-code": {
192
228
  "input": 0.95,
193
229
  "output": 4,
@@ -204,6 +240,10 @@ export const CURSOR_MODEL_CONTEXTS = {
204
240
  "maxContext": 300000,
205
241
  "maxContextForMaxMode": 1000000
206
242
  },
243
+ "claude-fable-5-1": {
244
+ "maxContext": 300000,
245
+ "maxContextForMaxMode": 1000000
246
+ },
207
247
  "claude-haiku-4-5": {
208
248
  "maxContext": 200000
209
249
  },
@@ -327,6 +367,9 @@ export const CURSOR_MODEL_CAPABILITIES = {
327
367
  "claude-fable-5": {
328
368
  "supportsImages": true
329
369
  },
370
+ "claude-fable-5-1": {
371
+ "supportsImages": true
372
+ },
330
373
  "claude-haiku-4-5": {
331
374
  "supportsImages": true
332
375
  },
@@ -360,9 +403,6 @@ export const CURSOR_MODEL_CAPABILITIES = {
360
403
  "composer-2.5": {
361
404
  "supportsImages": true
362
405
  },
363
- "default": {
364
- "supportsImages": false
365
- },
366
406
  "gemini-2.5-flash": {
367
407
  "supportsImages": true
368
408
  },
package/dist/pricing.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Cursor model token pricing for OpenCode cost reporting.
3
3
  *
4
- * Rates come from Cursor's public docs (Other Models pool = provider API price).
4
+ * Rates come from Cursor's public docs (Cursor Models + Other Models tables).
5
5
  * Generated data lives in `pricing-data.ts`; regenerate with
6
6
  * `bun run generate:pricing`. Unknown / unpublished models get no `cost`.
7
7
  */
@@ -32,22 +32,28 @@ export type CursorPricingCoverage = {
32
32
  };
33
33
  /**
34
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).
35
+ * (Auto is billed at the routed model's list price).
36
36
  */
37
- export declare const CURSOR_UNPRICED_MODEL_IDS: readonly ["default", "composer-2.5", "grok-4.5", "grok-4.6"];
37
+ export declare const CURSOR_UNPRICED_MODEL_IDS: readonly ["default"];
38
38
  export declare function validateOpenCodeModelCost(value: unknown, path?: string): {
39
39
  valid: boolean;
40
40
  errors: string[];
41
41
  };
42
42
  export declare function isOpenCodeModelCost(value: unknown): value is OpenCodeModelCost;
43
- /** Strip synthetic OpenCode long-context suffix (`…-1m`, `…-1m-2`, …). */
43
+ /** Strip synthetic OpenCode suffixes (`…-1m`, `…-1m-2`, `…-1m-fast`, …). */
44
44
  export declare function wireModelIdForPricing(modelId: string): {
45
45
  baseId: string;
46
46
  longContextEntry: boolean;
47
47
  };
48
+ /**
49
+ * True when Cursor publishes a distinct Fast rate for this wire model id.
50
+ * Catalog code uses this to split Fast variants into a `-fast` entry.
51
+ */
52
+ export declare function hasCursorFastPricing(modelId: string): boolean;
48
53
  /**
49
54
  * Look up classic OpenCode cost for a catalog / wire model id.
50
55
  * Synthetic `-1m` entries retain both base and documented long-context rates.
56
+ * Synthetic `-fast` entries are stored as their own keys.
51
57
  */
52
58
  export declare function getCursorModelCost(modelId: string): OpenCodeModelCost | undefined;
53
59
  export declare function applyCursorModelCost<T extends Record<string, unknown>>(modelId: string, entry: T): T & {
package/dist/pricing.js CHANGED
@@ -1,16 +1,16 @@
1
1
  /**
2
2
  * Cursor model token pricing for OpenCode cost reporting.
3
3
  *
4
- * Rates come from Cursor's public docs (Other Models pool = provider API price).
4
+ * Rates come from Cursor's public docs (Cursor Models + Other Models tables).
5
5
  * Generated data lives in `pricing-data.ts`; regenerate with
6
6
  * `bun run generate:pricing`. Unknown / unpublished models get no `cost`.
7
7
  */
8
8
  import { CURSOR_MODEL_COSTS } from "./pricing-data.js";
9
9
  /**
10
10
  * Models we expose that Cursor does not currently publish numeric rates for
11
- * on the Other Models table (Auto / first-party Cursor Models pool).
11
+ * (Auto is billed at the routed model's list price).
12
12
  */
13
- export const CURSOR_UNPRICED_MODEL_IDS = ["default", "composer-2.5", "grok-4.5", "grok-4.6"];
13
+ export const CURSOR_UNPRICED_MODEL_IDS = ["default"];
14
14
  const UNPRICED = new Set(CURSOR_UNPRICED_MODEL_IDS);
15
15
  function isRecord(value) {
16
16
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -47,12 +47,20 @@ export function validateOpenCodeModelCost(value, path = "cost") {
47
47
  export function isOpenCodeModelCost(value) {
48
48
  return validateOpenCodeModelCost(value).valid;
49
49
  }
50
- /** Strip synthetic OpenCode long-context suffix (`…-1m`, `…-1m-2`, …). */
50
+ /** Strip synthetic OpenCode suffixes (`…-1m`, `…-1m-2`, `…-1m-fast`, …). */
51
51
  export function wireModelIdForPricing(modelId) {
52
- const match = /^(.*)-1m(?:-(\d+))?$/.exec(modelId);
53
- if (!match)
54
- return { baseId: modelId, longContextEntry: false };
55
- return { baseId: match[1], longContextEntry: true };
52
+ const match = /^(.*)-1m(?:-\d+)?(-fast)?$/.exec(modelId);
53
+ if (match) {
54
+ return { baseId: `${match[1]}${match[2] ?? ""}`, longContextEntry: true };
55
+ }
56
+ return { baseId: modelId, longContextEntry: false };
57
+ }
58
+ /**
59
+ * True when Cursor publishes a distinct Fast rate for this wire model id.
60
+ * Catalog code uses this to split Fast variants into a `-fast` entry.
61
+ */
62
+ export function hasCursorFastPricing(modelId) {
63
+ return Object.prototype.hasOwnProperty.call(CURSOR_MODEL_COSTS, `${modelId}-fast`);
56
64
  }
57
65
  function asMutableCost(value) {
58
66
  const copy = { input: value.input, output: value.output };
@@ -68,6 +76,7 @@ function asMutableCost(value) {
68
76
  /**
69
77
  * Look up classic OpenCode cost for a catalog / wire model id.
70
78
  * Synthetic `-1m` entries retain both base and documented long-context rates.
79
+ * Synthetic `-fast` entries are stored as their own keys.
71
80
  */
72
81
  export function getCursorModelCost(modelId) {
73
82
  const { baseId } = wireModelIdForPricing(modelId);
@@ -22,3 +22,8 @@ export declare const CURSOR_EXEC_VARIANTS: readonly CursorExecVariant[];
22
22
  export declare function cursorExecVariantByRequestField(field: number): CursorExecVariant | undefined;
23
23
  export declare function cursorExecVariantByRequestName(name: string): CursorExecVariant | undefined;
24
24
  export declare function describeCursorExecVariant(field: number | undefined): string;
25
+ /**
26
+ * `ForceBackgroundShellResult` / `ForceBackgroundSubagentResult` status.
27
+ * Cursor CLI `ForceBackgroundStatus`: 0 unspecified, 1 ok, 2 error.
28
+ */
29
+ export declare const FORCE_BACKGROUND_STATUS_ERROR = 2;
@@ -1,10 +1,12 @@
1
1
  export const CURSOR_EXEC_VARIANTS = [
2
- { requestField: 2, requestName: "shell_args", resultField: 2, resultName: "shell_result", handling: "unsupported" },
2
+ { requestField: 2, requestName: "shell_args", resultField: 2, resultName: "shell_result", handling: "opencode-tool" },
3
3
  { requestField: 3, requestName: "write_args", resultField: 3, resultName: "write_result", handling: "opencode-tool" },
4
4
  { requestField: 4, requestName: "delete_args", resultField: 4, resultName: "delete_result", handling: "opencode-tool" },
5
5
  { requestField: 5, requestName: "grep_args", resultField: 5, resultName: "grep_result", handling: "opencode-tool" },
6
6
  { requestField: 7, requestName: "read_args", resultField: 7, resultName: "read_result", handling: "opencode-tool" },
7
7
  { requestField: 8, requestName: "ls_args", resultField: 8, resultName: "ls_result", handling: "opencode-tool" },
8
+ // Dedicated Cursor read_lints (#9) is not planned. OpenCode only presents
9
+ // LSP errors on write/edit/apply_patch output plus GET /lsp status / navigation.
8
10
  { requestField: 9, requestName: "diagnostics_args", resultField: 9, resultName: "diagnostics_result", handling: "unsupported" },
9
11
  { requestField: 10, requestName: "request_context_args", resultField: 10, resultName: "request_context_result", handling: "provider-control" },
10
12
  { requestField: 11, requestName: "mcp_args", resultField: 11, resultName: "mcp_result", handling: "opencode-tool" },
@@ -23,12 +25,13 @@ export const CURSOR_EXEC_VARIANTS = [
23
25
  { requestField: 31, requestName: "force_background_subagent_args", resultField: 31, resultName: "force_background_subagent_result", handling: "unsupported" },
24
26
  { requestField: 36, requestName: "mcp_state_exec_args", resultField: 36, resultName: "mcp_state_exec_result", handling: "provider-control" },
25
27
  { requestField: 37, requestName: "subagent_await_args", resultField: 37, resultName: "subagent_await_result", handling: "unsupported" },
28
+ // OpenCode has no Auto-review / `--auto-review` mode. Not planned: keep the typed soft-deny.
26
29
  { requestField: 38, requestName: "smart_mode_classifier_args", resultField: 38, resultName: "smart_mode_classifier_result", handling: "unsupported" },
27
30
  { requestField: 40, requestName: "canvas_diagnostics_args", resultField: 40, resultName: "canvas_diagnostics_result", handling: "unsupported" },
28
31
  { requestField: 41, requestName: "shell_allowlist_precheck_args", resultField: 41, resultName: "shell_allowlist_precheck_result", handling: "unsupported" },
29
32
  { requestField: 42, requestName: "mcp_allowlist_precheck_args", resultField: 42, resultName: "mcp_allowlist_precheck_result", handling: "unsupported" },
30
33
  { requestField: 43, requestName: "web_fetch_allowlist_precheck_args", resultField: 43, resultName: "web_fetch_allowlist_precheck_result", handling: "unsupported" },
31
- { requestField: 44, requestName: "git_diff_request", resultField: 44, resultName: "git_diff_response", handling: "unsupported" },
34
+ { requestField: 44, requestName: "git_diff_request", resultField: 44, resultName: "git_diff_response", handling: "provider-control" },
32
35
  { requestField: 45, requestName: "pi_read_args", resultField: 46, resultName: "pi_read_result", handling: "opencode-tool" },
33
36
  { requestField: 46, requestName: "pi_bash_args", resultField: 47, resultName: "pi_bash_result", handling: "opencode-tool" },
34
37
  { requestField: 47, requestName: "pi_edit_args", resultField: 48, resultName: "pi_edit_result", handling: "opencode-tool" },
@@ -36,6 +39,13 @@ export const CURSOR_EXEC_VARIANTS = [
36
39
  { requestField: 49, requestName: "pi_grep_args", resultField: 50, resultName: "pi_grep_result", handling: "opencode-tool" },
37
40
  { requestField: 50, requestName: "pi_find_args", resultField: 51, resultName: "pi_find_result", handling: "opencode-tool" },
38
41
  { requestField: 51, requestName: "pi_ls_args", resultField: 52, resultName: "pi_ls_result", handling: "opencode-tool" },
42
+ // Mini-SWE request #52 pairs with ShellResult at #55 (Pi-style offset; #55
43
+ // request is accept_hook_additional_contexts, not a variant).
44
+ // Mini-SWE / SWE-agent bash is not planned (same surface as Auto-review). Keep the typed soft-deny.
45
+ { requestField: 52, requestName: "mini_swe_agent_bash_args", resultField: 55, resultName: "mini_swe_agent_bash_result", handling: "unsupported" },
46
+ { requestField: 53, requestName: "conversation_search_args", resultField: 53, resultName: "conversation_search_result", handling: "unsupported" },
47
+ { requestField: 54, requestName: "agent_store_conflict_args", resultField: 54, resultName: "agent_store_conflict_result", handling: "unsupported" },
48
+ { requestField: 56, requestName: "adopt_args", resultField: 56, resultName: "adopt_result", handling: "unsupported" },
39
49
  ];
40
50
  const BY_REQUEST_FIELD = new Map(CURSOR_EXEC_VARIANTS.map((variant) => [variant.requestField, variant]));
41
51
  const BY_REQUEST_NAME = new Map(CURSOR_EXEC_VARIANTS.map((variant) => [variant.requestName, variant]));
@@ -53,3 +63,8 @@ export function describeCursorExecVariant(field) {
53
63
  return `unknown request field #${field}`;
54
64
  return `${variant.requestName} (request field #${variant.requestField}, expected result ${variant.resultName} field #${variant.resultField}, handling=${variant.handling})`;
55
65
  }
66
+ /**
67
+ * `ForceBackgroundShellResult` / `ForceBackgroundSubagentResult` status.
68
+ * Cursor CLI `ForceBackgroundStatus`: 0 unspecified, 1 ok, 2 error.
69
+ */
70
+ export const FORCE_BACKGROUND_STATUS_ERROR = 2;
@@ -0,0 +1,33 @@
1
+ /** aiserver.v1.GetDiffRequest.OutputFormat */
2
+ export declare const GIT_DIFF_FORMAT_UNSPECIFIED = 0;
3
+ export declare const GIT_DIFF_FORMAT_NAME_STATUS = 1;
4
+ export declare const GIT_DIFF_FORMAT_NAME_STATUS_AND_NUMSTAT = 2;
5
+ export declare const GIT_DIFF_FORMAT_FILE_DIFFS = 3;
6
+ export declare const GIT_DIFF_FORMAT_DIFFS_WITH_BEFORE_AND_AFTER = 4;
7
+ export type GitDiffChunk = {
8
+ content: string;
9
+ lines: string[];
10
+ old_start: number;
11
+ old_lines: number;
12
+ new_start: number;
13
+ new_lines: number;
14
+ };
15
+ export type GitFileDiff = {
16
+ from: string;
17
+ to: string;
18
+ chunks: GitDiffChunk[];
19
+ added: number;
20
+ removed: number;
21
+ };
22
+ /** Parse `git diff --no-color` into aiserver.v1.FileDiff rows (CLI `Oc` / `FR`). */
23
+ export declare function parseUnifiedDiff(text: string): GitFileDiff[];
24
+ export declare function executeGitDiff(request: Record<string, unknown>, workspaceRoot: string): Promise<Record<string, unknown>>;
25
+ /**
26
+ * Answer exec #44. GetDiffResponse has no error oneof — failures use
27
+ * exec_client_control_message.throw, matching Cursor CLI `class Dc`.
28
+ */
29
+ export declare function buildGitDiffExecMessages(input: {
30
+ execId: number;
31
+ request: Record<string, unknown>;
32
+ workspaceRoot: string;
33
+ }): Promise<Uint8Array[]>;