@oh-my-pi/pi-catalog 18.4.4 → 18.4.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.
- package/CHANGELOG.md +31 -0
- package/dist/types/compat/auth-ids.d.ts +2 -2
- package/dist/types/compat/axes.d.ts +3 -1
- package/dist/types/compat/factory-droid.d.ts +65 -0
- package/dist/types/compat/provider-ids.d.ts +1 -1
- package/dist/types/compat/resolve.d.ts +8 -2
- package/dist/types/compat/types.d.ts +26 -1
- package/dist/types/discovery/cursor-proto.d.ts +284 -0
- package/dist/types/discovery/cursor.d.ts +12 -6
- package/dist/types/discovery/factory-droid.d.ts +28 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/discovery/protobuf.d.ts +3 -3
- package/dist/types/model-manager.d.ts +10 -0
- package/dist/types/models.d.ts +5 -1
- package/dist/types/provider-models/cache-provider-id.d.ts +2 -1
- package/dist/types/provider-models/descriptor-types.d.ts +2 -1
- package/dist/types/provider-models/openai-compat.d.ts +21 -0
- package/dist/types/provider-models/special.d.ts +2 -0
- package/dist/types/types.d.ts +100 -3
- package/dist/types/wire/cursor.d.ts +20 -0
- package/dist/types/wire/factory-droid.d.ts +45 -0
- package/package.json +4 -4
- package/src/build.ts +21 -0
- package/src/compat/auth-ids.ts +4 -0
- package/src/compat/axes.ts +113 -2
- package/src/compat/cascade.ts +8 -1
- package/src/compat/collapse.ts +3 -0
- package/src/compat/factory-droid.ts +170 -0
- package/src/compat/provider-ids.ts +2 -0
- package/src/compat/resolve.ts +47 -5
- package/src/compat/rules/README.md +18 -8
- package/src/compat/rules/auth/_order.kdl +2 -2
- package/src/compat/rules/auth/factory-droid.kdl +28 -0
- package/src/compat/rules/auth/helmcode.kdl +9 -0
- package/src/compat/rules/classes/anthropic.kdl +195 -80
- package/src/compat/rules/providers/commandcode.kdl +21 -5
- package/src/compat/rules/providers/cursor.kdl +29 -17
- package/src/compat/rules/providers/factory-droid.kdl +1166 -0
- package/src/compat/rules/providers/helmcode.kdl +120 -0
- package/src/compat/rules/runtime/behavior.kdl +65 -4
- package/src/compat/rules/taxonomy/_collapse.kdl +36 -14
- package/src/compat/rules.json +1 -1
- package/src/compat/types.ts +27 -1
- package/src/discovery/cursor-models.proto +261 -0
- package/src/discovery/cursor-proto.ts +471 -0
- package/src/discovery/cursor.ts +817 -88
- package/src/discovery/devin.ts +1 -3
- package/src/discovery/factory-droid.ts +296 -0
- package/src/discovery/index.ts +1 -0
- package/src/discovery/protobuf.ts +16 -7
- package/src/model-manager.ts +13 -0
- package/src/models.json +1 -1
- package/src/models.ts +13 -0
- package/src/provider-models/cache-provider-id.ts +37 -6
- package/src/provider-models/descriptor-types.ts +2 -1
- package/src/provider-models/descriptors.ts +4 -0
- package/src/provider-models/openai-compat.ts +83 -17
- package/src/provider-models/special.ts +26 -2
- package/src/types.ts +112 -1
- package/src/wire/cursor.ts +32 -0
- package/src/wire/factory-droid.ts +130 -0
package/src/models.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
KnownProvider,
|
|
7
7
|
Model,
|
|
8
8
|
ModelCost,
|
|
9
|
+
ModelPricingStatus,
|
|
9
10
|
TimeBasedCost,
|
|
10
11
|
TokenCost,
|
|
11
12
|
Usage,
|
|
@@ -44,6 +45,11 @@ function getProviderModels(provider: string): Map<string, Model<Api>> | undefine
|
|
|
44
45
|
|
|
45
46
|
export type GeneratedProvider = keyof typeof MODELS;
|
|
46
47
|
|
|
48
|
+
/** Whether `provider` has bundled rows in models.json (e.g. a provider id authored in KDL). */
|
|
49
|
+
export function isGeneratedProvider(provider: string): provider is GeneratedProvider {
|
|
50
|
+
return Object.hasOwn(MODELS, provider);
|
|
51
|
+
}
|
|
52
|
+
|
|
47
53
|
export function getBundledModel<TApi extends Api = Api>(provider: GeneratedProvider, modelId: string): Model<TApi> {
|
|
48
54
|
const providerModels = getProviderModels(provider);
|
|
49
55
|
return providerModels?.get(modelId) as Model<TApi>;
|
|
@@ -57,6 +63,13 @@ export function getBundledModels(provider: GeneratedProvider): Model<Api>[] {
|
|
|
57
63
|
const models = getProviderModels(provider);
|
|
58
64
|
return models ? (Array.from(models.values()) as Model<Api>[]) : [];
|
|
59
65
|
}
|
|
66
|
+
/** Resolve display semantics without mistaking an absent rate card for a free model. */
|
|
67
|
+
export function getModelPricingStatus(model: Pick<Model<Api>, "cost" | "pricingStatus">): ModelPricingStatus {
|
|
68
|
+
const cost = model.cost;
|
|
69
|
+
if (cost.input !== 0 || cost.output !== 0 || cost.cacheRead !== 0 || cost.cacheWrite !== 0) return "fixed";
|
|
70
|
+
return model.pricingStatus ?? "unknown";
|
|
71
|
+
}
|
|
72
|
+
|
|
60
73
|
function resolveTokenCost(cost: ModelCost, promptInputTokens: number, timestamp: number | undefined): TokenCost {
|
|
61
74
|
let rates: ModelCost | EffectiveTokenCost = cost;
|
|
62
75
|
let effectiveFrom = -Infinity;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { CHARM_HYPER_API_BASE_URL, normalizeCharmHyperBaseUrl } from "../wire/charm-hyper";
|
|
2
2
|
import { CODEX_CLIENT_VERSION } from "../wire/codex";
|
|
3
|
+
import { CURSOR_DEFAULT_BASE_URL } from "../wire/cursor";
|
|
4
|
+
import { type AccountScope, factoryDroidModelCacheProviderId } from "../wire/factory-droid";
|
|
3
5
|
import { PERSONAL_GITHUB_COPILOT_BASE_URL } from "../wire/github-copilot";
|
|
4
6
|
import {
|
|
5
7
|
SINGULARITYAPI_DEV_API_BASE_URL,
|
|
@@ -7,7 +9,7 @@ import {
|
|
|
7
9
|
normalizeSingularityApiBaseUrl,
|
|
8
10
|
} from "../wire/singularityapi";
|
|
9
11
|
|
|
10
|
-
export interface ModelCacheProviderIdOptions {
|
|
12
|
+
export interface ModelCacheProviderIdOptions extends AccountScope {
|
|
11
13
|
apiKey?: string;
|
|
12
14
|
baseUrl?: string;
|
|
13
15
|
}
|
|
@@ -17,6 +19,8 @@ const CREDENTIAL_SCOPED_MODEL_CACHE_PROVIDERS: Readonly<Record<string, true>> =
|
|
|
17
19
|
"opencode-zen": true,
|
|
18
20
|
"github-copilot": true,
|
|
19
21
|
"muse-code": true,
|
|
22
|
+
cursor: true,
|
|
23
|
+
"factory-droid": true,
|
|
20
24
|
// Both SingularityAPI rosters are issued per key, so the namespace must be
|
|
21
25
|
// resolved with the credential (`hydrateCredentialScopedModelCaches`) rather
|
|
22
26
|
// than from the synchronous, credential-less startup read.
|
|
@@ -66,6 +70,24 @@ export function resolveOllamaModelCacheProviderId(providerId: string, baseUrl?:
|
|
|
66
70
|
return `${providerId}:ollama-models-v1:${Bun.hash(endpoint).toString(36)}`;
|
|
67
71
|
}
|
|
68
72
|
|
|
73
|
+
/**
|
|
74
|
+
* The `sub` claim of a Cursor access token. Cursor access tokens are JWTs
|
|
75
|
+
* that `refreshCursorToken` rotates (new `exp`/`iat`) for the same account,
|
|
76
|
+
* so the subject is the stable account scope. Non-JWT keys return undefined.
|
|
77
|
+
*/
|
|
78
|
+
function cursorCredentialSubject(apiKey: string): string | undefined {
|
|
79
|
+
const parts = apiKey.split(".");
|
|
80
|
+
if (parts.length !== 3 || !parts[1]) return undefined;
|
|
81
|
+
try {
|
|
82
|
+
const payload: unknown = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
|
|
83
|
+
if (typeof payload !== "object" || payload === null) return undefined;
|
|
84
|
+
const subject = Reflect.get(payload, "sub");
|
|
85
|
+
return typeof subject === "string" && subject.length > 0 ? `sub:${subject}` : undefined;
|
|
86
|
+
} catch {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
69
91
|
/** Resolve the cache namespace used by a provider's model-manager options without constructing those options. */
|
|
70
92
|
export function resolveModelCacheProviderId(providerId: string, options: ModelCacheProviderIdOptions = {}): string {
|
|
71
93
|
switch (providerId) {
|
|
@@ -74,11 +96,18 @@ export function resolveModelCacheProviderId(providerId: string, options: ModelCa
|
|
|
74
96
|
return `${providerId}:${CODEX_CLIENT_VERSION}`;
|
|
75
97
|
case "ollama":
|
|
76
98
|
return resolveOllamaModelCacheProviderId(providerId, options.baseUrl);
|
|
77
|
-
case "cursor":
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
|
|
99
|
+
case "cursor": {
|
|
100
|
+
// Cursor catalogs are entitlement-, admin-policy-, and privacy-mode
|
|
101
|
+
// scoped. A credential switch must never reuse another account's
|
|
102
|
+
// authoritative model rows.
|
|
103
|
+
// v3 invalidates zero-price rows written before rich lanes were priced
|
|
104
|
+
// from the KDL rate card; v4 keys the scope on the token's stable
|
|
105
|
+
// account subject instead of the rotating JWT.
|
|
106
|
+
const baseUrl = (options.baseUrl ?? CURSOR_DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
107
|
+
const apiKey = options.apiKey ?? "";
|
|
108
|
+
const scope = `${cursorCredentialSubject(apiKey) ?? apiKey}\u0000${baseUrl}`;
|
|
109
|
+
return `cursor:rich-models-v4:${Bun.hash(scope).toString(36)}`;
|
|
110
|
+
}
|
|
82
111
|
case "charm-hyper": {
|
|
83
112
|
// Discovery is authoritative for this gateway, so a warm cache is served
|
|
84
113
|
// for its full TTL without re-probing: the namespace must follow the
|
|
@@ -167,6 +196,8 @@ export function resolveModelCacheProviderId(providerId: string, options: ModelCa
|
|
|
167
196
|
const scope = `${options.apiKey ?? ""}\u0000${baseUrl}`;
|
|
168
197
|
return `github-copilot:models-v2:${Bun.hash(scope).toString(36)}`;
|
|
169
198
|
}
|
|
199
|
+
case "factory-droid":
|
|
200
|
+
return factoryDroidModelCacheProviderId(options);
|
|
170
201
|
case "openrouter":
|
|
171
202
|
return "openrouter:pseudo-api";
|
|
172
203
|
case "vllm": {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { CompiledProviderDiscovery } from "../compat/types";
|
|
2
2
|
import type { ModelManagerOptions } from "../model-manager";
|
|
3
3
|
import type { Api, FetchImpl } from "../types";
|
|
4
|
+
import type { AccountScope } from "../wire/factory-droid";
|
|
4
5
|
|
|
5
6
|
/** Config passed to a provider's runtime model-manager factory. */
|
|
6
|
-
export type ModelManagerConfig = {
|
|
7
|
+
export type ModelManagerConfig = AccountScope & {
|
|
7
8
|
apiKey?: string;
|
|
8
9
|
baseUrl?: string;
|
|
9
10
|
fetch?: FetchImpl;
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
githubCopilotModelManagerOptions,
|
|
38
38
|
gmiCloudModelManagerOptions,
|
|
39
39
|
groqModelManagerOptions,
|
|
40
|
+
helmcodeModelManagerOptions,
|
|
40
41
|
huggingfaceModelManagerOptions,
|
|
41
42
|
kiloModelManagerOptions,
|
|
42
43
|
kimiCodeModelManagerOptions,
|
|
@@ -79,6 +80,7 @@ import {
|
|
|
79
80
|
import {
|
|
80
81
|
cursorModelManagerOptions,
|
|
81
82
|
devinModelManagerOptions,
|
|
83
|
+
factoryDroidModelManagerOptions,
|
|
82
84
|
gitLabDuoWorkflowModelManagerOptions,
|
|
83
85
|
localModelManagerOptions,
|
|
84
86
|
typesafeModelManagerOptions,
|
|
@@ -107,6 +109,7 @@ const MODEL_MANAGER_FACTORIES: Readonly<Partial<Record<KnownProvider, ModelManag
|
|
|
107
109
|
deepinfra: config => deepinfraModelManagerOptions(config),
|
|
108
110
|
deepseek: config => deepseekModelManagerOptions(config),
|
|
109
111
|
devin: config => devinModelManagerOptions(config),
|
|
112
|
+
"factory-droid": config => factoryDroidModelManagerOptions(config),
|
|
110
113
|
"cline-pass": config => clinePassModelManagerOptions(config),
|
|
111
114
|
firepass: config => firepassModelManagerOptions(config),
|
|
112
115
|
fireworks: config => fireworksModelManagerOptions(config),
|
|
@@ -116,6 +119,7 @@ const MODEL_MANAGER_FACTORIES: Readonly<Partial<Record<KnownProvider, ModelManag
|
|
|
116
119
|
google: config => googleModelManagerOptions(config),
|
|
117
120
|
"google-vertex": config => googleVertexModelManagerOptions(config),
|
|
118
121
|
groq: config => groqModelManagerOptions(config),
|
|
122
|
+
helmcode: config => helmcodeModelManagerOptions(config),
|
|
119
123
|
huggingface: config => huggingfaceModelManagerOptions(config),
|
|
120
124
|
kilo: config => kiloModelManagerOptions(config),
|
|
121
125
|
"kimi-code": config => kimiCodeModelManagerOptions(config),
|
|
@@ -1257,6 +1257,81 @@ export function huggingfaceModelManagerOptions(
|
|
|
1257
1257
|
return createSimpleOpenAICompletionsOptions("huggingface", "https://router.huggingface.co/v1", config);
|
|
1258
1258
|
}
|
|
1259
1259
|
|
|
1260
|
+
// ---------------------------------------------------------------------------
|
|
1261
|
+
// 4.5 Helmcode
|
|
1262
|
+
// ---------------------------------------------------------------------------
|
|
1263
|
+
|
|
1264
|
+
export interface HelmcodeModelManagerConfig {
|
|
1265
|
+
apiKey?: string;
|
|
1266
|
+
baseUrl?: string;
|
|
1267
|
+
fetch?: FetchImpl;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
/**
|
|
1271
|
+
* First-party hosts of the models Helmcode resells (helmcode.com/docs/models,
|
|
1272
|
+
* "Frontier models"). Resold ids resolve only against these rows: the global
|
|
1273
|
+
* bare-id index picks whichever gateway row wins a context/output tie, which
|
|
1274
|
+
* can carry a zero or marked-up price instead of the vendor list price.
|
|
1275
|
+
*/
|
|
1276
|
+
const HELMCODE_RESOLD_VENDORS = ["anthropic", "openai", "google"] as const satisfies readonly GeneratedProvider[];
|
|
1277
|
+
|
|
1278
|
+
function createHelmcodeVendorReferenceMap(): Map<string, ModelSpec<"openai-completions">> {
|
|
1279
|
+
const references = new Map<string, ModelSpec<"openai-completions">>();
|
|
1280
|
+
for (const vendor of HELMCODE_RESOLD_VENDORS) {
|
|
1281
|
+
for (const [id, reference] of createBundledReferenceMap<"openai-completions">(vendor)) {
|
|
1282
|
+
if (!references.has(id)) references.set(id, reference);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
return references;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* Helmcode model manager: OpenAI-compatible chat completions at
|
|
1290
|
+
* `api.helmcode.com/v1`. `/v1/models` also lists embedding, rerank, TTS, and
|
|
1291
|
+
* STT models; the exclusion policy lives in `runtime/behavior.kdl`
|
|
1292
|
+
* (`exclude-models provider="helmcode"`).
|
|
1293
|
+
*
|
|
1294
|
+
* `/v1/models` carries no capability data. Resold frontier ids (Claude, GPT,
|
|
1295
|
+
* Gemini) take only capability facts from the first-party vendor's bundled
|
|
1296
|
+
* row: reasoning, modalities, context window, output cap, and list price. The
|
|
1297
|
+
* rest of that row (thinking shape, compat, native web search, tool dialects,
|
|
1298
|
+
* cache semantics) describes the vendor's own API, not this chat-completions
|
|
1299
|
+
* proxy; the host's `reasoning_effort` ladders and cache-write pricing live in
|
|
1300
|
+
* `providers/helmcode.kdl`. Ids with no Helmcode or vendor row (e.g. a new
|
|
1301
|
+
* open-weight model) inherit nothing from other gateways.
|
|
1302
|
+
*/
|
|
1303
|
+
export function helmcodeModelManagerOptions(
|
|
1304
|
+
config?: HelmcodeModelManagerConfig,
|
|
1305
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
1306
|
+
let vendorReferences: Map<string, ModelSpec<"openai-completions">> | undefined;
|
|
1307
|
+
const resolveVendorReference = (id: string) => (vendorReferences ??= createHelmcodeVendorReferenceMap()).get(id);
|
|
1308
|
+
return createOpenAICompatibleModelManagerOptions({
|
|
1309
|
+
api: "openai-completions",
|
|
1310
|
+
providerId: "helmcode",
|
|
1311
|
+
defaultBaseUrl: "https://api.helmcode.com/v1",
|
|
1312
|
+
config,
|
|
1313
|
+
requireApiKey: true,
|
|
1314
|
+
filterModel: (_entry, model) => !isExcludedModel("helmcode", model.id),
|
|
1315
|
+
mapModel: (entry, defaults, helmcodeReference) => {
|
|
1316
|
+
if (helmcodeReference) return mapWithBundledReference(entry, defaults, helmcodeReference);
|
|
1317
|
+
const vendor = resolveVendorReference(defaults.id);
|
|
1318
|
+
if (!vendor) return mapWithBundledReference(entry, defaults, undefined);
|
|
1319
|
+
return {
|
|
1320
|
+
...defaults,
|
|
1321
|
+
name: toModelName(entry.name, vendor.name),
|
|
1322
|
+
reasoning: vendor.reasoning,
|
|
1323
|
+
input: vendor.input,
|
|
1324
|
+
cost: vendor.cost,
|
|
1325
|
+
contextWindow: toPositiveNumber(entry.context_length, vendor.contextWindow),
|
|
1326
|
+
maxTokens: toPositiveNumber(entry.max_completion_tokens, vendor.maxTokens),
|
|
1327
|
+
};
|
|
1328
|
+
},
|
|
1329
|
+
// Must live on the manager options, not only the KDL descriptor:
|
|
1330
|
+
// `createModelManager()` prunes the bundled slice from this flag.
|
|
1331
|
+
dynamicModelsAuthoritative: true,
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1260
1335
|
// ---------------------------------------------------------------------------
|
|
1261
1336
|
// 5. NVIDIA
|
|
1262
1337
|
// ---------------------------------------------------------------------------
|
|
@@ -3277,12 +3352,8 @@ export function openrouterModelManagerOptions(config?: OpenRouterModelManagerCon
|
|
|
3277
3352
|
cacheRead: parseFloat(String(pricing?.input_cache_read ?? "0")) * 1_000_000,
|
|
3278
3353
|
cacheWrite: parseFloat(String(pricing?.input_cache_write ?? "0")) * 1_000_000,
|
|
3279
3354
|
},
|
|
3280
|
-
contextWindow:
|
|
3281
|
-
|
|
3282
|
-
maxTokens:
|
|
3283
|
-
typeof topProvider?.max_completion_tokens === "number"
|
|
3284
|
-
? topProvider.max_completion_tokens
|
|
3285
|
-
: baseModel.maxTokens,
|
|
3355
|
+
contextWindow: toPositiveNumber(entry.context_length, baseModel.contextWindow),
|
|
3356
|
+
maxTokens: toPositiveNumber(topProvider?.max_completion_tokens, baseModel.maxTokens),
|
|
3286
3357
|
...(!supportsToolChoice && {
|
|
3287
3358
|
compat: { ...baseModel.compat, supportsToolChoice: false },
|
|
3288
3359
|
}),
|
|
@@ -3346,11 +3417,9 @@ export function openrouterModelManagerOptions(config?: OpenRouterModelManagerCon
|
|
|
3346
3417
|
cacheRead: 0,
|
|
3347
3418
|
cacheWrite: 0,
|
|
3348
3419
|
},
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
? topProvider.max_completion_tokens
|
|
3353
|
-
: null,
|
|
3420
|
+
// Some rows (e.g. respan/span-01) advertise `0` for unknown limits.
|
|
3421
|
+
contextWindow: toPositiveNumber(entry.context_length, null),
|
|
3422
|
+
maxTokens: toPositiveNumber(topProvider?.max_completion_tokens, null),
|
|
3354
3423
|
};
|
|
3355
3424
|
},
|
|
3356
3425
|
fetch: config?.fetch,
|
|
@@ -3382,11 +3451,8 @@ export function openrouterModelManagerOptions(config?: OpenRouterModelManagerCon
|
|
|
3382
3451
|
supportsTools: false,
|
|
3383
3452
|
// OpenRouter bills reranking per search; ModelCost has no search-unit axis.
|
|
3384
3453
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
3385
|
-
contextWindow:
|
|
3386
|
-
maxTokens:
|
|
3387
|
-
typeof topProvider?.max_completion_tokens === "number"
|
|
3388
|
-
? topProvider.max_completion_tokens
|
|
3389
|
-
: null,
|
|
3454
|
+
contextWindow: toPositiveNumber(entry.context_length, null),
|
|
3455
|
+
maxTokens: toPositiveNumber(topProvider?.max_completion_tokens, null),
|
|
3390
3456
|
};
|
|
3391
3457
|
},
|
|
3392
3458
|
fetch: config?.fetch,
|
|
@@ -3430,7 +3496,7 @@ export function openrouterModelManagerOptions(config?: OpenRouterModelManagerCon
|
|
|
3430
3496
|
cacheRead: 0,
|
|
3431
3497
|
cacheWrite: 0,
|
|
3432
3498
|
},
|
|
3433
|
-
contextWindow:
|
|
3499
|
+
contextWindow: toPositiveNumber(entry.context_length, null),
|
|
3434
3500
|
maxTokens: null,
|
|
3435
3501
|
};
|
|
3436
3502
|
},
|
|
@@ -4,6 +4,11 @@ import { apiRouteFor } from "../compat/behavior";
|
|
|
4
4
|
import { seedModels } from "../compat/providers";
|
|
5
5
|
import { type CodexModelDiscoveryResult, fetchCodexModels } from "../discovery/codex";
|
|
6
6
|
import type { DevinModelDiscoveryOptions } from "../discovery/devin";
|
|
7
|
+
import {
|
|
8
|
+
type FactoryDroidModelDiscoveryOptions,
|
|
9
|
+
factoryDroidSeedModels,
|
|
10
|
+
fetchFactoryDroidModels,
|
|
11
|
+
} from "../discovery/factory-droid";
|
|
7
12
|
import { fetchTypeSafeModels, TYPESAFE_DEFAULT_BASE_URL } from "../discovery/typesafe";
|
|
8
13
|
import { buildGitLabDuoWorkflowFallbackModel, fetchGitLabDuoWorkflowModels } from "../discovery/gitlab-duo-workflow";
|
|
9
14
|
import type { ModelManagerOptions } from "../model-manager";
|
|
@@ -131,7 +136,8 @@ export function cursorModelManagerOptions(config: CursorModelManagerConfig = {})
|
|
|
131
136
|
const { apiKey, baseUrl, clientVersion } = config;
|
|
132
137
|
return {
|
|
133
138
|
providerId: "cursor",
|
|
134
|
-
|
|
139
|
+
dynamicModelsAuthoritative: true,
|
|
140
|
+
cacheProviderId: resolveModelCacheProviderId("cursor", { apiKey, baseUrl }),
|
|
135
141
|
...(apiKey
|
|
136
142
|
? {
|
|
137
143
|
fetchDynamicModels: async () => {
|
|
@@ -365,6 +371,25 @@ export function devinModelManagerOptions(config: DevinModelManagerConfig = {}):
|
|
|
365
371
|
|
|
366
372
|
const devinDiscovery = once(() => import("../discovery/devin"));
|
|
367
373
|
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
// Factory Droid
|
|
376
|
+
// ---------------------------------------------------------------------------
|
|
377
|
+
|
|
378
|
+
export function factoryDroidModelManagerOptions(
|
|
379
|
+
config: FactoryDroidModelDiscoveryOptions = {},
|
|
380
|
+
): ModelManagerOptions<"factory-droid-agent"> {
|
|
381
|
+
return {
|
|
382
|
+
providerId: "factory-droid",
|
|
383
|
+
cacheProviderId: resolveModelCacheProviderId("factory-droid", config),
|
|
384
|
+
// No model-listing endpoint exists; the registry narrowed offline is the seed.
|
|
385
|
+
staticModels: factoryDroidSeedModels(config),
|
|
386
|
+
dynamicModelsAuthoritative: true,
|
|
387
|
+
fetchDynamicModels: () => fetchFactoryDroidModels(config),
|
|
388
|
+
// Refresh current policy online; cached eligibility is not current entitlement.
|
|
389
|
+
alwaysRefetchDynamicModels: true,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
368
393
|
// ---------------------------------------------------------------------------
|
|
369
394
|
// Synthetic role providers
|
|
370
395
|
// ---------------------------------------------------------------------------
|
|
@@ -415,7 +440,6 @@ export function typesafeModelManagerOptions(config: TypeSafeModelManagerConfig =
|
|
|
415
440
|
: undefined),
|
|
416
441
|
};
|
|
417
442
|
}
|
|
418
|
-
|
|
419
443
|
// ---------------------------------------------------------------------------
|
|
420
444
|
// Zai
|
|
421
445
|
// ---------------------------------------------------------------------------
|
package/src/types.ts
CHANGED
|
@@ -19,6 +19,7 @@ export type KnownApi =
|
|
|
19
19
|
| "google-vertex"
|
|
20
20
|
| "ollama-chat"
|
|
21
21
|
| "cursor-agent"
|
|
22
|
+
| "factory-droid-agent"
|
|
22
23
|
| "gitlab-duo-agent"
|
|
23
24
|
| "devin-agent"
|
|
24
25
|
| "apple-foundation-models";
|
|
@@ -284,12 +285,21 @@ export interface OpenAICompat {
|
|
|
284
285
|
thinkingKeep?: "all" | false;
|
|
285
286
|
/** Which reasoning content field to emit on assistant messages. Default: auto-detected. */
|
|
286
287
|
reasoningContentField?: "reasoning_content" | "reasoning" | "reasoning_text";
|
|
288
|
+
/** Decode Mistral thinking parts in streamed content and replay them as typed assistant content. */
|
|
289
|
+
mistralReasoningContentParts?: boolean;
|
|
287
290
|
/** Whether assistant tool-call messages must include reasoning content. Default: false. */
|
|
288
291
|
requiresReasoningContentForToolCalls?: boolean;
|
|
289
292
|
/** Whether all assistant messages must include reasoning content. Default: false. */
|
|
290
293
|
requiresReasoningContentForAllAssistantTurns?: boolean;
|
|
291
294
|
/** Whether the provider accepts a synthetic placeholder (e.g. ".") for missing reasoning_content on tool-call turns. Default: true. Set to false for providers like DeepSeek that validate the exact reasoning_content value. */
|
|
292
295
|
allowsSyntheticReasoningContentForToolCalls?: boolean;
|
|
296
|
+
/**
|
|
297
|
+
* Value emitted for the reasoning field on tool-call turns when the provider
|
|
298
|
+
* requires it (`requiresReasoningContentForToolCalls`) but no reasoning was
|
|
299
|
+
* captured. Default: "". Some upstreams validate the exact value — the droid
|
|
300
|
+
* proxy's DeepSeek family requires a single space.
|
|
301
|
+
*/
|
|
302
|
+
syntheticReasoningContentFallback?: string;
|
|
293
303
|
/**
|
|
294
304
|
* Replay preserved thinking blocks as `reasoning_content` (or the configured
|
|
295
305
|
* `reasoningContentField`) on EVERY assistant turn that carried reasoning,
|
|
@@ -673,6 +683,25 @@ export interface AnthropicCompat {
|
|
|
673
683
|
stripImageInput?: boolean;
|
|
674
684
|
/** Thinking-loop watchdog guard family applied to streamed reasoning. */
|
|
675
685
|
thinkingLoopGuard?: "gemini" | "deepseek" | "xai";
|
|
686
|
+
/**
|
|
687
|
+
* Drop the enabled `thinking` config and replayed thinking blocks when the
|
|
688
|
+
* conversation is not thinking-led (a proxy contract for non-interleaved
|
|
689
|
+
* budget models). Default: false.
|
|
690
|
+
*/
|
|
691
|
+
stripThinkingHistory?: boolean;
|
|
692
|
+
/**
|
|
693
|
+
* Send the `effort-2025-11-24` beta exactly when `output_config.effort`
|
|
694
|
+
* rides the request. Unset keeps the transport's legacy heuristic.
|
|
695
|
+
*/
|
|
696
|
+
effortBeta?: boolean;
|
|
697
|
+
/**
|
|
698
|
+
* Wire form of a disabled-thinking turn: omit the field, send
|
|
699
|
+
* `{ type: "disabled" }`, or keep adaptive thinking. Unset keeps the
|
|
700
|
+
* direct-provider behavior.
|
|
701
|
+
*/
|
|
702
|
+
disabledThinking?: "omit" | "disabled" | "adaptive";
|
|
703
|
+
/** The model is a fast-mode SKU: send `speed: "fast"` with the fast-mode beta. Default: false. */
|
|
704
|
+
fastMode?: boolean;
|
|
676
705
|
}
|
|
677
706
|
|
|
678
707
|
/**
|
|
@@ -703,6 +732,12 @@ export interface BedrockCompat {
|
|
|
703
732
|
* `PI_OPENAI_STREAM_IDLE_TIMEOUT_MS` alias, then the 300s default.
|
|
704
733
|
*/
|
|
705
734
|
streamIdleTimeoutMs?: number;
|
|
735
|
+
/**
|
|
736
|
+
* Whether the model accepts a forced `toolChoice` (`any` / `tool`). Claude
|
|
737
|
+
* Opus/Sonnet 5.5 reject it outright; the request builder downgrades forced
|
|
738
|
+
* choices to `auto` when this is false. Default: true.
|
|
739
|
+
*/
|
|
740
|
+
supportsForcedToolChoice?: boolean;
|
|
706
741
|
}
|
|
707
742
|
|
|
708
743
|
/** Fully-resolved Bedrock Converse prompt-cache capabilities, materialized once by `buildModel`. */
|
|
@@ -713,6 +748,7 @@ export interface ResolvedBedrockCompat {
|
|
|
713
748
|
supportsLongPromptCacheRetention: boolean;
|
|
714
749
|
promptCacheMinimumTokens: number;
|
|
715
750
|
promptCacheMaximumCheckpoints: number;
|
|
751
|
+
supportsForcedToolChoice: boolean;
|
|
716
752
|
/**
|
|
717
753
|
* Stream-watchdog idle-timeout fallback in ms for hosts with no keepalive
|
|
718
754
|
* events; 0 disables the idle watchdog. Undefined defers to
|
|
@@ -780,9 +816,12 @@ export interface ResolvedOpenAISharedCompat {
|
|
|
780
816
|
supportsForcedToolChoice: boolean;
|
|
781
817
|
supportsNamedToolChoice: boolean;
|
|
782
818
|
reasoningContentField?: OpenAICompat["reasoningContentField"];
|
|
819
|
+
mistralReasoningContentParts?: boolean;
|
|
783
820
|
requiresReasoningContentForToolCalls: boolean;
|
|
784
821
|
requiresReasoningContentForAllAssistantTurns: boolean;
|
|
785
822
|
allowsSyntheticReasoningContentForToolCalls: boolean;
|
|
823
|
+
/** See {@link OpenAICompat.syntheticReasoningContentFallback}. */
|
|
824
|
+
syntheticReasoningContentFallback?: string;
|
|
786
825
|
replayReasoningContent: boolean;
|
|
787
826
|
qwenPreserveThinking: boolean;
|
|
788
827
|
qwenTemplateReasoningEffort: boolean;
|
|
@@ -860,9 +899,11 @@ export type ResolvedOpenAICompat = ResolvedOpenAISharedCompat &
|
|
|
860
899
|
| "supportsForcedToolChoice"
|
|
861
900
|
| "supportsNamedToolChoice"
|
|
862
901
|
| "reasoningContentField"
|
|
902
|
+
| "mistralReasoningContentParts"
|
|
863
903
|
| "requiresReasoningContentForToolCalls"
|
|
864
904
|
| "requiresReasoningContentForAllAssistantTurns"
|
|
865
905
|
| "allowsSyntheticReasoningContentForToolCalls"
|
|
906
|
+
| "syntheticReasoningContentFallback"
|
|
866
907
|
| "replayReasoningContent"
|
|
867
908
|
| "qwenPreserveThinking"
|
|
868
909
|
| "qwenTemplateReasoningEffort"
|
|
@@ -977,8 +1018,25 @@ export type ResolvedOpenRouterCompat = ResolvedOpenAICompat & ResolvedOpenAIResp
|
|
|
977
1018
|
|
|
978
1019
|
/** Fully-resolved anthropic-messages compat view (same contract as `ResolvedOpenAICompat`). */
|
|
979
1020
|
export type ResolvedAnthropicCompat = Required<
|
|
980
|
-
Omit<
|
|
1021
|
+
Omit<
|
|
1022
|
+
AnthropicCompat,
|
|
1023
|
+
| "streamIdleTimeoutMs"
|
|
1024
|
+
| "thinkingLoopGuard"
|
|
1025
|
+
| "bedrockMessagesApi"
|
|
1026
|
+
| "effortBeta"
|
|
1027
|
+
| "disabledThinking"
|
|
1028
|
+
| "stripThinkingHistory"
|
|
1029
|
+
| "fastMode"
|
|
1030
|
+
>
|
|
981
1031
|
> & {
|
|
1032
|
+
/** Effort-beta override; undefined keeps the transport's legacy heuristic. */
|
|
1033
|
+
effortBeta?: AnthropicCompat["effortBeta"];
|
|
1034
|
+
/** Disabled-thinking wire form; undefined keeps the direct-provider behavior. */
|
|
1035
|
+
disabledThinking?: AnthropicCompat["disabledThinking"];
|
|
1036
|
+
/** Strip thinking history on non-thinking-led turns; undefined behaves as false. */
|
|
1037
|
+
stripThinkingHistory?: AnthropicCompat["stripThinkingHistory"];
|
|
1038
|
+
/** Fast-mode SKU; undefined behaves as false. */
|
|
1039
|
+
fastMode?: AnthropicCompat["fastMode"];
|
|
982
1040
|
/** Thinking-loop watchdog guard family applied to streamed reasoning. */
|
|
983
1041
|
thinkingLoopGuard?: AnthropicCompat["thinkingLoopGuard"];
|
|
984
1042
|
/**
|
|
@@ -1145,6 +1203,9 @@ export interface TokenCost {
|
|
|
1145
1203
|
cacheWrite: number;
|
|
1146
1204
|
}
|
|
1147
1205
|
|
|
1206
|
+
/** User-facing interpretation of a model's token-rate card. */
|
|
1207
|
+
export type ModelPricingStatus = "fixed" | "free" | "included" | "variable" | "unknown";
|
|
1208
|
+
|
|
1148
1209
|
/**
|
|
1149
1210
|
* Rates applied to the full request when its prompt exceeds `inputThreshold`,
|
|
1150
1211
|
* or reaches it when `inputThresholdInclusive` is true. Prompt input is the
|
|
@@ -1218,6 +1279,18 @@ export interface ModelAccountAccess {
|
|
|
1218
1279
|
cyberPrograms?: readonly string[];
|
|
1219
1280
|
}
|
|
1220
1281
|
|
|
1282
|
+
/** One Cursor `RequestedModel.parameters` entry recovered from rich discovery. */
|
|
1283
|
+
export interface CursorModelParameter {
|
|
1284
|
+
id: string;
|
|
1285
|
+
value: string;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/** Cursor wire target selected after local effort/variant routing. */
|
|
1289
|
+
export interface CursorModelRoute {
|
|
1290
|
+
modelId: string;
|
|
1291
|
+
parameters: readonly CursorModelParameter[];
|
|
1292
|
+
maxMode?: boolean;
|
|
1293
|
+
}
|
|
1221
1294
|
// Model interface for the unified model system
|
|
1222
1295
|
export interface Model<TApi extends Api = Api> {
|
|
1223
1296
|
id: string;
|
|
@@ -1327,6 +1400,29 @@ export interface Model<TApi extends Api = Api> {
|
|
|
1327
1400
|
* bundled/config rows and on single-account discovery.
|
|
1328
1401
|
*/
|
|
1329
1402
|
accountAccess?: Readonly<Record<string, ModelAccountAccess>>;
|
|
1403
|
+
/** Cursor `RequestedModel.parameters` for this model's default variant. */
|
|
1404
|
+
cursorModelParameters?: readonly CursorModelParameter[];
|
|
1405
|
+
/**
|
|
1406
|
+
* Per-wire-id Cursor routes recovered from `AvailableModels`. Generic effort
|
|
1407
|
+
* collapse retains these while routing selects one key at request time.
|
|
1408
|
+
*/
|
|
1409
|
+
cursorModelRoutes?: Readonly<Record<string, CursorModelRoute>>;
|
|
1410
|
+
/** Cursor's account-relative model price/multiplier; not a per-token USD rate. */
|
|
1411
|
+
cursorPrice?: number;
|
|
1412
|
+
/** Cursor requires data retention to invoke this model. */
|
|
1413
|
+
cursorRequiresDataRetention?: boolean;
|
|
1414
|
+
/** Cursor explicitly reports support for its Agent surface. */
|
|
1415
|
+
cursorSupportsAgent?: boolean;
|
|
1416
|
+
/** Cursor explicitly reports support for sandboxed execution. */
|
|
1417
|
+
cursorSupportsSandboxing?: boolean;
|
|
1418
|
+
/** Factory Droid: account-resolved upstream rotation (first entry is the default `x-api-provider`). */
|
|
1419
|
+
factoryDroidApiProviders?: string[];
|
|
1420
|
+
/** Factory Droid: `configured_order` when live routing chose the rotation; absent means registry order. */
|
|
1421
|
+
factoryDroidRoutingSource?: "configured_order";
|
|
1422
|
+
/** Factory Droid Standard Credits base rate (relative per-token weight, not dollars). */
|
|
1423
|
+
factoryDroidCredits?: number;
|
|
1424
|
+
/** Canonical organization resolved during Factory discovery. */
|
|
1425
|
+
factoryDroidOrgId?: string;
|
|
1330
1426
|
cost: ModelCost;
|
|
1331
1427
|
/**
|
|
1332
1428
|
* Prompt-cache entry lifetime per retention tier, in seconds. Populated only
|
|
@@ -1336,9 +1432,22 @@ export interface Model<TApi extends Api = Api> {
|
|
|
1336
1432
|
* Custom models and provider overrides opt in via models.yml `promptCache`.
|
|
1337
1433
|
*/
|
|
1338
1434
|
promptCache?: ModelPromptCache;
|
|
1435
|
+
/**
|
|
1436
|
+
* Verbatim configured lifetimes (models.yml, `modelOverrides`, runtime
|
|
1437
|
+
* registrations). `buildModel` applies them over catalog `prompt-cache`
|
|
1438
|
+
* rules on every rebuild; `{}` keeps warming disabled.
|
|
1439
|
+
*/
|
|
1440
|
+
promptCacheConfig?: ModelPromptCache;
|
|
1441
|
+
/**
|
|
1442
|
+
* Interpretation of an all-zero token-rate card. Omitted zero-rate cards
|
|
1443
|
+
* are unknown; any non-zero rate is always treated as fixed pricing.
|
|
1444
|
+
*/
|
|
1445
|
+
pricingStatus?: Exclude<ModelPricingStatus, "fixed">;
|
|
1339
1446
|
/** Premium Copilot requests charged per user-initiated request (defaults to 1). */
|
|
1340
1447
|
premiumMultiplier?: number;
|
|
1341
1448
|
contextWindow: number | null;
|
|
1449
|
+
/** Preserve the host's supplied window instead of applying inferred expansion or reference-price caps. */
|
|
1450
|
+
contextWindowAuthoritative?: boolean;
|
|
1342
1451
|
/** Optional larger prompt window available when extended context is enabled. */
|
|
1343
1452
|
maxContextWindow?: number;
|
|
1344
1453
|
maxTokens: number | null;
|
|
@@ -1411,6 +1520,8 @@ export interface Model<TApi extends Api = Api> {
|
|
|
1411
1520
|
isNew?: boolean;
|
|
1412
1521
|
/** Upstream marks this model as beta / preview quality. */
|
|
1413
1522
|
isBeta?: boolean;
|
|
1523
|
+
/** Authenticated provider catalog marks this as the account's default model. */
|
|
1524
|
+
isProviderDefault?: boolean;
|
|
1414
1525
|
/** Upstream marks this model as one of its recommended picks. */
|
|
1415
1526
|
isRecommended?: boolean;
|
|
1416
1527
|
/** Canonical thinking capability metadata for this model. */
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor wire constants shared by authenticated catalog discovery, the pi-ai
|
|
3
|
+
* provider, and account usage. Keep the announced client build aligned with the
|
|
4
|
+
* released Cursor agent CLI because the service gates protocol features on it.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Default host for Cursor's Connect RPCs and account API. */
|
|
8
|
+
export const CURSOR_DEFAULT_BASE_URL = "https://api2.cursor.sh";
|
|
9
|
+
|
|
10
|
+
/** Released Cursor agent CLI build whose protocol surface this client mirrors. */
|
|
11
|
+
export const CURSOR_CLIENT_VERSION = "cli-2026.09.02-c22c1a3";
|
|
12
|
+
|
|
13
|
+
export const CURSOR_RUN_PATH = "/agent.v1.AgentService/Run";
|
|
14
|
+
export const CURSOR_RUN_SSE_PATH = "/agent.v1.AgentService/RunSSE";
|
|
15
|
+
export const CURSOR_BIDI_APPEND_PATH = "/aiserver.v1.BidiService/BidiAppend";
|
|
16
|
+
export const CURSOR_GET_USABLE_MODELS_PATH = "/agent.v1.AgentService/GetUsableModels";
|
|
17
|
+
export const CURSOR_GET_DEFAULT_MODEL_PATH = "/agent.v1.AgentService/GetDefaultModelForCli";
|
|
18
|
+
export const CURSOR_AVAILABLE_MODELS_PATH = "/aiserver.v1.AiService/AvailableModels";
|
|
19
|
+
|
|
20
|
+
/** Headers shared by Cursor's authenticated CLI RPCs. */
|
|
21
|
+
export function cursorClientHeaders(
|
|
22
|
+
apiKey: string,
|
|
23
|
+
options: { clientVersion?: string; contentType?: "application/proto" | "application/connect+proto" } = {},
|
|
24
|
+
): Record<string, string> {
|
|
25
|
+
return {
|
|
26
|
+
"content-type": options.contentType ?? "application/proto",
|
|
27
|
+
authorization: `Bearer ${apiKey}`,
|
|
28
|
+
"x-cursor-client-type": "cli",
|
|
29
|
+
"x-cursor-client-version": options.clientVersion ?? CURSOR_CLIENT_VERSION,
|
|
30
|
+
"x-ghost-mode": "true",
|
|
31
|
+
};
|
|
32
|
+
}
|