cursor-opencode-provider 0.8.1 → 0.8.3
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/CHANGELOG.md +95 -0
- package/README.md +204 -37
- package/SECURITY.md +20 -8
- package/dist/activity.d.ts +73 -1
- package/dist/activity.js +231 -5
- package/dist/agent-url.js +37 -9
- package/dist/auth-renewal.d.ts +1 -1
- package/dist/auth-renewal.js +58 -26
- package/dist/billing.d.ts +105 -0
- package/dist/billing.js +212 -0
- package/dist/classic-tool-results.d.ts +1 -0
- package/dist/classic-tool-results.js +42 -0
- package/dist/context/build.d.ts +1 -0
- package/dist/context/build.js +32 -5
- package/dist/context/env.js +27 -3
- package/dist/context/epoch.js +9 -5
- package/dist/context/git.d.ts +5 -2
- package/dist/context/git.js +17 -7
- package/dist/context/paths.d.ts +1 -1
- package/dist/context/paths.js +7 -4
- package/dist/debug.js +13 -2
- package/dist/early-tool-rows.d.ts +60 -0
- package/dist/early-tool-rows.js +96 -0
- package/dist/errors.d.ts +49 -7
- package/dist/errors.js +163 -8
- package/dist/host-interrupt.d.ts +12 -0
- package/dist/host-interrupt.js +18 -0
- package/dist/host-steer.d.ts +71 -0
- package/dist/host-steer.js +232 -0
- package/dist/image-input.js +4 -3
- package/dist/index.d.ts +2 -2
- package/dist/language-model.d.ts +114 -23
- package/dist/language-model.js +2290 -469
- package/dist/lost-request.d.ts +39 -0
- package/dist/lost-request.js +111 -0
- package/dist/model-config.d.ts +3 -1
- package/dist/model-config.js +50 -29
- package/dist/models.d.ts +26 -3
- package/dist/models.js +101 -45
- package/dist/opencode2/tool-results.d.ts +18 -0
- package/dist/opencode2/tool-results.js +66 -0
- package/dist/plugin-opencode2.js +185 -25
- package/dist/plugin.js +28 -5
- package/dist/pricing-data.d.ts +26 -2
- package/dist/pricing-data.js +26 -2
- package/dist/pricing.d.ts +1 -1
- package/dist/pricing.js +7 -4
- package/dist/process-shared.d.ts +14 -0
- package/dist/process-shared.js +18 -0
- package/dist/protocol/apply-patch.d.ts +25 -13
- package/dist/protocol/apply-patch.js +79 -30
- package/dist/protocol/blob-store.d.ts +5 -1
- package/dist/protocol/blob-store.js +10 -6
- package/dist/protocol/client-version.js +6 -4
- package/dist/protocol/conversation-cache-header.d.ts +23 -0
- package/dist/protocol/conversation-cache-header.js +149 -0
- package/dist/protocol/conversation-persistence.d.ts +27 -1
- package/dist/protocol/conversation-persistence.js +628 -126
- package/dist/protocol/conversation-state.d.ts +8 -1
- package/dist/protocol/conversation-state.js +28 -7
- package/dist/protocol/host-notes.d.ts +31 -0
- package/dist/protocol/host-notes.js +85 -0
- package/dist/protocol/messages.js +91 -8
- package/dist/protocol/request.d.ts +35 -19
- package/dist/protocol/request.js +53 -28
- package/dist/protocol/sent-history-images.d.ts +49 -0
- package/dist/protocol/sent-history-images.js +102 -0
- package/dist/protocol/tool-call-bridge.d.ts +2 -0
- package/dist/protocol/tool-call-bridge.js +10 -0
- package/dist/protocol/tools.d.ts +74 -8
- package/dist/protocol/tools.js +431 -141
- package/dist/protocol/turn-provenance.d.ts +19 -4
- package/dist/protocol/turn-provenance.js +77 -20
- package/dist/replay-safety.d.ts +16 -4
- package/dist/replay-safety.js +34 -25
- package/dist/retry-budget.d.ts +21 -0
- package/dist/retry-budget.js +94 -0
- package/dist/runtime-files.d.ts +23 -0
- package/dist/runtime-files.js +581 -0
- package/dist/session.d.ts +94 -9
- package/dist/session.js +153 -34
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +1 -1
- package/dist/shell-timeout.d.ts +54 -10
- package/dist/shell-timeout.js +362 -92
- package/dist/stream-write.d.ts +20 -0
- package/dist/stream-write.js +87 -0
- package/dist/terminal-runner.d.ts +15 -0
- package/dist/terminal-runner.js +528 -0
- package/dist/transport/connect.d.ts +8 -1
- package/dist/transport/connect.js +103 -23
- package/dist/usage.d.ts +37 -17
- package/dist/usage.js +63 -29
- package/package.json +3 -2
- package/dist/protocol/thinking.d.ts +0 -15
- package/dist/protocol/thinking.js +0 -17
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type LostRequest = {
|
|
2
|
+
conversationId: string;
|
|
3
|
+
checkpointKey: string;
|
|
4
|
+
/** Everything Cursor has not checkpointed, as the last attempt sent it. */
|
|
5
|
+
text: string;
|
|
6
|
+
/** That attempt's own live text, before anything was prepended. */
|
|
7
|
+
latest: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function checkpointKey(bytes: Uint8Array | undefined): string;
|
|
10
|
+
/**
|
|
11
|
+
* Prepend a user message whose Run never received a newer checkpoint.
|
|
12
|
+
* A history rebuild already contains it, so that path drops the record.
|
|
13
|
+
* A retry is the previous attempt's live text again: it sends what that
|
|
14
|
+
* attempt sent. A host that sends the same note before every attempt must not
|
|
15
|
+
* pile it up. Any other text is a new request and is appended, even when it
|
|
16
|
+
* repeats an older one (red, blue, then red again).
|
|
17
|
+
*/
|
|
18
|
+
export declare function carryLostRequest(input: {
|
|
19
|
+
sessionKey: string;
|
|
20
|
+
conversationId: string;
|
|
21
|
+
checkpoint: Uint8Array | undefined;
|
|
22
|
+
text: string;
|
|
23
|
+
rebuildFromHistory: boolean;
|
|
24
|
+
}): string;
|
|
25
|
+
/** `text` is what this turn sends; `latest` is its own live text (defaults to `text`). */
|
|
26
|
+
export declare function rememberLostRequest(input: {
|
|
27
|
+
sessionKey: string;
|
|
28
|
+
conversationId: string;
|
|
29
|
+
checkpoint: Uint8Array | undefined;
|
|
30
|
+
text: string;
|
|
31
|
+
latest?: string;
|
|
32
|
+
}): void;
|
|
33
|
+
/** A checkpoint other than the one this turn was sent on means Cursor kept the turn. */
|
|
34
|
+
export declare function noteCheckpoint(sessionKey: string | undefined, bytes: Uint8Array): void;
|
|
35
|
+
export declare function hasLostRequest(sessionKey: string | undefined): boolean;
|
|
36
|
+
export declare function dropLostRequest(sessionKey: string | undefined): void;
|
|
37
|
+
export declare function serializeLostRequest(sessionKey: string | undefined): string | undefined;
|
|
38
|
+
export declare function restoreLostRequest(sessionKey: string, raw: string | undefined): void;
|
|
39
|
+
export declare function resetLostRequestsForTests(): void;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { processShared } from "./process-shared.js";
|
|
3
|
+
const MAX_RECORDS = 1_024;
|
|
4
|
+
const records = () => processShared("lost-request.v2", () => new Map());
|
|
5
|
+
export function checkpointKey(bytes) {
|
|
6
|
+
if (!bytes?.length)
|
|
7
|
+
return "none";
|
|
8
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Prepend a user message whose Run never received a newer checkpoint.
|
|
12
|
+
* A history rebuild already contains it, so that path drops the record.
|
|
13
|
+
* A retry is the previous attempt's live text again: it sends what that
|
|
14
|
+
* attempt sent. A host that sends the same note before every attempt must not
|
|
15
|
+
* pile it up. Any other text is a new request and is appended, even when it
|
|
16
|
+
* repeats an older one (red, blue, then red again).
|
|
17
|
+
*/
|
|
18
|
+
export function carryLostRequest(input) {
|
|
19
|
+
const map = records();
|
|
20
|
+
const record = map.get(input.sessionKey);
|
|
21
|
+
if (!record)
|
|
22
|
+
return input.text;
|
|
23
|
+
const key = checkpointKey(input.checkpoint);
|
|
24
|
+
if (input.rebuildFromHistory
|
|
25
|
+
|| record.conversationId !== input.conversationId
|
|
26
|
+
|| record.checkpointKey !== key) {
|
|
27
|
+
map.delete(input.sessionKey);
|
|
28
|
+
return input.text;
|
|
29
|
+
}
|
|
30
|
+
if (input.text === record.latest)
|
|
31
|
+
return record.text;
|
|
32
|
+
// The host already resends the lost text ahead of this one (OpenCode 1.x
|
|
33
|
+
// merges user messages left without a reply into one live turn).
|
|
34
|
+
if (input.text === record.text || input.text.startsWith(`${record.text}\n\n`))
|
|
35
|
+
return input.text;
|
|
36
|
+
map.delete(input.sessionKey);
|
|
37
|
+
return `${record.text}\n\n${input.text}`;
|
|
38
|
+
}
|
|
39
|
+
/** `text` is what this turn sends; `latest` is its own live text (defaults to `text`). */
|
|
40
|
+
export function rememberLostRequest(input) {
|
|
41
|
+
if (!input.sessionKey || !input.text || input.text === ".")
|
|
42
|
+
return;
|
|
43
|
+
setRecord(input.sessionKey, {
|
|
44
|
+
conversationId: input.conversationId,
|
|
45
|
+
checkpointKey: checkpointKey(input.checkpoint),
|
|
46
|
+
text: input.text,
|
|
47
|
+
latest: input.latest ?? input.text,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** A session that never sends another turn must not keep its record forever. */
|
|
51
|
+
function setRecord(sessionKey, record) {
|
|
52
|
+
const map = records();
|
|
53
|
+
map.delete(sessionKey);
|
|
54
|
+
map.set(sessionKey, record);
|
|
55
|
+
while (map.size > MAX_RECORDS) {
|
|
56
|
+
const oldest = map.keys().next().value;
|
|
57
|
+
if (oldest === undefined)
|
|
58
|
+
break;
|
|
59
|
+
map.delete(oldest);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** A checkpoint other than the one this turn was sent on means Cursor kept the turn. */
|
|
63
|
+
export function noteCheckpoint(sessionKey, bytes) {
|
|
64
|
+
if (!sessionKey)
|
|
65
|
+
return;
|
|
66
|
+
const map = records();
|
|
67
|
+
const record = map.get(sessionKey);
|
|
68
|
+
if (!record)
|
|
69
|
+
return;
|
|
70
|
+
if (checkpointKey(bytes) !== record.checkpointKey)
|
|
71
|
+
map.delete(sessionKey);
|
|
72
|
+
}
|
|
73
|
+
export function hasLostRequest(sessionKey) {
|
|
74
|
+
return !!sessionKey && records().has(sessionKey);
|
|
75
|
+
}
|
|
76
|
+
export function dropLostRequest(sessionKey) {
|
|
77
|
+
if (sessionKey)
|
|
78
|
+
records().delete(sessionKey);
|
|
79
|
+
}
|
|
80
|
+
export function serializeLostRequest(sessionKey) {
|
|
81
|
+
if (!sessionKey)
|
|
82
|
+
return undefined;
|
|
83
|
+
const record = records().get(sessionKey);
|
|
84
|
+
return record ? JSON.stringify(record) : undefined;
|
|
85
|
+
}
|
|
86
|
+
export function restoreLostRequest(sessionKey, raw) {
|
|
87
|
+
if (!raw || records().has(sessionKey))
|
|
88
|
+
return;
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = JSON.parse(raw);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (typeof parsed.conversationId !== "string"
|
|
97
|
+
|| typeof parsed.checkpointKey !== "string"
|
|
98
|
+
|| typeof parsed.text !== "string"
|
|
99
|
+
|| parsed.text.length === 0
|
|
100
|
+
|| (parsed.latest !== undefined && typeof parsed.latest !== "string"))
|
|
101
|
+
return;
|
|
102
|
+
setRecord(sessionKey, {
|
|
103
|
+
conversationId: parsed.conversationId,
|
|
104
|
+
checkpointKey: parsed.checkpointKey,
|
|
105
|
+
text: parsed.text,
|
|
106
|
+
latest: parsed.latest ?? parsed.text,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
export function resetLostRequestsForTests() {
|
|
110
|
+
records().clear();
|
|
111
|
+
}
|
package/dist/model-config.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ModelInfo, type ModelVariant } from "./models.js";
|
|
2
|
+
type ContextTier = "base" | "long" | number;
|
|
2
3
|
/**
|
|
3
4
|
* Display names shared by both a thinking and a non-thinking model. A thinking
|
|
4
5
|
* model with such a name needs a "Thinking" tag to disambiguate it from its
|
|
@@ -19,8 +20,9 @@ export declare function thinkingSuffixBaseNames(models: ModelInfo[]): Set<string
|
|
|
19
20
|
export declare function cursorModelFamily(id: string): string | undefined;
|
|
20
21
|
export declare function modelInfoToConfig(mi: ModelInfo, options?: {
|
|
21
22
|
thinkingSuffix?: boolean;
|
|
22
|
-
contextTier?:
|
|
23
|
+
contextTier?: ContextTier;
|
|
23
24
|
variants?: ModelVariant[];
|
|
24
25
|
fast?: boolean;
|
|
25
26
|
}): Record<string, any>;
|
|
26
27
|
export declare function modelsToConfig(models: ModelInfo[]): Record<string, any>;
|
|
28
|
+
export {};
|
package/dist/model-config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CURSOR_VARIANT_PARAMETERS_KEY, CURSOR_WIRE_MODEL_ID_KEY, parseCursorContextLimit, } from "./models.js";
|
|
1
|
+
import { CURSOR_VARIANT_PARAMETERS_KEY, CURSOR_WIRE_MODEL_ID_KEY, findDefaultVariant, parseCursorContextLimit, } from "./models.js";
|
|
2
2
|
import { applyCursorModelCost, hasCursorFastPricing } from "./pricing.js";
|
|
3
3
|
import { getDocumentedCursorModelContext, resolveCursorModelSupportsImages, } from "./model-metadata.js";
|
|
4
4
|
/**
|
|
@@ -112,8 +112,23 @@ function isLongContextVariant(v) {
|
|
|
112
112
|
function isFastVariant(v) {
|
|
113
113
|
return v.parameterValues.some((p) => p.id === "fast" && p.value === "true");
|
|
114
114
|
}
|
|
115
|
+
function variantContext(v) {
|
|
116
|
+
return parseCursorContextLimit(v.parameterValues.find((p) => p.id === "context")?.value);
|
|
117
|
+
}
|
|
118
|
+
function isSeparateContext(mi, v) {
|
|
119
|
+
const context = variantContext(v);
|
|
120
|
+
return isLongContextVariant(v) ||
|
|
121
|
+
(v.isMaxMode === true && context !== undefined && context !== mi.maxContext);
|
|
122
|
+
}
|
|
123
|
+
function contextSuffix(context) {
|
|
124
|
+
return context % 1_000_000 === 0 ? `${context / 1_000_000}m` : `${context / 1_000}k`;
|
|
125
|
+
}
|
|
115
126
|
function variantsForTier(mi, tier) {
|
|
116
|
-
return mi.variants.filter((v) =>
|
|
127
|
+
return mi.variants.filter((v) => tier === "base"
|
|
128
|
+
? !isSeparateContext(mi, v)
|
|
129
|
+
: tier === "long"
|
|
130
|
+
? isLongContextVariant(v)
|
|
131
|
+
: isSeparateContext(mi, v) && variantContext(v) === tier);
|
|
117
132
|
}
|
|
118
133
|
/**
|
|
119
134
|
* Display names shared by both a thinking and a non-thinking model. A thinking
|
|
@@ -171,6 +186,8 @@ export function modelInfoToConfig(mi, options = {}) {
|
|
|
171
186
|
name += " Fast";
|
|
172
187
|
if (contextTier === "long")
|
|
173
188
|
name += " 1M";
|
|
189
|
+
if (typeof contextTier === "number")
|
|
190
|
+
name += ` ${contextSuffix(contextTier).toUpperCase()}`;
|
|
174
191
|
// OpenCode's context limit is static per model entry, while Cursor's context
|
|
175
192
|
// tier is a variant parameter. Long-context choices are therefore emitted as
|
|
176
193
|
// separate OpenCode entries by modelsToConfig.
|
|
@@ -179,9 +196,12 @@ export function modelInfoToConfig(mi, options = {}) {
|
|
|
179
196
|
// live checkpoints report 256k for the base tier — the previous 200k
|
|
180
197
|
// fallback made the host context meter disagree with Cursor.
|
|
181
198
|
const fallbackContext = mi.id === "default" ? 256_000 : 200_000;
|
|
182
|
-
const context = contextTier === "
|
|
183
|
-
?
|
|
184
|
-
:
|
|
199
|
+
const context = typeof contextTier === "number"
|
|
200
|
+
? contextTier
|
|
201
|
+
: contextTier === "long"
|
|
202
|
+
? (variants.some(isLongContextVariant) ? 1_000_000
|
|
203
|
+
: (mi.maxContextForMaxMode ?? documentedContext?.maxContextForMaxMode ?? 1_000_000))
|
|
204
|
+
: (mi.maxContext ?? documentedContext?.maxContext ?? fallbackContext);
|
|
185
205
|
// OpenCode's overflow/compaction/UI use limit.context; generation and
|
|
186
206
|
// thinking budgets use limit.output. models.dev 1M peers advertise
|
|
187
207
|
// 64k–128k output — a tiny cap makes long-context sessions feel broken
|
|
@@ -212,10 +232,8 @@ export function modelInfoToConfig(mi, options = {}) {
|
|
|
212
232
|
const variantConfig = modelInfoVariants(mi, variants);
|
|
213
233
|
if (variantConfig)
|
|
214
234
|
config.variants = variantConfig;
|
|
215
|
-
if (contextTier
|
|
216
|
-
const defaultVariant = contextTier
|
|
217
|
-
? (variants.find((v) => v.isDefaultMax) ?? variants[0])
|
|
218
|
-
: (variants.find((v) => v.isDefaultNonMax) ?? variants[0]);
|
|
235
|
+
if (contextTier !== "base" || options.fast) {
|
|
236
|
+
const defaultVariant = findDefaultVariant(mi, variants, contextTier !== "base") ?? variants[0];
|
|
219
237
|
if (defaultVariant) {
|
|
220
238
|
config.options = {
|
|
221
239
|
[CURSOR_WIRE_MODEL_ID_KEY]: mi.id,
|
|
@@ -245,11 +263,9 @@ function uniqueCatalogId(usedIds, modelId, suffix) {
|
|
|
245
263
|
}
|
|
246
264
|
let n = 2;
|
|
247
265
|
while (true) {
|
|
248
|
-
const candidate = suffix
|
|
249
|
-
? `${modelId}-
|
|
250
|
-
: suffix
|
|
251
|
-
? `${modelId}-1m-${n}-fast`
|
|
252
|
-
: `${modelId}-1m-${n}`;
|
|
266
|
+
const candidate = suffix !== "-fast" && suffix.endsWith("-fast")
|
|
267
|
+
? `${modelId}${suffix.slice(0, -5)}-${n}-fast`
|
|
268
|
+
: `${modelId}${suffix}-${n}`;
|
|
253
269
|
if (!usedIds.has(candidate)) {
|
|
254
270
|
usedIds.add(candidate);
|
|
255
271
|
return candidate;
|
|
@@ -265,13 +281,28 @@ export function modelsToConfig(models) {
|
|
|
265
281
|
const thinkingSuffix = !!m.supportsThinking && ambiguous.has(baseName(m));
|
|
266
282
|
const splitFast = hasCursorFastPricing(m.id);
|
|
267
283
|
const groups = [];
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
284
|
+
const extraContexts = new Set(m.variants
|
|
285
|
+
.filter((v) => isSeparateContext(m, v) && !isLongContextVariant(v))
|
|
286
|
+
.map(variantContext)
|
|
287
|
+
.filter((context) => context !== undefined));
|
|
288
|
+
const tiers = ["base", "long", ...extraContexts];
|
|
289
|
+
for (const contextTier of tiers) {
|
|
290
|
+
const tierSuffix = contextTier === "base" ? ""
|
|
291
|
+
: contextTier === "long" ? "-1m" : `-${contextSuffix(contextTier)}`;
|
|
292
|
+
const suffixFor = (fast) => `${tierSuffix}${fast ? "-fast" : ""}`;
|
|
293
|
+
const tierVariants = new Set(variantsForTier(m, contextTier));
|
|
294
|
+
// Base/Fast ids and labels are persisted by the host. Keep their pre-split
|
|
295
|
+
// non-1M variants available while dedicated entries supply accurate limits.
|
|
296
|
+
const advertised = contextTier === "base"
|
|
297
|
+
? m.variants.filter((variant) => !isLongContextVariant(variant))
|
|
298
|
+
: [...tierVariants];
|
|
299
|
+
for (const group of speedGroups(advertised, splitFast)) {
|
|
300
|
+
const tierGroup = group.variants.filter((variant) => tierVariants.has(variant));
|
|
271
301
|
groups.push({
|
|
272
302
|
contextTier,
|
|
273
303
|
fast: group.fast,
|
|
274
|
-
variants: group.variants,
|
|
304
|
+
variants: tierGroup.length > 0 ? tierGroup : group.variants,
|
|
305
|
+
advertisedVariants: group.variants,
|
|
275
306
|
suffix: suffixFor(group.fast),
|
|
276
307
|
});
|
|
277
308
|
}
|
|
@@ -293,17 +324,7 @@ export function modelsToConfig(models) {
|
|
|
293
324
|
variants: group.variants,
|
|
294
325
|
fast: group.fast,
|
|
295
326
|
});
|
|
296
|
-
|
|
297
|
-
const defaultVariant = (group.contextTier === "long"
|
|
298
|
-
? group.variants.find((v) => v.isDefaultMax)
|
|
299
|
-
: group.variants.find((v) => v.isDefaultNonMax)) ?? group.variants[0];
|
|
300
|
-
if (defaultVariant) {
|
|
301
|
-
config.options = {
|
|
302
|
-
[CURSOR_WIRE_MODEL_ID_KEY]: m.id,
|
|
303
|
-
[CURSOR_VARIANT_PARAMETERS_KEY]: defaultVariant.parameterValues.map((p) => ({ ...p })),
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
}
|
|
327
|
+
config.variants = modelInfoVariants(m, group.advertisedVariants);
|
|
307
328
|
out[catalogId] = applyCursorModelCost(pricingId, config);
|
|
308
329
|
}
|
|
309
330
|
}
|
package/dist/models.d.ts
CHANGED
|
@@ -12,6 +12,11 @@ export type ModelVariant = {
|
|
|
12
12
|
displayName: string;
|
|
13
13
|
isDefaultNonMax: boolean;
|
|
14
14
|
isDefaultMax: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Cursor's `AvailableModelVariant.is_max_mode`: this tuple runs only with
|
|
17
|
+
* request-level max mode. Absent when discovery omits the marker.
|
|
18
|
+
*/
|
|
19
|
+
isMaxMode?: boolean;
|
|
15
20
|
};
|
|
16
21
|
export type ModelInfo = {
|
|
17
22
|
id: string;
|
|
@@ -24,6 +29,11 @@ export type ModelInfo = {
|
|
|
24
29
|
/** Context window when max-mode is on (proto field 16). */
|
|
25
30
|
maxContextForMaxMode?: number;
|
|
26
31
|
supportsMaxMode?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* `AvailableModel.supports_non_max_mode`; `false` means every variant runs
|
|
34
|
+
* in max mode. Absent when discovery omits it.
|
|
35
|
+
*/
|
|
36
|
+
supportsNonMaxMode?: boolean;
|
|
27
37
|
variants: ModelVariant[];
|
|
28
38
|
};
|
|
29
39
|
export type ModelCache = {
|
|
@@ -45,17 +55,30 @@ export declare function normalizeModelCache(value: unknown): ModelCache | null;
|
|
|
45
55
|
*/
|
|
46
56
|
export declare function extractCursorVariantParameters(providerOptions: Record<string, unknown> | undefined): ModelParameterValue[] | undefined;
|
|
47
57
|
export declare function resolveCursorWireModelId(providerOptions: Record<string, unknown> | undefined, fallback: string): string;
|
|
48
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* True when resolved params select the long-context (1m) tier. Fallback for
|
|
60
|
+
* variants without Cursor's `isMaxMode` marker.
|
|
61
|
+
*/
|
|
49
62
|
export declare function paramsImplyMaxMode(params: ModelParameterValue[]): boolean;
|
|
50
63
|
/**
|
|
51
|
-
* Resolve the request-level max_mode bit.
|
|
52
|
-
*
|
|
64
|
+
* Resolve the request-level max_mode bit. Like Cursor CLI, a cataloged variant
|
|
65
|
+
* requires max mode when Cursor marks it `isMaxMode` (Grok 4.7's 500k tier as
|
|
66
|
+
* well as 1m tiers) or the model does not support non-max mode. An explicit
|
|
67
|
+
* variant pick owns the context tier; otherwise a standalone maxMode hint may
|
|
68
|
+
* select max mode.
|
|
53
69
|
*/
|
|
54
70
|
export declare function resolveVariantMaxMode(params: ModelParameterValue[], opts?: {
|
|
55
71
|
picked?: ModelParameterValue[];
|
|
56
72
|
maxMode?: boolean;
|
|
73
|
+
model?: ModelInfo;
|
|
57
74
|
}): boolean;
|
|
58
75
|
export declare function parseCursorContextLimit(value: unknown): number | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* Find Cursor's declared default within a catalog subset, retaining its effort:
|
|
78
|
+
* the declared variant, else its speed twin, else its twin in another context
|
|
79
|
+
* tier (a 500k entry when the declared max default is 1m).
|
|
80
|
+
*/
|
|
81
|
+
export declare function findDefaultVariant(model: ModelInfo, candidates: ModelVariant[], maxMode: boolean): ModelVariant | undefined;
|
|
59
82
|
export declare function isCacheFresh(cache: ModelCache, ttlMs?: number): boolean;
|
|
60
83
|
export declare function cacheFilePath(cacheDir: string): string;
|
|
61
84
|
export declare function readCache(cacheDir: string): Promise<ModelCache | null>;
|
package/dist/models.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { MODEL_CACHE_FILE, MODEL_CACHE_SCHEMA_VERSION, MODEL_CACHE_TTL_MS } from "./shared.js";
|
|
2
2
|
import { unaryAvailableModels } from "./transport/connect.js";
|
|
3
|
-
import { buildRequestedModelParams } from "./protocol/thinking.js";
|
|
4
3
|
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
5
4
|
import path from "node:path";
|
|
6
5
|
/** Dedicated OpenCode provider-option key for a selected Cursor variant. */
|
|
@@ -59,11 +58,15 @@ export function normalizeModelParameterValues(value) {
|
|
|
59
58
|
if (!Array.isArray(value))
|
|
60
59
|
return null;
|
|
61
60
|
const parameters = [];
|
|
61
|
+
const ids = new Set();
|
|
62
62
|
for (const parameter of value) {
|
|
63
63
|
if (!isPlainRecord(parameter))
|
|
64
64
|
return null;
|
|
65
65
|
if (!isNonEmptyString(parameter.id) || typeof parameter.value !== "string")
|
|
66
66
|
return null;
|
|
67
|
+
if (ids.has(parameter.id))
|
|
68
|
+
return null;
|
|
69
|
+
ids.add(parameter.id);
|
|
67
70
|
parameters.push({ id: parameter.id, value: parameter.value });
|
|
68
71
|
}
|
|
69
72
|
return parameters;
|
|
@@ -78,10 +81,12 @@ function normalizeVariant(value, fallbackName) {
|
|
|
78
81
|
const displayName = optionalString(value, ["displayName", "display_name"]);
|
|
79
82
|
const isDefaultNonMax = optionalBoolean(value, ["isDefaultNonMax", "isDefaultNonMaxConfig", "is_default_non_max_config"]);
|
|
80
83
|
const isDefaultMax = optionalBoolean(value, ["isDefaultMax", "isDefaultMaxConfig", "is_default_max_config"]);
|
|
84
|
+
const isMaxMode = optionalBoolean(value, ["isMaxMode", "is_max_mode"]);
|
|
81
85
|
if (key === null ||
|
|
82
86
|
displayName === null ||
|
|
83
87
|
isDefaultNonMax === null ||
|
|
84
|
-
isDefaultMax === null
|
|
88
|
+
isDefaultMax === null ||
|
|
89
|
+
isMaxMode === null) {
|
|
85
90
|
return null;
|
|
86
91
|
}
|
|
87
92
|
if ((key !== undefined && !isNonEmptyString(key)) ||
|
|
@@ -94,6 +99,7 @@ function normalizeVariant(value, fallbackName) {
|
|
|
94
99
|
parameterValues: parameters,
|
|
95
100
|
isDefaultNonMax: isDefaultNonMax ?? false,
|
|
96
101
|
isDefaultMax: isDefaultMax ?? false,
|
|
102
|
+
...(isMaxMode === undefined ? {} : { isMaxMode }),
|
|
97
103
|
};
|
|
98
104
|
}
|
|
99
105
|
function normalizeModelInfo(value) {
|
|
@@ -111,6 +117,7 @@ function normalizeModelInfo(value) {
|
|
|
111
117
|
const supportsAgent = optionalBoolean(value, ["supportsAgent", "supports_agent"]);
|
|
112
118
|
const supportsImages = optionalBoolean(value, ["supportsImages", "supports_images"]);
|
|
113
119
|
const supportsMaxMode = optionalBoolean(value, ["supportsMaxMode", "supports_max_mode"]);
|
|
120
|
+
const supportsNonMaxMode = optionalBoolean(value, ["supportsNonMaxMode", "supports_non_max_mode"]);
|
|
114
121
|
const maxContext = optionalPositiveNumber(value, ["maxContext", "contextTokenLimit", "context_token_limit"]);
|
|
115
122
|
const maxContextForMaxMode = optionalPositiveNumber(value, ["maxContextForMaxMode", "contextTokenLimitForMaxMode", "context_token_limit_for_max_mode"]);
|
|
116
123
|
if (displayName === null ||
|
|
@@ -119,6 +126,7 @@ function normalizeModelInfo(value) {
|
|
|
119
126
|
supportsAgent === null ||
|
|
120
127
|
supportsImages === null ||
|
|
121
128
|
supportsMaxMode === null ||
|
|
129
|
+
supportsNonMaxMode === null ||
|
|
122
130
|
maxContext === null ||
|
|
123
131
|
maxContextForMaxMode === null) {
|
|
124
132
|
return null;
|
|
@@ -133,6 +141,7 @@ function normalizeModelInfo(value) {
|
|
|
133
141
|
...(maxContext === undefined ? {} : { maxContext }),
|
|
134
142
|
...(maxContextForMaxMode === undefined ? {} : { maxContextForMaxMode }),
|
|
135
143
|
...(supportsMaxMode === undefined ? {} : { supportsMaxMode }),
|
|
144
|
+
...(supportsNonMaxMode === undefined ? {} : { supportsNonMaxMode }),
|
|
136
145
|
variants: variants,
|
|
137
146
|
};
|
|
138
147
|
}
|
|
@@ -176,17 +185,37 @@ export function resolveCursorWireModelId(providerOptions, fallback) {
|
|
|
176
185
|
const value = providerOptions?.[CURSOR_WIRE_MODEL_ID_KEY];
|
|
177
186
|
return typeof value === "string" && value.trim() ? value : fallback;
|
|
178
187
|
}
|
|
179
|
-
/**
|
|
188
|
+
/**
|
|
189
|
+
* True when resolved params select the long-context (1m) tier. Fallback for
|
|
190
|
+
* variants without Cursor's `isMaxMode` marker.
|
|
191
|
+
*/
|
|
180
192
|
export function paramsImplyMaxMode(params) {
|
|
181
193
|
return params.some((parameter) => parameter.id === "context" &&
|
|
182
194
|
parseCursorContextLimit(parameter.value) === 1_000_000);
|
|
183
195
|
}
|
|
196
|
+
function sameParameterTuple(a, b) {
|
|
197
|
+
const byId = new Map(b.map((parameter) => [parameter.id, parameter.value]));
|
|
198
|
+
return (a.length === b.length &&
|
|
199
|
+
new Set(a.map((parameter) => parameter.id)).size === a.length &&
|
|
200
|
+
byId.size === b.length &&
|
|
201
|
+
a.every((parameter) => byId.get(parameter.id) === parameter.value));
|
|
202
|
+
}
|
|
203
|
+
/** Cursor's per-variant max-mode marker, else the 1m-context fallback. */
|
|
204
|
+
function variantIsMaxMode(v) {
|
|
205
|
+
return v.isMaxMode ?? paramsImplyMaxMode(v.parameterValues);
|
|
206
|
+
}
|
|
184
207
|
/**
|
|
185
|
-
* Resolve the request-level max_mode bit.
|
|
186
|
-
*
|
|
208
|
+
* Resolve the request-level max_mode bit. Like Cursor CLI, a cataloged variant
|
|
209
|
+
* requires max mode when Cursor marks it `isMaxMode` (Grok 4.7's 500k tier as
|
|
210
|
+
* well as 1m tiers) or the model does not support non-max mode. An explicit
|
|
211
|
+
* variant pick owns the context tier; otherwise a standalone maxMode hint may
|
|
212
|
+
* select max mode.
|
|
187
213
|
*/
|
|
188
214
|
export function resolveVariantMaxMode(params, opts = {}) {
|
|
189
|
-
|
|
215
|
+
if (opts.model?.supportsNonMaxMode === false)
|
|
216
|
+
return true;
|
|
217
|
+
const marked = opts.model?.variants.find((v) => sameParameterTuple(v.parameterValues, params))?.isMaxMode;
|
|
218
|
+
return (marked ?? paramsImplyMaxMode(params)) || (opts.picked === undefined && opts.maxMode === true);
|
|
190
219
|
}
|
|
191
220
|
// Cursor encodes a model's context window as a variant parameter `id: "context"`
|
|
192
221
|
// whose value is a tier string — the same 200k / 272k / 300k / 1m the IDE's
|
|
@@ -212,8 +241,28 @@ function variantContextTokens(v) {
|
|
|
212
241
|
const raw = v?.parameterValues.find((p) => p.id === "context")?.value;
|
|
213
242
|
return parseCursorContextLimit(raw);
|
|
214
243
|
}
|
|
215
|
-
|
|
216
|
-
|
|
244
|
+
/** Same parameters as `target` except the ignored ids. */
|
|
245
|
+
function isTwinOf(v, target, ignored) {
|
|
246
|
+
return (v.parameterValues.length === target.parameterValues.length &&
|
|
247
|
+
sameParameterTuple(v.parameterValues.filter((p) => !ignored.includes(p.id)), target.parameterValues.filter((p) => !ignored.includes(p.id))));
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Find Cursor's declared default within a catalog subset, retaining its effort:
|
|
251
|
+
* the declared variant, else its speed twin, else its twin in another context
|
|
252
|
+
* tier (a 500k entry when the declared max default is 1m).
|
|
253
|
+
*/
|
|
254
|
+
export function findDefaultVariant(model, candidates, maxMode) {
|
|
255
|
+
const isDeclared = (v) => maxMode ? v.isDefaultMax : v.isDefaultNonMax;
|
|
256
|
+
const declared = model.variants.find(isDeclared);
|
|
257
|
+
return candidates.find(isDeclared) ??
|
|
258
|
+
(declared === undefined
|
|
259
|
+
? undefined
|
|
260
|
+
: candidates.find((v) => isTwinOf(v, declared, ["fast"])) ??
|
|
261
|
+
candidates.find((v) => isTwinOf(v, declared, ["fast", "context"])));
|
|
262
|
+
}
|
|
263
|
+
/** Send an advertised tuple verbatim; never rewrite one of its values. */
|
|
264
|
+
function copyParameters(parameters) {
|
|
265
|
+
return parameters.map((parameter) => ({ ...parameter }));
|
|
217
266
|
}
|
|
218
267
|
function positiveNumber(value) {
|
|
219
268
|
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
@@ -286,27 +335,38 @@ export function mapAvailableModelsResponse(raw) {
|
|
|
286
335
|
if (!parameterValues) {
|
|
287
336
|
throw new Error(`AvailableModels returned invalid parameter values for ${name}`);
|
|
288
337
|
}
|
|
338
|
+
const isMaxMode = optionalBoolean(v, ["isMaxMode", "is_max_mode"]);
|
|
339
|
+
if (isMaxMode === null) {
|
|
340
|
+
throw new Error(`AvailableModels returned a non-boolean isMaxMode for ${name}`);
|
|
341
|
+
}
|
|
289
342
|
variants.push({
|
|
290
343
|
key: name,
|
|
291
344
|
parameterValues,
|
|
292
345
|
displayName: (v.displayName ?? v.display_name ?? name),
|
|
293
346
|
isDefaultNonMax: apiBoolean(v, ["isDefaultNonMaxConfig", "is_default_non_max_config"]),
|
|
294
347
|
isDefaultMax: apiBoolean(v, ["isDefaultMaxConfig", "is_default_max_config"]),
|
|
348
|
+
...(isMaxMode === undefined ? {} : { isMaxMode }),
|
|
295
349
|
});
|
|
296
350
|
}
|
|
297
351
|
// Derive the context window from the variant `context` param (primary
|
|
298
352
|
// source). Base tier ← default non-max variant; max tier ← default max
|
|
299
353
|
// variant. Fall back to the (often empty) proto fields, then undefined.
|
|
300
|
-
// Prefer a non-
|
|
354
|
+
// Prefer a non-max context when isDefaultNonMax is missing so a leading
|
|
301
355
|
// max-mode variant cannot inflate the base window.
|
|
302
356
|
const variantBaseContext = variantContextTokens(variants.find((v) => v.isDefaultNonMax)) ??
|
|
303
|
-
variantContextTokens(variants.find((v) => variantContextTokens(v) !==
|
|
357
|
+
variantContextTokens(variants.find((v) => !variantIsMaxMode(v) && variantContextTokens(v) !== undefined &&
|
|
358
|
+
variantContextTokens(v) !== 1_000_000)) ??
|
|
304
359
|
variantContextTokens(variants.find((v) => v.parameterValues.some((p) => p.id === "context")));
|
|
305
|
-
const variantMaxContext = variantContextTokens(variants.find((v) => v.isDefaultMax))
|
|
360
|
+
const variantMaxContext = variantContextTokens(variants.find((v) => v.isDefaultMax)) ??
|
|
361
|
+
variantContextTokens(variants.find((v) => variantIsMaxMode(v) && variantContextTokens(v) !== undefined));
|
|
306
362
|
const supportsImages = optionalBoolean(e, ["supportsImages", "supports_images"]);
|
|
307
363
|
if (supportsImages === null) {
|
|
308
364
|
throw new Error(`AvailableModels returned invalid supportsImages for ${name}`);
|
|
309
365
|
}
|
|
366
|
+
const supportsNonMaxMode = optionalBoolean(e, ["supportsNonMaxMode", "supports_non_max_mode"]);
|
|
367
|
+
if (supportsNonMaxMode === null) {
|
|
368
|
+
throw new Error(`AvailableModels returned invalid supportsNonMaxMode for ${name}`);
|
|
369
|
+
}
|
|
310
370
|
models.push({
|
|
311
371
|
id: name,
|
|
312
372
|
displayName: (e.clientDisplayName ?? e.client_display_name ?? name),
|
|
@@ -317,6 +377,7 @@ export function mapAvailableModelsResponse(raw) {
|
|
|
317
377
|
maxContextForMaxMode: variantMaxContext ??
|
|
318
378
|
positiveNumber(e.contextTokenLimitForMaxMode ?? e.context_token_limit_for_max_mode),
|
|
319
379
|
supportsMaxMode: apiBoolean(e, ["supportsMaxMode", "supports_max_mode"]),
|
|
380
|
+
...(supportsNonMaxMode === undefined ? {} : { supportsNonMaxMode }),
|
|
320
381
|
variants,
|
|
321
382
|
});
|
|
322
383
|
}
|
|
@@ -344,51 +405,46 @@ export function resolveVariantParameters(model, opts = {}) {
|
|
|
344
405
|
}
|
|
345
406
|
return opts.reasoningEffort ? [{ id: "effort", value: opts.reasoningEffort }] : [];
|
|
346
407
|
}
|
|
347
|
-
const effortOf = (v) => v.parameterValues.find((p) => p.id === "effort" || p.id === "reasoning")?.value;
|
|
408
|
+
const effortOf = (v) => v.parameterValues.find((p) => p.id === "effort" || p.id === "reasoning" || p.id === "reasoning_effort")?.value;
|
|
348
409
|
const isFast = (v) => v.parameterValues.find((p) => p.id === "fast")?.value === "true";
|
|
349
|
-
const
|
|
350
|
-
const wantMax = opts.maxMode ?? false;
|
|
351
|
-
// Non-fast pool for hint-based resolution only. Exact `picked` matching
|
|
352
|
-
// searches all variants so Fast selections are not silently dropped.
|
|
353
|
-
const pool = model.variants.filter((v) => !isFast(v));
|
|
354
|
-
const scoped = pool.length > 0 ? pool : model.variants;
|
|
410
|
+
const wantMax = (opts.maxMode ?? false) || model.supportsNonMaxMode === false;
|
|
355
411
|
// 1. Variant explicitly picked by opencode (verbatim — preserves context, fast, …).
|
|
356
412
|
if (picked !== undefined) {
|
|
357
|
-
const
|
|
358
|
-
const exact = model.variants.find((v) => v.parameterValues.length === picked.length &&
|
|
359
|
-
pickedById.size === picked.length &&
|
|
360
|
-
v.parameterValues.every((parameter) => pickedById.get(parameter.id) === parameter.value));
|
|
413
|
+
const exact = model.variants.find((v) => sameParameterTuple(v.parameterValues, picked));
|
|
361
414
|
if (exact) {
|
|
362
|
-
return
|
|
415
|
+
return copyParameters(exact.parameterValues);
|
|
363
416
|
}
|
|
364
417
|
throw new CursorVariantSelectionError(`is stale for model ${JSON.stringify(model.id)}: its exact parameter tuple is unavailable`);
|
|
365
418
|
}
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
|
|
369
|
-
const
|
|
370
|
-
|
|
419
|
+
// Choose the tier before preferring non-Fast variants. Otherwise a Fast-only
|
|
420
|
+
// max tier disappears, or a leading max variant steals an effort-only hint.
|
|
421
|
+
// isDefaultMax is a preference, not a requirement to run in max mode.
|
|
422
|
+
const tier = model.variants.filter((v) => variantIsMaxMode(v) === wantMax);
|
|
423
|
+
const candidates = tier.length > 0 ? tier : model.variants;
|
|
424
|
+
const nonFast = candidates.filter((v) => !isFast(v));
|
|
425
|
+
const scoped = nonFast.length > 0 ? nonFast : candidates;
|
|
426
|
+
// Cursor can declare a Fast variant as a default (Grok, GPT max defaults);
|
|
427
|
+
// the tier default retains the declared default's effort when its speed
|
|
428
|
+
// twin is needed (same parameters except `fast`).
|
|
429
|
+
const byDefault = (wantMax
|
|
430
|
+
? findDefaultVariant(model, scoped, true) ?? scoped.find(variantIsMaxMode)
|
|
431
|
+
: undefined) ?? findDefaultVariant(model, scoped, false) ?? scoped[0];
|
|
432
|
+
// 2. Effort hint (e.g. user passed reasoningEffort through the CLI). Like
|
|
433
|
+
// Cursor CLI, break ties toward the tier default so the hint changes only
|
|
434
|
+
// effort, not thinking or other parameters (Claude lists thinking=false first).
|
|
371
435
|
if (opts.reasoningEffort) {
|
|
372
|
-
const
|
|
436
|
+
const shared = (v) => v.parameterValues.filter((parameter) => byDefault.parameterValues.some((d) => d.id === parameter.id && d.value === parameter.value)).length;
|
|
437
|
+
const match = scoped
|
|
438
|
+
.filter((v) => effortOf(v) === opts.reasoningEffort)
|
|
439
|
+
.reduce((best, v) => best && shared(best) >= shared(v) ? best : v, undefined);
|
|
373
440
|
if (match) {
|
|
374
|
-
return
|
|
375
|
-
reasoningEffort: opts.reasoningEffort,
|
|
376
|
-
maxMode: wantMax,
|
|
377
|
-
});
|
|
441
|
+
return copyParameters(match.parameterValues);
|
|
378
442
|
}
|
|
379
443
|
}
|
|
380
|
-
// 3. Max-mode hint →
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
return buildRequestedModelParams(max.parameterValues, { maxMode: true });
|
|
385
|
-
}
|
|
386
|
-
// 4. Default non-max variant.
|
|
387
|
-
const byDefault = scoped.find((v) => v.isDefaultNonMax) ?? scoped[0];
|
|
388
|
-
return buildRequestedModelParams(byDefault.parameterValues, {
|
|
389
|
-
reasoningEffort: opts.reasoningEffort,
|
|
390
|
-
maxMode: false,
|
|
391
|
-
});
|
|
444
|
+
// 3. Max-mode hint → the default max variant (the max tier).
|
|
445
|
+
// 4. Otherwise the default non-max variant, else the first candidate.
|
|
446
|
+
// An unsupported effort must not synthesize a tuple Cursor never advertised.
|
|
447
|
+
return copyParameters(byDefault.parameterValues);
|
|
392
448
|
}
|
|
393
449
|
// ── Fetch + cache orchestration ──
|
|
394
450
|
export async function fetchModels(token, options = {}) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preserve native result status before OpenCode's AI SDK lowers errors to text.
|
|
3
|
+
* Upstream: https://github.com/anomalyco/opencode/blob/v2.0.27/packages/core/src/aisdk.ts#L652-L685
|
|
4
|
+
* Native part.providerMetadata becomes SDK part.providerOptions (same file, L717-L720).
|
|
5
|
+
*/
|
|
6
|
+
export type ToolRefusal = "permission_denied" | "rejected";
|
|
7
|
+
export type NativeToolFailure = {
|
|
8
|
+
type: string;
|
|
9
|
+
text: string;
|
|
10
|
+
refusal?: ToolRefusal;
|
|
11
|
+
};
|
|
12
|
+
export declare const TOOL_RESULT_STATUS_KEY = "opencodeToolResult";
|
|
13
|
+
export declare function nativeToolFailure(value: unknown, refusal?: ToolRefusal): NativeToolFailure;
|
|
14
|
+
/** The native tool hook preserves the underlying typed permission cause. */
|
|
15
|
+
export declare function nativeToolRefusal(error: unknown): ToolRefusal | undefined;
|
|
16
|
+
/** Metadata travels with each part, including history and independently loaded SDK copies. */
|
|
17
|
+
export declare function preserveNativeToolResults(messages: readonly unknown[] | undefined, refusals?: ReadonlyMap<string, ToolRefusal>): any[];
|
|
18
|
+
export declare function toolResultFailure(options: unknown, callID: string): NativeToolFailure | undefined;
|