@xiaoso/dsh-tool-plus 0.1.16 → 0.1.17-beta.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.
Files changed (109) hide show
  1. package/lib/{abort-DZ54CaKU.mjs → abort-BTblp0Rt.mjs} +2 -2
  2. package/lib/{alibaba-coding-plan-DODpkcqi.mjs → alibaba-coding-plan-t3R4YKCB.mjs} +3 -3
  3. package/lib/{alibaba-token-plan-C6lpKCda.mjs → alibaba-token-plan-CO4djG1y.mjs} +4 -4
  4. package/lib/{alibaba-token-plan-CHGyWOfX.mjs → alibaba-token-plan-CUjYJX0-.mjs} +1 -1
  5. package/lib/{anthropic-Dv_GGX6g.mjs → anthropic-nf7w6so1.mjs} +48 -21
  6. package/lib/{api-key-validation-MnMf_ihX.mjs → api-key-validation-C_tZ7Y9a.mjs} +2 -2
  7. package/lib/behavior-BwvTUtag.mjs +111 -0
  8. package/lib/{browser-render-BZXt548F.mjs → browser-render-BQaEVK_n.mjs} +4 -4
  9. package/lib/{reference-CtvM4zLU.mjs → bundled-references-CrQ4dQcT.mjs} +57 -4
  10. package/lib/cascade-BWjRTD1q.mjs +30265 -0
  11. package/lib/{cline-pass-BT9CBEOj.mjs → cline-pass-Bjm-O8GG.mjs} +2 -2
  12. package/lib/cloudflare-CKXRJ0l9.mjs +37 -0
  13. package/lib/{cloudflare-ai-gateway-DReN6ohJ.mjs → cloudflare-ai-gateway-BpnBSjwV.mjs} +8 -4
  14. package/lib/cloudflare-workers-ai-odj9XswL.mjs +32 -0
  15. package/lib/collapse-_nkb2dn7.mjs +1945 -0
  16. package/lib/{coreweave-C82HrLpb.mjs → coreweave-CGDZOD8-.mjs} +1 -1
  17. package/lib/{coreweave-C68UgRQO.mjs → coreweave-DwffcgW5.mjs} +4 -4
  18. package/lib/cursor-BgOuV7Qf.mjs +794 -0
  19. package/lib/{cursor-_gHHq-Dq.mjs → cursor-CF1iHHbv.mjs} +68 -19
  20. package/lib/{cursor-proto-Cr_WEMuv.mjs → cursor-proto-BuNTjX3L.mjs} +823 -23
  21. package/lib/{devin-QpfTSl9H.mjs → devin-7OEeEb80.mjs} +112 -31
  22. package/lib/{devin-proto-DYhp4Ty6.mjs → devin-proto-C-QreeAv.mjs} +71 -5
  23. package/lib/{dom-DG3sPESc.mjs → dom-BmbZv4v9.mjs} +598 -182
  24. package/lib/{error-O63pC8zg.mjs → error-8LVo7tug.mjs} +725 -104
  25. package/lib/factory-droid-ChhmsGWb.mjs +71 -0
  26. package/lib/{fs-promises-shim-B2YFWkcd.mjs → fs-promises-shim-CANAxFWh.mjs} +10 -1
  27. package/lib/{github-copilot-BMEcw8N1.mjs → github-copilot-QwevdT30.mjs} +4 -4
  28. package/lib/{github-copilot-headers-DnjGEbko.mjs → github-copilot-headers-CAUZ0rUD.mjs} +138 -26
  29. package/lib/{gitlab-duo-DnHqJjFY.mjs → gitlab-duo-DigV7k_o.mjs} +2 -2
  30. package/lib/{google-antigravity-B1mSckrK.mjs → google-antigravity-D-oe0r8A.mjs} +6 -7
  31. package/lib/{google-gemini-cli-DJwThCCn.mjs → google-gemini-cli-CQBHgGJT.mjs} +5 -6
  32. package/lib/{google-oauth-shared-BctLpVUN.mjs → google-oauth-shared-CvgVIUwJ.mjs} +2 -2
  33. package/lib/google-validation-CdvRj4ec.mjs +126 -0
  34. package/lib/id-EJwVLDHE.mjs +2548 -0
  35. package/lib/index.mjs +248 -258
  36. package/lib/{kilo-BEgEwMJQ.mjs → kilo-5r6OffCa.mjs} +2 -2
  37. package/lib/{kimi-T9NldEIJ.mjs → kimi-DOOtbXmm.mjs} +9 -10
  38. package/lib/{launch-BIWNyrBB.mjs → launch-DkAjib7h.mjs} +1 -1
  39. package/lib/{markit-Dvp2JPdT.mjs → markit-CCcyeAds.mjs} +359 -140
  40. package/lib/{models-DnXMYGcJ.mjs → models-DCpDT_im.mjs} +199203 -112275
  41. package/lib/{muse-code-CNvqqAp6.mjs → muse-code-CYf8zj_B.mjs} +5 -5
  42. package/lib/{openai-codex-D5-CyTxa.mjs → openai-codex-Cj-TvSFh.mjs} +13 -10
  43. package/lib/{perplexity-A2PEoGaC.mjs → perplexity-Dh_Qgsmx.mjs} +68 -25
  44. package/lib/{pkce-BYjmugmw.mjs → pkce-DO-jBJLV.mjs} +1 -1
  45. package/lib/pricing-DusBEhwm.mjs +593 -0
  46. package/lib/{readability-uGhqb4GC.mjs → readability-XHK0o3bv.mjs} +287 -27
  47. package/lib/{scrapers-C1ujjV8k.mjs → scrapers-B6PADOVm.mjs} +13 -13
  48. package/lib/slugify-ynYWBIA5.mjs +11 -0
  49. package/lib/snowflake-B_jap6lz.mjs +666 -0
  50. package/lib/snowflake-Cu1ZyOp6.mjs +151 -0
  51. package/lib/{src-AELUUb5U.mjs → src-BhinCSpi.mjs} +1172 -458
  52. package/lib/{stream-AVOoe5Hj.mjs → stream-D2ivg3GH.mjs} +3740 -5311
  53. package/lib/{turndown-D6fof1OX.mjs → turndown-Vya8SavL.mjs} +82 -30
  54. package/lib/types/tools/hashline/native/index.d.ts +2 -2
  55. package/lib/types/tools/hashline/native/policy.d.ts +19 -21
  56. package/lib/types/tools/omp/session/agent-storage.d.ts +3 -3
  57. package/lib/types/tools/shared/bun-named-shim.d.ts +5 -0
  58. package/lib/{types-BLrLohwK.mjs → types-tH1v9uRm.mjs} +2 -2
  59. package/lib/{utils-cZX3f_Ls.mjs → utils-C_Ncya1r.mjs} +1737 -509
  60. package/lib/utils-D2tK38xq.mjs +34006 -0
  61. package/lib/{utils-I4I8azHN.mjs → utils-DrDwmNtu.mjs} +8 -3
  62. package/lib/{xai-oauth-CbeldUSV.mjs → xai-oauth-DLLzX6my.mjs} +4 -4
  63. package/lib/{xiaomi-Dl3VSs04.mjs → xiaomi-CqK-KivK.mjs} +2 -2
  64. package/lib/{zai-FyjGSypr.mjs → zai-DoJ2xPIH.mjs} +2 -2
  65. package/lib/{zip-DRrycScC.mjs → zip-DG1mUV6X.mjs} +1 -1
  66. package/package.json +8 -8
  67. package/lib/amazon-bedrock-Dx0rr9dY.mjs +0 -996
  68. package/lib/anthropic-ClCw0PV_.mjs +0 -3624
  69. package/lib/anthropic-wire-lbGAFzYb.mjs +0 -927
  70. package/lib/azure-openai-responses-WE_bgFWp.mjs +0 -285
  71. package/lib/bundled-references-DY3EJxZV.mjs +0 -56
  72. package/lib/cloudflare-ai-gateway-CrMa97Ap.mjs +0 -44
  73. package/lib/collapse-CkMNRNr7.mjs +0 -2304
  74. package/lib/cursor-C6jaIJDg.mjs +0 -283
  75. package/lib/cursor-D-XZYDf_.mjs +0 -4544
  76. package/lib/deterministic-id-CUEIMSqR.mjs +0 -17
  77. package/lib/devin-ExGIghmx.mjs +0 -69
  78. package/lib/devin-u0ljw5P6.mjs +0 -628
  79. package/lib/effort-BdtQMN-_.mjs +0 -23
  80. package/lib/event-stream-BKEtb45Y.mjs +0 -167
  81. package/lib/gemini-headers-Co9cVvex.mjs +0 -54
  82. package/lib/google-auth-qAN-6DMW.mjs +0 -232
  83. package/lib/google-gemini-cli-B2Aw1S5w.mjs +0 -844
  84. package/lib/google-lTxuq28n.mjs +0 -38
  85. package/lib/google-shared-Y01j9xOr.mjs +0 -856
  86. package/lib/google-validation-BEkNs-Bx.mjs +0 -18
  87. package/lib/google-vertex-DcM-FRC5.mjs +0 -99
  88. package/lib/hosts-00rjRzUD.mjs +0 -203
  89. package/lib/id-AL5obQzR.mjs +0 -71
  90. package/lib/idle-iterator-D0ZXEcUq.mjs +0 -366
  91. package/lib/ollama-6R0oXZks.mjs +0 -574
  92. package/lib/openai-codex-responses-C8bnBWOe.mjs +0 -3232
  93. package/lib/openai-completions-DNr3mUsn.mjs +0 -1430
  94. package/lib/openai-configuration-update-xMYUjaK_.mjs +0 -150
  95. package/lib/openai-reasoning-fallback-Ju-5pSR8.mjs +0 -380
  96. package/lib/openai-responses-CjpafAEr.mjs +0 -861
  97. package/lib/protobuf-BMgDYu_U.mjs +0 -717
  98. package/lib/provider-response-SqKSkkUc.mjs +0 -22
  99. package/lib/proxy-DxvCQOCA.mjs +0 -230
  100. package/lib/request-debug-BwPOqN_j.mjs +0 -183
  101. package/lib/sanitize-text-BCf5KmCf.mjs +0 -33
  102. package/lib/sse-debug-B-tkLCPW.mjs +0 -11
  103. package/lib/stream-markup-healing-CN3RsqG9.mjs +0 -267
  104. package/lib/strict-tool-validation-CBTz1dAA.mjs +0 -105
  105. package/lib/taxonomy-DSIM99rS.mjs +0 -15392
  106. package/lib/thinking-ngMwf_1Y.mjs +0 -336
  107. package/lib/type-guards-CttAANEx.mjs +0 -11
  108. package/lib/utils-CE3DSebh.mjs +0 -11763
  109. package/lib/vision-guard-BCpXWUj3.mjs +0 -77
@@ -0,0 +1,794 @@
1
+ import { installBunShim as __installBunShim } from "./bun-shim.mjs";
2
+ __installBunShim();
3
+ import "./src-BhinCSpi.mjs";
4
+ import { c as compareRevision, u as parseRevision } from "./cascade-BWjRTD1q.mjs";
5
+ import { t as slugify } from "./slugify-ynYWBIA5.mjs";
6
+ import { L as THINKING_EFFORTS, o as resolveCatalogAxes, s as resolveModelPolicy } from "./id-EJwVLDHE.mjs";
7
+ import { i as datedCost, l as classifyModel, s as parseDatedCostCards } from "./pricing-DusBEhwm.mjs";
8
+ import { i as create, l as toBinary, r as reviewedVariantFamilyId, s as fromBinary, t as collapseVariants } from "./collapse-_nkb2dn7.mjs";
9
+ import { r as toModelSpec } from "./bundled-references-CrQ4dQcT.mjs";
10
+ import { r as getBundledModels } from "./models-DCpDT_im.mjs";
11
+ import { Ar as CURSOR_DEFAULT_BASE_URL, Dr as CURSOR_AVAILABLE_MODELS_PATH, Fr as cursorClientHeaders, Mr as CURSOR_GET_USABLE_MODELS_PATH, ct as GetUsableModelsRequestSchema, jr as CURSOR_GET_DEFAULT_MODEL_PATH, lt as GetUsableModelsResponseSchema, m as AvailableModelsResponseSchema, ot as GetDefaultModelForCliRequestSchema, p as AvailableModelsRequestSchema, st as GetDefaultModelForCliResponseSchema } from "./cursor-proto-BuNTjX3L.mjs";
12
+ import { type } from "@oh-my-pi/omptype";
13
+ import * as http2$1 from "node:http2";
14
+ //#region node_modules/.pnpm/@oh-my-pi+pi-catalog@18.12.6/node_modules/@oh-my-pi/pi-catalog/src/discovery/cursor.ts
15
+ const DEFAULT_CONTEXT_WINDOW = 2e5;
16
+ const DEFAULT_MAX_TOKENS = 64e3;
17
+ /**
18
+ * `GetUsableModels` carries no context-window field, so the 1M ceiling is
19
+ * recovered from the signals Cursor does send:
20
+ * - display-name labels ("Opus 5 1M", "GPT-5.5 1M High") across families,
21
+ * - natively 1M families Cursor serves unlabeled (Kimi K3, GLM 5.2+),
22
+ * - the max-mode flag on Claude/Gemini ids, whose max-mode ceiling is 1M.
23
+ */
24
+ const CURSOR_1M_CONTEXT_WINDOW = 1e6;
25
+ const CURSOR_1M_NAME_PATTERN = /\b1m\b/i;
26
+ const OptionalDisplayNameSchema = type("unknown").pipe((raw) => typeof raw === "string" ? raw : void 0);
27
+ const CursorAliasesSchema = type("unknown").pipe((raw) => {
28
+ if (Array.isArray(raw)) return raw.filter((alias) => typeof alias === "string");
29
+ return [];
30
+ });
31
+ const CursorModelDetailsSchema = type({
32
+ modelId: "string",
33
+ displayName: OptionalDisplayNameSchema.default(void 0),
34
+ displayNameShort: OptionalDisplayNameSchema.default(void 0),
35
+ displayModelId: OptionalDisplayNameSchema.default(void 0),
36
+ aliases: CursorAliasesSchema.default(() => []),
37
+ "thinkingDetails?": "unknown",
38
+ maxMode: "boolean = false"
39
+ });
40
+ const CursorModelsInnerSchema = type("unknown[]");
41
+ const ResilientCursorModelsSchema = type("unknown").pipe((raw) => {
42
+ const out = CursorModelsInnerSchema(raw);
43
+ return out instanceof type.errors ? [] : out;
44
+ });
45
+ const CursorDecodedResponseSchema = type({ models: ResilientCursorModelsSchema.default(() => []) });
46
+ /**
47
+ * Joins Cursor's account-scoped model RPCs:
48
+ * - `AvailableModels` supplies rich base models, parameter axes, variants,
49
+ * capabilities, aliases, and cost-multiplier metadata.
50
+ * - `GetUsableModels` supplies the exact legacy wire slugs the account can run.
51
+ * - `GetDefaultModelForCli` identifies the account's current default.
52
+ *
53
+ * Per-token rates come from the reviewed `providers/cursor.kdl` rate card and
54
+ * the bundled rows; discovery only scales them by a variant's declared multiplier.
55
+ *
56
+ * Returns `null` unless at least one model-list RPC (`GetUsableModels` or
57
+ * `AvailableModels`) decoded. A successful empty catalog returns `[]`,
58
+ * preserving model-manager retry and fallback semantics.
59
+ */
60
+ async function fetchCursorUsableModels(options) {
61
+ const timeoutMs = options.timeoutMs ?? 5e3;
62
+ const baseUrl = (options.baseUrl ?? "https://api2.cursor.sh").replace(/\/+$/, "");
63
+ const usableRequest = create(GetUsableModelsRequestSchema, { customModelIds: normalizeCustomModelIds(options.customModelIds) });
64
+ const availableRequest = create(AvailableModelsRequestSchema, {
65
+ includeLongContextModels: true,
66
+ useModelParameters: true,
67
+ doNotUseMarkdown: true,
68
+ useCloudAgentEffortModes: true
69
+ });
70
+ const defaultRequest = create(GetDefaultModelForCliRequestSchema, {});
71
+ const [usablePayload, availablePayload, defaultPayload] = await Promise.all([
72
+ fetchCursorUnary(baseUrl, CURSOR_GET_USABLE_MODELS_PATH, toBinary(GetUsableModelsRequestSchema, usableRequest), options, timeoutMs),
73
+ fetchCursorUnary(baseUrl, CURSOR_AVAILABLE_MODELS_PATH, toBinary(AvailableModelsRequestSchema, availableRequest), options, Math.min(timeoutMs, 2e3)),
74
+ fetchCursorUnary(baseUrl, CURSOR_GET_DEFAULT_MODEL_PATH, toBinary(GetDefaultModelForCliRequestSchema, defaultRequest), options, timeoutMs)
75
+ ]);
76
+ const usable = decodeUnary(GetUsableModelsResponseSchema, usablePayload);
77
+ const available = decodeUnary(AvailableModelsResponseSchema, availablePayload);
78
+ if (usable === null && available === null) return null;
79
+ const defaultModel = decodeUnary(GetDefaultModelForCliResponseSchema, defaultPayload)?.model;
80
+ const parsedUsable = CursorDecodedResponseSchema(usable);
81
+ const references = createCursorReferenceMap();
82
+ const legacyModels = parsedUsable instanceof type.errors ? [] : normalizeCursorModels(parsedUsable.models, options.baseUrl, references);
83
+ const usableModelIds = usable === null ? void 0 : new Set(legacyModels.map((model) => model.id));
84
+ if (!available || available.models.length === 0) return legacyModels;
85
+ const richModels = normalizeRichCursorModels(available.models, usableModelIds, options.baseUrl, references, defaultModel?.modelId, defaultModel?.maxMode);
86
+ const richCatalogIds = collectRichCursorCatalogIds(available.models);
87
+ const representedIds = /* @__PURE__ */ new Set();
88
+ for (const model of richModels) {
89
+ representedIds.add(model.id);
90
+ representedIds.add(model.requestModelId ?? model.id);
91
+ for (const routeId of Object.keys(model.cursorModelRoutes ?? {})) representedIds.add(routeId);
92
+ }
93
+ for (const model of legacyModels) if (!representedIds.has(model.id) && !richCatalogIds.has(model.id)) richModels.push(model);
94
+ return richModels.sort((a, b) => a.id.localeCompare(b.id));
95
+ }
96
+ /**
97
+ * One unary RPC under a single deadline. HTTP/1.1 fetch goes first; the
98
+ * HTTP/2 Connect retry runs only after a transport-level failure (h2-only
99
+ * endpoints) and only with the remaining budget. An HTTP response of any
100
+ * status, or a deadline abort, is final.
101
+ */
102
+ async function fetchCursorUnary(baseUrl, path, body, options, timeoutMs) {
103
+ const deadline = Date.now() + timeoutMs;
104
+ const signal = AbortSignal.timeout(timeoutMs);
105
+ let response;
106
+ try {
107
+ response = await fetch(new URL(path, baseUrl), {
108
+ method: "POST",
109
+ headers: {
110
+ ...cursorClientHeaders(options.apiKey, { clientVersion: options.clientVersion }),
111
+ "connect-protocol-version": "1",
112
+ "x-request-id": crypto.randomUUID()
113
+ },
114
+ body,
115
+ signal
116
+ });
117
+ } catch {
118
+ const remainingMs = deadline - Date.now();
119
+ if (signal.aborted || remainingMs <= 0) return null;
120
+ return fetchViaHttp2(baseUrl, path, body, options, remainingMs);
121
+ }
122
+ if (!response.ok) return null;
123
+ try {
124
+ return new Uint8Array(await response.arrayBuffer());
125
+ } catch {
126
+ return null;
127
+ }
128
+ }
129
+ /** HTTP/2 transport used by Cursor's unary protobuf RPCs. */
130
+ async function fetchViaHttp2(baseUrl, path, body, options, timeoutMs) {
131
+ const { promise, resolve } = Promise.withResolvers();
132
+ const client = http2$1.connect(baseUrl);
133
+ let settled = false;
134
+ const finish = (value) => {
135
+ if (settled) return;
136
+ settled = true;
137
+ clearTimeout(timer);
138
+ client.close();
139
+ resolve(value);
140
+ };
141
+ const timer = setTimeout(() => {
142
+ client.destroy();
143
+ finish(null);
144
+ }, timeoutMs);
145
+ client.on("error", () => finish(null));
146
+ const req = client.request({
147
+ ":method": "POST",
148
+ ":path": path,
149
+ te: "trailers",
150
+ ...cursorClientHeaders(options.apiKey, { clientVersion: options.clientVersion }),
151
+ "connect-protocol-version": "1",
152
+ "x-request-id": crypto.randomUUID()
153
+ });
154
+ const chunks = [];
155
+ req.on("data", (chunk) => chunks.push(chunk));
156
+ req.on("end", () => finish(new Uint8Array(Buffer.concat(chunks))));
157
+ req.on("error", () => finish(null));
158
+ req.on("response", (headers) => {
159
+ const status = Number(headers[":status"] ?? 0);
160
+ if (status < 200 || status >= 300) finish(null);
161
+ });
162
+ req.end(Buffer.from(body));
163
+ return promise;
164
+ }
165
+ function normalizeCustomModelIds(customModelIds) {
166
+ if (!customModelIds) return [];
167
+ const normalized = /* @__PURE__ */ new Set();
168
+ for (const value of customModelIds) {
169
+ if (typeof value !== "string") continue;
170
+ const trimmed = value.trim();
171
+ if (!trimmed) continue;
172
+ normalized.add(trimmed);
173
+ }
174
+ return [...normalized];
175
+ }
176
+ function createCursorReferenceMap() {
177
+ const references = /* @__PURE__ */ new Map();
178
+ for (const model of getBundledModels("cursor")) references.set(model.id, toModelSpec(model));
179
+ return references;
180
+ }
181
+ function decodeUnary(schema, payload) {
182
+ if (payload === null) return null;
183
+ const body = decodeConnectUnaryBody(payload) ?? payload;
184
+ try {
185
+ return fromBinary(schema, body);
186
+ } catch {
187
+ return null;
188
+ }
189
+ }
190
+ function decodeConnectUnaryBody(payload) {
191
+ if (payload.length < 5) return null;
192
+ let offset = 0;
193
+ while (offset + 5 <= payload.length) {
194
+ const flags = payload[offset];
195
+ const messageLength = new DataView(payload.buffer, payload.byteOffset + offset, payload.byteLength - offset).getUint32(1, false);
196
+ const frameEnd = offset + 5 + messageLength;
197
+ if (frameEnd > payload.length) return null;
198
+ if ((flags & 1) !== 0) return null;
199
+ if (!((flags & 2) !== 0)) return payload.subarray(offset + 5, frameEnd);
200
+ offset = frameEnd;
201
+ }
202
+ return null;
203
+ }
204
+ function isCursorKimiK3(id) {
205
+ const identity = classifyModel("cursor", id, { lenient: true });
206
+ return identity.class === "kimi" && identity.family === "k3";
207
+ }
208
+ function isCursorVersionedGrok(id) {
209
+ const identity = classifyModel("cursor", id, { lenient: true });
210
+ if (identity.class !== "xai" || identity.revision === void 0) return false;
211
+ const revision = parseRevision(identity.revision);
212
+ const floor = parseRevision("4");
213
+ return revision !== void 0 && floor !== void 0 && compareRevision(revision, floor) >= 0;
214
+ }
215
+ function isCursorGlm52CodingModel(id) {
216
+ const identity = classifyModel("cursor", id, { lenient: true });
217
+ if (identity.class !== "glm" || identity.revision === void 0) return false;
218
+ if (identity.family !== void 0 && identity.family !== "air" && identity.family !== "turbo") return false;
219
+ const revision = parseRevision(identity.revision);
220
+ const floor = parseRevision("5.2");
221
+ return revision !== void 0 && floor !== void 0 && compareRevision(revision, floor) >= 0;
222
+ }
223
+ function collectRichCursorCatalogIds(models) {
224
+ const ids = /* @__PURE__ */ new Set();
225
+ for (const details of models) {
226
+ const baseId = details.name.trim();
227
+ if (baseId) ids.add(baseId);
228
+ for (const id of [...details.legacySlugs, ...details.idAliases]) if (id.trim()) ids.add(id.trim());
229
+ for (const [index, variant] of details.variants.entries()) {
230
+ const parameterSuffix = variant.parameterValues.map((parameter) => ({
231
+ id: parameter.id.trim(),
232
+ value: parameter.value.trim()
233
+ })).filter((parameter) => parameter.id.length > 0).map((parameter) => `${parameter.id}=${parameter.value}`).join(",");
234
+ const id = variant.legacySlug?.trim() || variant.variantStringRepresentation?.trim() || (parameterSuffix ? `${baseId}@${parameterSuffix}` : `${baseId}@variant-${index + 1}`);
235
+ if (id) ids.add(id);
236
+ }
237
+ }
238
+ return ids;
239
+ }
240
+ const CURSOR_REASONING_PARAMETER_IDS = /* @__PURE__ */ new Set([
241
+ "effort",
242
+ "reasoning",
243
+ "reasoning_effort",
244
+ "thinking",
245
+ "thinking_effort"
246
+ ]);
247
+ /** The declared definition value a variant parameter selects, when Cursor defines one. */
248
+ function cursorParameterValue(details, parameter) {
249
+ const definition = details.parameterDefinitions.find((candidate) => candidate.id === parameter.id);
250
+ if (!definition) return void 0;
251
+ const booleanValue = definition.parameterType?.booleanParameter?.values.find((candidate) => candidate.value === parameter.value);
252
+ const enumValue = definition.parameterType?.enumParameter?.values.find((candidate) => candidate.value === parameter.value);
253
+ const selected = booleanValue ?? enumValue;
254
+ if (!selected) return void 0;
255
+ return {
256
+ increasesModelCost: selected.increasesModelCost,
257
+ markdownTooltip: enumValue?.markdownTooltip,
258
+ definitionTooltip: definition.markdownTooltip
259
+ };
260
+ }
261
+ function cursorVariantCostMultiplier(details, entry) {
262
+ let multiplier = 1;
263
+ for (const parameter of entry.parameters) {
264
+ const selected = cursorParameterValue(details, parameter);
265
+ if (selected?.increasesModelCost !== true) continue;
266
+ for (const description of [selected.markdownTooltip, selected.definitionTooltip]) {
267
+ const match = description ? /\b(\d+(?:\.\d+)?)\s*[x×](?![a-z])/i.exec(description) : void 0;
268
+ if (!match?.[1]) continue;
269
+ const parameterMultiplier = Number(match[1]);
270
+ if (Number.isFinite(parameterMultiplier) && parameterMultiplier > 0) {
271
+ multiplier *= parameterMultiplier;
272
+ break;
273
+ }
274
+ }
275
+ }
276
+ return multiplier;
277
+ }
278
+ function scaleTokenCost(cost, multiplier) {
279
+ if (multiplier === 1) return cost;
280
+ const scale = (card) => ({
281
+ ...card,
282
+ input: card.input * multiplier,
283
+ output: card.output * multiplier,
284
+ cacheRead: card.cacheRead * multiplier,
285
+ cacheWrite: card.cacheWrite * multiplier
286
+ });
287
+ const effectiveRates = cost.timeBased?.effectiveRates;
288
+ return {
289
+ ...scale({
290
+ input: cost.input,
291
+ output: cost.output,
292
+ cacheRead: cost.cacheRead,
293
+ cacheWrite: cost.cacheWrite
294
+ }),
295
+ ...cost.timeBased && { timeBased: {
296
+ ...cost.timeBased,
297
+ ...effectiveRates && { effectiveRates: effectiveRates.map(scale) }
298
+ } }
299
+ };
300
+ }
301
+ /**
302
+ * Price a rich variant from the reviewed `providers/cursor.kdl` rate card,
303
+ * which keys on Cursor wire slugs (`claude-opus-4-8-high`, `gpt-5.4`). Cursor
304
+ * bills per price class — the values of the model's cost-bearing parameters
305
+ * (`fast`, `context`) — not per effort or thinking toggle, so every slug in
306
+ * the variant's own class shares its price. The lane id leads the own-class
307
+ * slugs: `buildModel` later resolves the same card by lane id, so a lane-keyed
308
+ * `cost-patch` must win here too or the two would disagree. Multiplier rule:
309
+ * - `cost-patch` on an own-class slug is that class's reviewed price and is
310
+ * final, even for fast classes (`claude-opus-4-7-high-fast` $30/$150);
311
+ * - `cost-fallback` rows, and any base-class slug or the model name, carry the
312
+ * base card, scaled by the declared multiplier (`cursorVariantCostMultiplier`).
313
+ */
314
+ function resolveRichCursorCardCost(details, entry, laneId) {
315
+ const costAxes = details.parameterDefinitions.filter((definition) => [...definition.parameterType?.booleanParameter?.values ?? [], ...definition.parameterType?.enumParameter?.values ?? []].some((value) => value.increasesModelCost === true)).map((definition) => definition.id);
316
+ const priceClass = (parameters) => costAxes.map((id) => parameters.find((parameter) => parameter.id === id)?.value ?? "").join("\0");
317
+ const entryClass = priceClass(entry.parameters);
318
+ const ownSlugs = [laneId, entry.id];
319
+ const baseSlugs = [details.name.trim()];
320
+ for (const variant of details.variants) {
321
+ const slug = variant.legacySlug?.trim();
322
+ if (!slug) continue;
323
+ const parameters = variant.parameterValues.map((parameter) => ({
324
+ id: parameter.id.trim(),
325
+ value: parameter.value.trim()
326
+ }));
327
+ if (priceClass(parameters) === entryClass) ownSlugs.push(slug);
328
+ else if (!parameters.some((parameter) => cursorParameterValue(details, parameter)?.increasesModelCost === true)) baseSlugs.push(slug);
329
+ }
330
+ let base;
331
+ for (const slug of ownSlugs) {
332
+ const card = resolveCursorCardAxes(slug);
333
+ if (card.patch) return card.patch;
334
+ base ??= card.fallback;
335
+ }
336
+ for (const slug of baseSlugs) {
337
+ if (base) break;
338
+ const card = resolveCursorCardAxes(slug);
339
+ base = card.patch ?? card.fallback;
340
+ }
341
+ return base && scaleTokenCost(base, cursorVariantCostMultiplier(details, entry));
342
+ }
343
+ function resolveCursorCardAxes(slug) {
344
+ const axes = resolveCatalogAxes({
345
+ id: slug,
346
+ name: slug,
347
+ api: "cursor-agent",
348
+ provider: "cursor",
349
+ baseUrl: CURSOR_DEFAULT_BASE_URL,
350
+ reasoning: false,
351
+ input: ["text"],
352
+ cost: {
353
+ input: 0,
354
+ output: 0,
355
+ cacheRead: 0,
356
+ cacheWrite: 0
357
+ },
358
+ contextWindow: DEFAULT_CONTEXT_WINDOW,
359
+ maxTokens: DEFAULT_MAX_TOKENS
360
+ });
361
+ const card = (payload, axis) => {
362
+ if (typeof payload !== "object" || payload === null) return void 0;
363
+ const field = (key) => {
364
+ const value = Reflect.get(payload, key);
365
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
366
+ };
367
+ const input = field("input");
368
+ const output = field("output");
369
+ if (input === void 0 || output === void 0) return void 0;
370
+ return datedCost({
371
+ input,
372
+ output,
373
+ cacheRead: field("cacheRead") ?? 0,
374
+ cacheWrite: field("cacheWrite") ?? 0
375
+ }, parseDatedCostCards(payload, axis));
376
+ };
377
+ return {
378
+ patch: card(axes.costPatch, "cost-patch"),
379
+ fallback: card(axes.costFallback, "cost-fallback")
380
+ };
381
+ }
382
+ function normalizeRichCursorModels(models, usableModelIds, baseUrlOverride, references, defaultModelId, defaultMaxMode) {
383
+ const normalized = [];
384
+ for (const details of models) {
385
+ const baseId = details.name.trim();
386
+ if (!baseId || details.supportsAgent === false || details.isChatOnly === true) continue;
387
+ if (details.requiresDataRetention === true) continue;
388
+ const variants = normalizeRichCursorVariants(details, baseId, usableModelIds);
389
+ if (variants.length === 0) continue;
390
+ const defaultRank = (entry) => entry.variant?.isDefaultNonMaxConfig === true ? 0 : entry.variant?.isDefaultMaxConfig === true ? 1 : 2;
391
+ const parameterDefaults = /* @__PURE__ */ new Map();
392
+ for (const entry of variants.toSorted((left, right) => defaultRank(left) - defaultRank(right))) for (const parameter of cursorLaneParameters(entry.parameters)) if (!parameterDefaults.has(parameter.id)) parameterDefaults.set(parameter.id, parameter.value);
393
+ const lanes = /* @__PURE__ */ new Map();
394
+ for (const entry of variants) {
395
+ const dimensions = cursorLaneParameters(entry.parameters);
396
+ const key = JSON.stringify([dimensions, entry.variant?.isMaxMode === true]);
397
+ const lane = lanes.get(key);
398
+ if (lane) lane.push(entry);
399
+ else lanes.set(key, [entry]);
400
+ }
401
+ const claimedLaneIds = /* @__PURE__ */ new Set();
402
+ for (const entries of lanes.values()) {
403
+ const first = entries[0];
404
+ if (!first) continue;
405
+ const richLaneId = cursorLaneId(baseId, cursorLaneParameters(first.parameters), parameterDefaults, first.variant?.isMaxMode === true, claimedLaneIds);
406
+ const laneName = cursorLaneName(details, richLaneId, baseId);
407
+ const laneId = reviewedCursorLaneId(entries, claimedLaneIds) ?? richLaneId;
408
+ normalized.push(...buildRichCursorLane(details, entries, laneId, laneName, baseUrlOverride, references, defaultModelId, defaultMaxMode));
409
+ }
410
+ }
411
+ return normalized.sort((a, b) => a.id.localeCompare(b.id));
412
+ }
413
+ function normalizeRichCursorVariants(details, baseId, usableModelIds) {
414
+ if (details.variants.length === 0) {
415
+ const candidates = [
416
+ baseId,
417
+ ...details.legacySlugs,
418
+ ...details.idAliases
419
+ ];
420
+ if (usableModelIds !== void 0 && !candidates.some((id) => usableModelIds.has(id))) return [];
421
+ return [{
422
+ id: baseId,
423
+ parameters: [],
424
+ variant: void 0,
425
+ effort: void 0
426
+ }];
427
+ }
428
+ const variants = [];
429
+ const seenRoutes = /* @__PURE__ */ new Set();
430
+ for (const [index, variant] of details.variants.entries()) {
431
+ if (isRichVariantBlocked(details, variant)) continue;
432
+ const parameters = variant.parameterValues.map((parameter) => ({
433
+ id: parameter.id.trim(),
434
+ value: parameter.value.trim()
435
+ })).filter((parameter) => parameter.id.length > 0);
436
+ const parameterSuffix = parameters.map((parameter) => `${parameter.id}=${parameter.value}`).join(",");
437
+ const id = variant.legacySlug?.trim() || variant.variantStringRepresentation?.trim() || (parameterSuffix ? `${baseId}@${parameterSuffix}` : `${baseId}@variant-${index + 1}`);
438
+ const entitlementIds = [
439
+ variant.legacySlug?.trim(),
440
+ id,
441
+ baseId,
442
+ ...details.idAliases
443
+ ].filter((candidate) => Boolean(candidate));
444
+ if (usableModelIds !== void 0 && !entitlementIds.some((candidate) => usableModelIds.has(candidate))) continue;
445
+ const routeSignature = `${parameterSuffix}\u0000${variant.isMaxMode === true ? "max" : "standard"}`;
446
+ if (seenRoutes.has(routeSignature)) continue;
447
+ seenRoutes.add(routeSignature);
448
+ variants.push({
449
+ id,
450
+ parameters,
451
+ variant,
452
+ effort: cursorVariantEffort(parameters)
453
+ });
454
+ }
455
+ return variants;
456
+ }
457
+ function cursorVariantEffort(parameters) {
458
+ const thinking = parameters.find((parameter) => parameter.id === "thinking")?.value.toLowerCase();
459
+ if (thinking === "false" || thinking === "off" || thinking === "none") return "off";
460
+ const rawEffort = parameters.find((parameter) => [
461
+ "reasoning",
462
+ "reasoning_effort",
463
+ "thinking_effort",
464
+ "effort"
465
+ ].includes(parameter.id))?.value;
466
+ if (rawEffort === void 0) return void 0;
467
+ const normalized = rawEffort.toLowerCase().replace(/[_\s]+/g, "-");
468
+ if (normalized === "none" || normalized === "off" || normalized === "disabled") return "off";
469
+ if (normalized === "minimal" || normalized === "low" || normalized === "medium" || normalized === "high" || normalized === "xhigh" || normalized === "max") return normalized;
470
+ if (normalized === "extra-high") return "xhigh";
471
+ }
472
+ function cursorLaneParameters(parameters) {
473
+ return parameters.filter((parameter) => !CURSOR_REASONING_PARAMETER_IDS.has(parameter.id));
474
+ }
475
+ function cursorLaneId(baseId, parameters, defaults, isMaxMode, claimed) {
476
+ const suffixes = [];
477
+ for (const parameter of parameters) {
478
+ const id = slugify(parameter.id);
479
+ const value = slugify(parameter.value);
480
+ if (!id || !value || parameter.value === "false") continue;
481
+ if (parameter.value === "true") suffixes.push(id);
482
+ else if (parameter.value === defaults.get(parameter.id)) continue;
483
+ else if (parameter.id === "context") suffixes.push(value);
484
+ else suffixes.push(`${id}-${value}`);
485
+ }
486
+ const root = [baseId, ...suffixes].join("-");
487
+ let candidate = root;
488
+ if (claimed.has(candidate)) candidate = `${root}-${isMaxMode ? "max-mode" : "standard"}`;
489
+ let duplicate = 2;
490
+ while (claimed.has(candidate)) {
491
+ candidate = `${root}-${duplicate}`;
492
+ duplicate += 1;
493
+ }
494
+ claimed.add(candidate);
495
+ return candidate;
496
+ }
497
+ /**
498
+ * The reviewed collapse family owning a lane's legacy slugs, when exactly one
499
+ * family claims any of them (slugs no family lists, e.g. a newly added tier,
500
+ * stay in the lane). `AvailableModels` names Grok 4.5/4.6 `grok-4.6` while its
501
+ * slugs are `cursor-grok-4.6-*`; reusing the family id keeps the lane id equal
502
+ * to the `GetUsableModels`-only fallback and the bundled catalog, so a selector
503
+ * survives whichever RPC answered.
504
+ */
505
+ function reviewedCursorLaneId(entries, claimed) {
506
+ let familyId;
507
+ for (const entry of entries) {
508
+ const slug = entry.variant?.legacySlug?.trim();
509
+ const owner = slug ? reviewedVariantFamilyId("cursor", slug) : void 0;
510
+ if (owner === void 0) continue;
511
+ if (familyId !== void 0 && owner !== familyId) return void 0;
512
+ familyId = owner;
513
+ }
514
+ if (familyId === void 0 || claimed.has(familyId)) return void 0;
515
+ claimed.add(familyId);
516
+ return familyId;
517
+ }
518
+ function cursorLaneName(details, laneId, baseId) {
519
+ const baseName = details.clientDisplayName?.trim() || details.serverModelName?.trim() || details.name.trim() || baseId;
520
+ const suffix = laneId.slice(baseId.length).replace(/^-/, "").split("-").filter(Boolean).map((part) => part === "1m" ? "1M" : part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
521
+ return suffix ? `${baseName} ${suffix}` : baseName;
522
+ }
523
+ function buildRichCursorLane(details, entries, laneId, laneName, baseUrlOverride, references, defaultModelId, defaultMaxMode) {
524
+ const isThinkingToggle = (entry) => entry.effort === void 0 && entry.parameters.some((parameter) => parameter.id === "thinking" && parameter.value.toLowerCase() === "true");
525
+ const selectedByEffort = /* @__PURE__ */ new Map();
526
+ for (const entry of entries) {
527
+ const slot = entry.effort ?? (isThinkingToggle(entry) ? "thinking" : "fixed");
528
+ const current = selectedByEffort.get(slot);
529
+ if (current === void 0 || cursorVariantPreference(details, entry, defaultModelId, defaultMaxMode) > cursorVariantPreference(details, current, defaultModelId, defaultMaxMode)) selectedByEffort.set(slot, entry);
530
+ }
531
+ const selected = [...selectedByEffort.values()];
532
+ if (selected.length === 0) return [];
533
+ const routeKeys = /* @__PURE__ */ new Map();
534
+ const usedRouteKeys = /* @__PURE__ */ new Set();
535
+ const routes = {};
536
+ for (const entry of selected) {
537
+ let routeKey = entry.id;
538
+ if (usedRouteKeys.has(routeKey)) routeKey = entry.variant?.variantStringRepresentation?.trim() || `${entry.id}@${entry.parameters.map((parameter) => `${parameter.id}=${parameter.value}`).join(",")}`;
539
+ let duplicate = 2;
540
+ const root = routeKey;
541
+ while (usedRouteKeys.has(routeKey)) {
542
+ routeKey = `${root}#${duplicate}`;
543
+ duplicate += 1;
544
+ }
545
+ usedRouteKeys.add(routeKey);
546
+ routeKeys.set(entry, routeKey);
547
+ routes[routeKey] = {
548
+ modelId: details.name.trim(),
549
+ parameters: entry.parameters,
550
+ ...entry.variant?.isMaxMode === void 0 ? void 0 : { maxMode: entry.variant.isMaxMode }
551
+ };
552
+ }
553
+ const members = [];
554
+ const routing = {};
555
+ let defaultMember;
556
+ let thinkingToggleMember;
557
+ for (const entry of selected) {
558
+ const routeKey = routeKeys.get(entry);
559
+ if (!routeKey) continue;
560
+ const variant = entry.variant;
561
+ const reference = references.get(entry.id) ?? references.get(details.name.trim()) ?? details.legacySlugs.map((id) => references.get(id)).find((candidate) => candidate !== void 0);
562
+ const isMaxMode = variant?.isMaxMode ?? false;
563
+ const contextParameter = entry.parameters.find((parameter) => parameter.id === "context")?.value;
564
+ const discoveredContextWindow = parseCursorContextWindow(contextParameter) ?? (isMaxMode ? details.contextTokenLimitForMaxMode : details.contextTokenLimit) ?? details.autoContextExtendedMaxTokens ?? details.autoContextMaxTokens;
565
+ const fallbackContext = reference?.contextWindow ?? DEFAULT_CONTEXT_WINDOW;
566
+ const input = details.supportsImages === void 0 ? resolveCursorInput(entry.id, reference?.input) : details.supportsImages ? ["text", "image"] : ["text"];
567
+ const isProviderDefault = isCursorProviderDefault(details, entry, defaultModelId, defaultMaxMode);
568
+ const reasoning = entry.effort === "off" ? false : entry.effort !== void 0 || isThinkingToggle(entry) ? true : details.supportsThinking ?? reference?.reasoning ?? false;
569
+ const referenceCost = reference && (reference.cost.input !== 0 || reference.cost.output !== 0) ? reference === references.get(entry.id) ? reference.cost : scaleTokenCost(reference.cost, cursorVariantCostMultiplier(details, entry)) : void 0;
570
+ const cost = resolveRichCursorCardCost(details, entry, laneId) ?? referenceCost ?? {
571
+ input: 0,
572
+ output: 0,
573
+ cacheRead: 0,
574
+ cacheWrite: 0
575
+ };
576
+ members.push({
577
+ ...reference ?? {
578
+ id: routeKey,
579
+ name: laneName,
580
+ api: "cursor-agent",
581
+ provider: "cursor",
582
+ baseUrl: baseUrlOverride ?? "https://api2.cursor.sh",
583
+ reasoning,
584
+ input,
585
+ cost: {
586
+ input: 0,
587
+ output: 0,
588
+ cacheRead: 0,
589
+ cacheWrite: 0
590
+ },
591
+ contextWindow: fallbackContext,
592
+ maxTokens: DEFAULT_MAX_TOKENS
593
+ },
594
+ id: routeKey,
595
+ name: laneName,
596
+ baseUrl: baseUrlOverride ?? reference?.baseUrl ?? "https://api2.cursor.sh",
597
+ reasoning,
598
+ input,
599
+ cost,
600
+ supportsTools: details.supportsAgent ?? reference?.supportsTools,
601
+ contextWindow: discoveredContextWindow ?? fallbackContext,
602
+ cursorMaxMode: isMaxMode,
603
+ cursorModelParameters: entry.parameters,
604
+ cursorModelRoutes: routes,
605
+ cursorPrice: details.price,
606
+ cursorRequiresDataRetention: details.requiresDataRetention,
607
+ cursorSupportsAgent: details.supportsAgent,
608
+ cursorSupportsSandboxing: details.supportsSandboxing,
609
+ isRecommended: isProviderDefault || details.defaultOn,
610
+ description: variant?.tagline ?? details.tagline ?? cursorTooltipBlurb(variant?.tooltipData?.markdownContent) ?? cursorTooltipBlurb(details.tooltipData?.markdownContent) ?? reference?.description
611
+ });
612
+ if (entry.effort !== void 0) routing[entry.effort] = routeKey;
613
+ else if (isThinkingToggle(entry)) thinkingToggleMember = members.at(-1);
614
+ if (isProviderDefault) defaultMember = routeKey;
615
+ }
616
+ if (members.length === 0) return [];
617
+ const toggleEfforts = [];
618
+ if (thinkingToggleMember) {
619
+ const { compat: _compat, ...policySpec } = thinkingToggleMember;
620
+ const surfaceEfforts = thinkingToggleMember.thinking?.efforts.length ? thinkingToggleMember.thinking.efforts : resolveModelPolicy({
621
+ ...policySpec,
622
+ reasoning: true,
623
+ thinking: void 0
624
+ }).thinking?.efforts ?? [];
625
+ for (const effort of surfaceEfforts) {
626
+ if (routing[effort] !== void 0) continue;
627
+ routing[effort] = thinkingToggleMember.id;
628
+ toggleEfforts.push(effort);
629
+ }
630
+ }
631
+ defaultMember ??= selected.filter((entry) => entry.variant?.isDefaultNonMaxConfig === true || entry.variant?.isDefaultMaxConfig === true).map((entry) => routeKeys.get(entry)).find((routeKey) => routeKey !== void 0);
632
+ defaultMember ??= routing.off ?? members[0]?.id;
633
+ const efforts = THINKING_EFFORTS.filter((effort) => routing[effort] !== void 0);
634
+ const family = {
635
+ id: laneId,
636
+ name: laneName,
637
+ members: members.map((member) => member.id),
638
+ routing,
639
+ ...defaultMember === void 0 ? void 0 : { defaultMember },
640
+ ...efforts.length === 0 ? void 0 : { thinking: {
641
+ mode: "effort",
642
+ efforts,
643
+ ...routing.off === void 0 ? { requiresEffort: true } : {},
644
+ ...defaultMember === void 0 || toggleEfforts.length > 0 ? void 0 : { defaultLevel: efforts.find((effort) => routing[effort] === defaultMember) }
645
+ } }
646
+ };
647
+ return collapseVariants(members, { table: { families: [family] } });
648
+ }
649
+ /**
650
+ * The one-line model blurb from Cursor's IDE tooltip. The tooltip is HTML for
651
+ * Cursor's editor (`<br />` separators, a bold title, context/effort lines,
652
+ * styled warning spans); the model's prose sentence is the second segment.
653
+ * Context and effort are already separate model fields, so the rest is dropped.
654
+ */
655
+ function cursorTooltipBlurb(markdown) {
656
+ if (!markdown) return void 0;
657
+ const segments = markdown.split(/<br\s*\/?>/i).map((segment) => segment.replace(/<[^>]+>/g, "").replace(/[*_`]/g, "").trim()).filter((segment) => segment.length > 0);
658
+ return segments[1] ?? segments[0];
659
+ }
660
+ function cursorVariantPreference(details, entry, defaultModelId, defaultMaxMode) {
661
+ if (isCursorProviderDefault(details, entry, defaultModelId, defaultMaxMode)) return 3;
662
+ if (entry.variant?.isDefaultNonMaxConfig === true || entry.variant?.isDefaultMaxConfig === true) return 2;
663
+ return 1;
664
+ }
665
+ function isCursorProviderDefault(details, entry, defaultModelId, defaultMaxMode) {
666
+ const variant = entry.variant;
667
+ if (defaultModelId !== void 0) {
668
+ if (defaultModelId === entry.id) return defaultMaxMode === void 0 || defaultMaxMode === (variant?.isMaxMode ?? false);
669
+ if (defaultModelId !== details.name && !details.legacySlugs.includes(defaultModelId) && !details.idAliases.includes(defaultModelId)) return false;
670
+ if (variant === void 0) return true;
671
+ return defaultMaxMode === true ? variant.isDefaultMaxConfig === true : variant.isDefaultNonMaxConfig === true;
672
+ }
673
+ if (!details.defaultOn) return false;
674
+ return variant === void 0 || variant.isDefaultNonMaxConfig === true || variant.isDefaultMaxConfig === true;
675
+ }
676
+ function parseCursorContextWindow(value) {
677
+ if (!value) return void 0;
678
+ const match = /^(\d+(?:\.\d+)?)([km])?$/i.exec(value.trim());
679
+ if (!match?.[1]) return void 0;
680
+ const amount = Number(match[1]);
681
+ if (!Number.isFinite(amount) || amount <= 0) return void 0;
682
+ const unit = match[2]?.toLowerCase();
683
+ return Math.round(amount * (unit === "m" ? 1e6 : unit === "k" ? 1e3 : 1));
684
+ }
685
+ function isRichVariantBlocked(details, variant) {
686
+ for (const parameter of variant.parameterValues) {
687
+ const definition = details.parameterDefinitions.find((candidate) => candidate.id === parameter.id);
688
+ if ([...definition?.parameterType?.booleanParameter?.values ?? [], ...definition?.parameterType?.enumParameter?.values ?? []].find((candidate) => candidate.value === parameter.value)?.blockedByAdminAllowlist === true) return true;
689
+ }
690
+ return false;
691
+ }
692
+ function normalizeCursorModels(models, baseUrlOverride, references) {
693
+ if (!models || models.length === 0) return [];
694
+ const byId = /* @__PURE__ */ new Map();
695
+ for (const model of models) {
696
+ const normalized = normalizeCursorModel(model, baseUrlOverride, references);
697
+ if (!normalized) continue;
698
+ byId.set(normalized.id, normalized);
699
+ }
700
+ return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
701
+ }
702
+ function normalizeCursorModel(model, baseUrlOverride, references) {
703
+ const parsedModel = CursorModelDetailsSchema(model);
704
+ if (parsedModel instanceof type.errors) return null;
705
+ const details = parsedModel;
706
+ const id = details.modelId.trim();
707
+ if (!id) return null;
708
+ const name = pickModelDisplayName(details, id);
709
+ const reference = references.get(id);
710
+ const reasoning = isCursorKimiK3(id) || isCursorVersionedGrok(id) || Boolean(details.thinkingDetails) || reference?.reasoning === true;
711
+ if (reference) return {
712
+ ...reference,
713
+ id,
714
+ name,
715
+ baseUrl: baseUrlOverride ?? reference.baseUrl,
716
+ reasoning,
717
+ input: resolveCursorInput(id, reference.input),
718
+ contextWindow: resolveCursorContextWindow(details, id, reference.contextWindow),
719
+ cursorMaxMode: details.maxMode
720
+ };
721
+ return {
722
+ id,
723
+ name,
724
+ api: "cursor-agent",
725
+ provider: "cursor",
726
+ baseUrl: baseUrlOverride ?? "https://api2.cursor.sh",
727
+ reasoning,
728
+ input: resolveCursorInput(id),
729
+ cost: {
730
+ input: 0,
731
+ output: 0,
732
+ cacheRead: 0,
733
+ cacheWrite: 0
734
+ },
735
+ contextWindow: resolveCursorContextWindow(details, id, DEFAULT_CONTEXT_WINDOW),
736
+ maxTokens: DEFAULT_MAX_TOKENS,
737
+ cursorMaxMode: details.maxMode
738
+ };
739
+ }
740
+ /**
741
+ * Context window for a discovered Cursor model: the 1M ceiling when any 1M
742
+ * signal fires (never below a larger bundled reference), else the fallback.
743
+ */
744
+ function resolveCursorContextWindow(model, id, fallback) {
745
+ const labeled1M = CURSOR_1M_NAME_PATTERN.test(id) || [
746
+ model.displayName,
747
+ model.displayNameShort,
748
+ model.displayModelId,
749
+ ...model.aliases
750
+ ].some((candidate) => typeof candidate === "string" && CURSOR_1M_NAME_PATTERN.test(candidate));
751
+ const identity = classifyModel("cursor", id, { lenient: true });
752
+ const maxMode1M = model.maxMode && (identity.class === "anthropic" || identity.class === "gemini");
753
+ if (labeled1M || isCursorNative1MModelId(id) || maxMode1M) return Math.max(fallback ?? 0, CURSOR_1M_CONTEXT_WINDOW);
754
+ return fallback;
755
+ }
756
+ /**
757
+ * Natively 1M-context families Cursor serves without a "1M" label: GLM 5.2+
758
+ * base/air/turbo coding SKUs (structured family and revision gates exclude
759
+ * vision and sub-1M variants). K3 — including Cursor's bare `k3` alias — is
760
+ * rule-owned via `context-window-floor` in `providers/cursor.kdl`.
761
+ */
762
+ function isCursorNative1MModelId(id) {
763
+ return isCursorGlm52CodingModel(id);
764
+ }
765
+ function pickModelDisplayName(model, fallbackId) {
766
+ const candidates = [
767
+ model.displayName,
768
+ model.displayNameShort,
769
+ model.displayModelId,
770
+ ...model.aliases,
771
+ fallbackId
772
+ ];
773
+ for (const candidate of candidates) {
774
+ if (typeof candidate !== "string") continue;
775
+ const trimmed = candidate.trim();
776
+ if (trimmed) return trimmed;
777
+ }
778
+ return fallbackId;
779
+ }
780
+ /**
781
+ * Resolves input modalities from a bundled reference when available. The
782
+ * Cursor-verified families (K3, grok-4, composer-2.5) are rule-owned via
783
+ * `input-modalities` in `providers/cursor.kdl` and corrected at build time.
784
+ * Without a reference, families whose native catalogs are multimodal
785
+ * (anthropic, gemini, openai) fall back to id classification.
786
+ */
787
+ function resolveCursorInput(id, referenceInput) {
788
+ if (referenceInput) return referenceInput;
789
+ const identity = classifyModel("cursor", id, { lenient: true });
790
+ if (identity.class === "anthropic" || identity.class === "gemini" || identity.class === "openai") return ["text", "image"];
791
+ return ["text"];
792
+ }
793
+ //#endregion
794
+ export { fetchCursorUsableModels, resolveCursorInput };