@oh-my-pi/pi-catalog 18.4.3 → 18.4.5
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 +37 -0
- package/dist/types/compat/anthropic.d.ts +6 -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/hosts.d.ts +13 -0
- 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 +115 -3
- package/dist/types/wire/codex.d.ts +5 -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 +19 -0
- package/src/compat/anthropic.ts +11 -1
- package/src/compat/auth-ids.ts +4 -0
- package/src/compat/axes.ts +114 -1
- 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 +59 -8
- package/src/compat/rules/README.md +15 -7
- 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 +16 -4
- 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/providers/openai-codex.kdl +17 -0
- package/src/compat/rules/runtime/behavior.kdl +44 -0
- 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/codex.ts +16 -0
- 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/hosts.ts +47 -0
- 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 +129 -1
- package/src/wire/codex.ts +5 -3
- package/src/wire/cursor.ts +32 -0
- package/src/wire/factory-droid.ts +130 -0
package/src/compat/axes.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { Effort } from "../effort";
|
|
13
13
|
import { MODEL_KINDS, type ThinkingControlMode } from "../types";
|
|
14
|
+
import { FACTORY_DROID_UPSTREAMS } from "../wire/factory-droid";
|
|
14
15
|
|
|
15
16
|
/** Value shape a directive accepts (see `rules/README.md`). */
|
|
16
17
|
export type AxisShape = "scalar" | "array" | "object";
|
|
@@ -19,7 +20,7 @@ export type AxisShape = "scalar" | "array" | "object";
|
|
|
19
20
|
export type AxisSet = "wire" | "thinking" | "catalog";
|
|
20
21
|
|
|
21
22
|
/** Resolved compat record families a wire axis may be assigned onto. */
|
|
22
|
-
export type CompatRecordName = "openai" | "openai-responses" | "anthropic" | "bedrock" | "devin" | "google";
|
|
23
|
+
export type CompatRecordName = "openai" | "openai-responses" | "anthropic" | "bedrock" | "devin" | "google" | "request";
|
|
23
24
|
|
|
24
25
|
/** One axis definition: resolved key, namespace, shape, and applicability. */
|
|
25
26
|
export interface AxisDef {
|
|
@@ -37,6 +38,8 @@ export interface AxisDef {
|
|
|
37
38
|
* that compile to camelCase resolved keys.
|
|
38
39
|
*/
|
|
39
40
|
verbatimKeys?: true;
|
|
41
|
+
/** Array axes only: a bare directive assigns an empty list (an explicit "none"). */
|
|
42
|
+
emptyArray?: true;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
const OAI = ["openai", "openai-responses"] as const;
|
|
@@ -86,6 +89,34 @@ function wire(
|
|
|
86
89
|
* blocks.
|
|
87
90
|
*/
|
|
88
91
|
export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
92
|
+
// Selected-route request shaping, resolved alongside shared adapter compat.
|
|
93
|
+
"completions-reasoning-mode": wire("completionsReasoningMode", ["request"], "scalar", [
|
|
94
|
+
"none",
|
|
95
|
+
"effort",
|
|
96
|
+
"opt-in",
|
|
97
|
+
"forced-on",
|
|
98
|
+
]),
|
|
99
|
+
"completions-reasoning-history": wire("completionsReasoningHistory", ["request"], "scalar", [
|
|
100
|
+
"omit",
|
|
101
|
+
"preserved",
|
|
102
|
+
"interleaved",
|
|
103
|
+
]),
|
|
104
|
+
"anthropic-thinking": wire("anthropicThinking", ["request"], "scalar", [
|
|
105
|
+
"adaptive",
|
|
106
|
+
"adaptive-summarized",
|
|
107
|
+
"budget-interleaved",
|
|
108
|
+
"budget-effort",
|
|
109
|
+
]),
|
|
110
|
+
"anthropic-tool-streaming-beta": wire("anthropicToolStreamingBeta", ["request"]),
|
|
111
|
+
"openai-platform-header": wire("openaiPlatformHeader", ["request"]),
|
|
112
|
+
"responses-cache-retention": wire("responsesCacheRetention", ["request"]),
|
|
113
|
+
"responses-verbosity": wire("responsesVerbosity", ["request"], "scalar", ["low"]),
|
|
114
|
+
"responses-service-tier": wire("responsesServiceTier", ["request"], "scalar", ["priority"]),
|
|
115
|
+
"responses-parallel-tool-calls": wire("responsesParallelToolCalls", ["request"]),
|
|
116
|
+
"responses-safety-identifier": wire("responsesSafetyIdentifier", ["request"]),
|
|
117
|
+
"responses-tool-choice-auto": wire("responsesToolChoiceAuto", ["request"]),
|
|
118
|
+
"routing-session-lock": wire("routingSessionLock", ["request"]),
|
|
119
|
+
"google-thinking": wire("googleThinking", ["request"], "scalar", ["level", "level-medium"]),
|
|
89
120
|
// ── wire: OpenAI-compatible surfaces (chat completions + Responses) ──
|
|
90
121
|
"allows-synthetic-reasoning-content-for-tool-calls": wire("allowsSyntheticReasoningContentForToolCalls", OAI),
|
|
91
122
|
"always-send-max-tokens": wire("alwaysSendMaxTokens", OAI),
|
|
@@ -101,6 +132,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
101
132
|
"include-encrypted-reasoning": wire("includeEncryptedReasoning", OAI),
|
|
102
133
|
"kimi-api-format": wire("kimiApiFormat", ["openai"], "scalar", ["openai", "anthropic"]),
|
|
103
134
|
"max-tokens-field": wire("maxTokensField", ["openai"], "scalar", ["max_completion_tokens", "max_tokens"]),
|
|
135
|
+
"mistral-reasoning-content-parts": wire("mistralReasoningContentParts", ["openai"]),
|
|
104
136
|
"native-kimi-k3-reasoning": wire("nativeKimiK3Reasoning", ["openai"]),
|
|
105
137
|
"omit-reasoning-effort": wire("omitReasoningEffort", OAI),
|
|
106
138
|
"prompt-cache-breakpoint-ttl": wire("promptCacheBreakpointTtl", OAI, "scalar", ["30m"]),
|
|
@@ -128,6 +160,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
128
160
|
]),
|
|
129
161
|
"reasoning-effort-map": wire("reasoningEffortMap", OAI, "object"),
|
|
130
162
|
"replay-reasoning-content": wire("replayReasoningContent", ["openai"]),
|
|
163
|
+
"synthetic-reasoning-content-fallback": wire("syntheticReasoningContentFallback", ["openai"]),
|
|
131
164
|
"requires-assistant-after-tool-result": wire("requiresAssistantAfterToolResult", ["openai"]),
|
|
132
165
|
"requires-assistant-content-for-tool-calls": wire("requiresAssistantContentForToolCalls", OAI),
|
|
133
166
|
"requires-mistral-tool-ids": wire("requiresMistralToolIds", ["openai"]),
|
|
@@ -190,10 +223,14 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
190
223
|
"zai-reasoning-effort-dialect": wire("zaiReasoningEffortDialect", ["openai"]),
|
|
191
224
|
|
|
192
225
|
// ── wire: anthropic-messages ──
|
|
226
|
+
"bedrock-messages-api": wire("bedrockMessagesApi", ["anthropic"]),
|
|
193
227
|
"allow-anthropic-header-overrides": wire("allowAnthropicHeaderOverrides", ["anthropic"]),
|
|
194
228
|
"disable-adaptive-thinking": wire("disableAdaptiveThinking", ["anthropic"]),
|
|
195
229
|
"disable-strict-tools": wire("disableStrictTools", ["anthropic"]),
|
|
230
|
+
"disabled-thinking": wire("disabledThinking", ["anthropic"], "scalar", ["omit", "disabled", "adaptive"]),
|
|
231
|
+
"effort-beta": wire("effortBeta", ["anthropic"]),
|
|
196
232
|
"escape-builtin-tool-names": wire("escapeBuiltinToolNames", ["anthropic"]),
|
|
233
|
+
"fast-mode": wire("fastMode", ["anthropic"]),
|
|
197
234
|
"first-party-provider": wire("firstPartyProvider", ["anthropic"]),
|
|
198
235
|
"inject-claude-code-instruction": wire("injectClaudeCodeInstruction", ["anthropic"]),
|
|
199
236
|
"official-endpoint": wire("officialEndpoint", ["anthropic", "openai-responses"]),
|
|
@@ -201,6 +238,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
201
238
|
"requires-thinking-enabled": wire("requiresThinkingEnabled", ["anthropic"]),
|
|
202
239
|
"requires-tool-result-id": wire("requiresToolResultId", ["anthropic"]),
|
|
203
240
|
"signing-endpoint": wire("signingEndpoint", ["anthropic"]),
|
|
241
|
+
"strip-thinking-history": wire("stripThinkingHistory", ["anthropic"]),
|
|
204
242
|
"supports-context-management": wire("supportsContextManagement", ["anthropic"]),
|
|
205
243
|
"supports-output-effort": wire("supportsOutputEffort", ["anthropic"]),
|
|
206
244
|
"supports-eager-tool-input-streaming": wire("supportsEagerToolInputStreaming", ["anthropic"]),
|
|
@@ -209,6 +247,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
209
247
|
"supports-mid-conversation-tool-changes": wire("supportsMidConversationToolChanges", ["anthropic"]),
|
|
210
248
|
"supports-per-message-effort": wire("supportsPerMessageEffort", ["anthropic"]),
|
|
211
249
|
"supports-server-compaction": wire("supportsServerCompaction", ["anthropic"]),
|
|
250
|
+
"supports-between-tools-thinking": wire("supportsBetweenToolsThinking", ["anthropic"]),
|
|
212
251
|
"supports-thinking-binding-controls": wire("supportsThinkingBindingControls", ["anthropic"]),
|
|
213
252
|
"supports-turn-scoped-system": wire("supportsTurnScopedSystem", ["anthropic"]),
|
|
214
253
|
|
|
@@ -319,6 +358,12 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
319
358
|
"clamp-context-override": { key: "clampContextOverride", set: "catalog", shape: "scalar" },
|
|
320
359
|
"context-promotion-target": { key: "contextPromotionTarget", set: "catalog", shape: "scalar" },
|
|
321
360
|
"context-window-floor": { key: "contextWindowFloor", set: "catalog", shape: "scalar" },
|
|
361
|
+
"context-window-authoritative": {
|
|
362
|
+
key: "contextWindowAuthoritative",
|
|
363
|
+
set: "catalog",
|
|
364
|
+
shape: "scalar",
|
|
365
|
+
values: [true, false],
|
|
366
|
+
},
|
|
322
367
|
"cost-patch": { key: "costPatch", set: "catalog", shape: "object" },
|
|
323
368
|
"cost-fallback": { key: "costFallback", set: "catalog", shape: "object" },
|
|
324
369
|
/**
|
|
@@ -351,6 +396,12 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
351
396
|
"prompt-cache": { key: "promptCache", set: "catalog", shape: "object" },
|
|
352
397
|
"long-usage-limit-fallback": { key: "longUsageLimitFallback", set: "catalog", shape: "scalar" },
|
|
353
398
|
"max-context-window": { key: "maxContextWindow", set: "catalog", shape: "scalar" },
|
|
399
|
+
"pricing-status": {
|
|
400
|
+
key: "pricingStatus",
|
|
401
|
+
set: "catalog",
|
|
402
|
+
shape: "scalar",
|
|
403
|
+
values: ["free", "included", "variable", "unknown"],
|
|
404
|
+
},
|
|
354
405
|
"requires-cursor-tool-schema-projection": {
|
|
355
406
|
key: "requiresCursorToolSchemaProjection",
|
|
356
407
|
set: "catalog",
|
|
@@ -377,6 +428,68 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
377
428
|
priority: { key: "priority", set: "catalog", shape: "scalar" },
|
|
378
429
|
"service-tier-cost": { key: "serviceTierCost", set: "catalog", shape: "object" },
|
|
379
430
|
"time-based-cost": { key: "timeBased", set: "catalog", shape: "object" },
|
|
431
|
+
|
|
432
|
+
// ── catalog: routed-subscription registry ──
|
|
433
|
+
// A gateway whose proxy fans one model out to several upstreams (Factory
|
|
434
|
+
// Droid). Consumed through `./factory-droid`; `api-routes` picks the wire
|
|
435
|
+
// and `quota-tiers` the billing pool.
|
|
436
|
+
/** Ordered upstream rotation; the first entry is the default `x-api-provider`. */
|
|
437
|
+
"upstream-rotation": { key: "upstreamRotation", set: "catalog", shape: "array", values: FACTORY_DROID_UPSTREAMS },
|
|
438
|
+
/**
|
|
439
|
+
* Upstreams eligible to serve one inference region. The provider-wide rule
|
|
440
|
+
* is the upstream serving table; a model rule replaces it (the native
|
|
441
|
+
* region override), and a bare directive means the region never serves it.
|
|
442
|
+
*/
|
|
443
|
+
"region-upstreams-global": {
|
|
444
|
+
key: "regionUpstreamsGlobal",
|
|
445
|
+
set: "catalog",
|
|
446
|
+
shape: "array",
|
|
447
|
+
values: FACTORY_DROID_UPSTREAMS,
|
|
448
|
+
emptyArray: true,
|
|
449
|
+
},
|
|
450
|
+
"region-upstreams-us": {
|
|
451
|
+
key: "regionUpstreamsUs",
|
|
452
|
+
set: "catalog",
|
|
453
|
+
shape: "array",
|
|
454
|
+
values: FACTORY_DROID_UPSTREAMS,
|
|
455
|
+
emptyArray: true,
|
|
456
|
+
},
|
|
457
|
+
"region-upstreams-eu": {
|
|
458
|
+
key: "regionUpstreamsEu",
|
|
459
|
+
set: "catalog",
|
|
460
|
+
shape: "array",
|
|
461
|
+
values: FACTORY_DROID_UPSTREAMS,
|
|
462
|
+
emptyArray: true,
|
|
463
|
+
},
|
|
464
|
+
/** EU inference limits (`context-window`, `max-tokens`) where the region narrows the default. */
|
|
465
|
+
"region-limits-eu": { key: "regionLimitsEu", set: "catalog", shape: "object" },
|
|
466
|
+
/**
|
|
467
|
+
* Subscription credit rates: `input` is the per-token credit weight shown
|
|
468
|
+
* as the model's multiplier; `output` and `cache-read` multiply it.
|
|
469
|
+
*/
|
|
470
|
+
"credit-rates": { key: "creditRates", set: "catalog", shape: "object" },
|
|
471
|
+
/**
|
|
472
|
+
* List price borrowed from a bundled catalog row: provider id, then the
|
|
473
|
+
* row id when it differs from the model's own. Absent means no list price.
|
|
474
|
+
*/
|
|
475
|
+
"list-price-from": { key: "listPriceFrom", set: "catalog", shape: "array" },
|
|
476
|
+
/** Provider family whose live routing defaults apply to the model. */
|
|
477
|
+
"routing-family": {
|
|
478
|
+
key: "routingFamily",
|
|
479
|
+
set: "catalog",
|
|
480
|
+
shape: "scalar",
|
|
481
|
+
values: ["anthropic", "openai", "google", "factory", "xai"],
|
|
482
|
+
},
|
|
483
|
+
/** Other ids organization policy may use for the model. */
|
|
484
|
+
"policy-aliases": { key: "policyAliases", set: "catalog", shape: "array" },
|
|
485
|
+
/**
|
|
486
|
+
* Account gates: `feature-flag` must be on to list the model,
|
|
487
|
+
* `deprecation-flag` hides it once on, `requires-explicit-opt-in` hides it
|
|
488
|
+
* without an org policy, and `base-variant` marks a fast tier of that model.
|
|
489
|
+
*/
|
|
490
|
+
entitlement: { key: "entitlement", set: "catalog", shape: "object" },
|
|
491
|
+
/** The native default when the caller picks no effort is thinking off. */
|
|
492
|
+
"default-reasoning-off": { key: "defaultReasoningOff", set: "catalog", shape: "scalar", values: [true, false] },
|
|
380
493
|
};
|
|
381
494
|
|
|
382
495
|
/** Records applicable to each API family; used by `resolve.ts` when applying wire axes. */
|
package/src/compat/cascade.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* for one structured model target from the compiled rule tree.
|
|
4
4
|
*
|
|
5
5
|
* Faithful port of the o2 reference resolver (`cascade.rs`): rules are
|
|
6
|
-
* conjunctions over `(class, provider, api, family, revision, models)`; per axis
|
|
6
|
+
* conjunctions over `(class, provider, api, upstream, family, revision, models)`; per axis
|
|
7
7
|
* the matching rule with the greatest `(model-selector exactness,
|
|
8
8
|
* constrained-dimension count, priority)` tuple wins, and an equal-tuple
|
|
9
9
|
* same-axis contest throws {@link AmbiguousOverlapError}. Declaration and
|
|
@@ -129,6 +129,7 @@ function buildRuleIndex(cascade: CompiledCascade): RuleIndex {
|
|
|
129
129
|
Number(compiled.class !== undefined) +
|
|
130
130
|
Number(compiled.providers !== undefined) +
|
|
131
131
|
Number(compiled.apis !== undefined) +
|
|
132
|
+
Number(compiled.upstreams !== undefined) +
|
|
132
133
|
Number(compiled.family !== undefined) +
|
|
133
134
|
Number(compiled.revision !== undefined) +
|
|
134
135
|
Number(compiled.models !== undefined),
|
|
@@ -215,6 +216,11 @@ function rankRule(rule: IndexedRule, prepared: PreparedTarget): readonly [number
|
|
|
215
216
|
const { compiled } = rule;
|
|
216
217
|
const { target } = prepared;
|
|
217
218
|
if (compiled.apis !== undefined && !compiled.apis.includes(target.api)) return undefined;
|
|
219
|
+
if (
|
|
220
|
+
compiled.upstreams !== undefined &&
|
|
221
|
+
(target.upstream === undefined || !compiled.upstreams.includes(target.upstream))
|
|
222
|
+
)
|
|
223
|
+
return undefined;
|
|
218
224
|
if (compiled.family !== undefined && compiled.family !== target.family) return undefined;
|
|
219
225
|
if (rule.revision !== undefined && (!prepared.revision || !revisionSatisfies(prepared.revision, rule.revision))) {
|
|
220
226
|
return undefined;
|
|
@@ -292,6 +298,7 @@ function targetKey(target: ResolveTarget): string {
|
|
|
292
298
|
return (
|
|
293
299
|
keyPart(target.provider) +
|
|
294
300
|
keyPart(target.api) +
|
|
301
|
+
keyPart(target.upstream) +
|
|
295
302
|
keyPart(target.class) +
|
|
296
303
|
keyPart(target.family) +
|
|
297
304
|
keyPart(target.revision) +
|
package/src/compat/collapse.ts
CHANGED
|
@@ -937,6 +937,9 @@ function collapseWithTable<TSpec extends VariantSpecLike>(
|
|
|
937
937
|
...(cursorMaxMode === undefined ? {} : { cursorMaxMode }),
|
|
938
938
|
...(cursorMaxModeRoutes === undefined ? {} : { cursorMaxModeRoutes }),
|
|
939
939
|
};
|
|
940
|
+
if (memberSpecs.some(spec => spec.isProviderDefault === true)) {
|
|
941
|
+
collapsed.isProviderDefault = true;
|
|
942
|
+
}
|
|
940
943
|
// The default wire id is the family's declared `defaultMember` when live,
|
|
941
944
|
// else the highest-priority live member. Omitted when it equals the
|
|
942
945
|
// logical id (bare/thinking pairs) — `resolveWireModelId` falls back.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory Droid registry policy read from KDL: the roster seed and per-model
|
|
3
|
+
* catalog axes in `rules/providers/factory-droid.kdl`, plus each model's wire
|
|
4
|
+
* (`api-routes`) in `rules/runtime/behavior.kdl`. Discovery builds
|
|
5
|
+
* account-scoped specs from it and the pi-ai provider re-reads it per request
|
|
6
|
+
* to bind a model to the account serving it.
|
|
7
|
+
*/
|
|
8
|
+
import { isRecord, once } from "@oh-my-pi/pi-utils";
|
|
9
|
+
import type { Api, ModelSpec } from "../types";
|
|
10
|
+
import { FACTORY_DROID_WIRES, type FactoryDroidRegion, type FactoryDroidWire } from "../wire/factory-droid";
|
|
11
|
+
import { apiRouteFor } from "./behavior";
|
|
12
|
+
import { seedModels } from "./providers";
|
|
13
|
+
import { resolveCatalogAxes } from "./resolve";
|
|
14
|
+
|
|
15
|
+
const PROVIDER = "factory-droid";
|
|
16
|
+
|
|
17
|
+
/** Native token limits for one inference region. */
|
|
18
|
+
export interface FactoryDroidLimits {
|
|
19
|
+
contextWindow: number | null;
|
|
20
|
+
maxTokens: number | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Standard Credits rates: `input` is the per-token weight; `output` and `cacheRead` multiply it. */
|
|
24
|
+
export interface FactoryDroidCreditRates {
|
|
25
|
+
input: number;
|
|
26
|
+
output?: number;
|
|
27
|
+
cacheRead?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Account gates on one registry model (the `entitlement` axis). */
|
|
31
|
+
export interface FactoryDroidEntitlement {
|
|
32
|
+
/** Feature flag that must be on for the account to see the model. */
|
|
33
|
+
featureFlag?: string;
|
|
34
|
+
/** Hard deprecation flag; when on, first-party clients hide the model. */
|
|
35
|
+
deprecationFlag?: string;
|
|
36
|
+
/** Hidden unless an org policy is known and does not require consent. */
|
|
37
|
+
requiresExplicitOptIn: boolean;
|
|
38
|
+
/** Base model this entry is the fast tier of; org policy can withdraw fast tiers as a class. */
|
|
39
|
+
baseVariant?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Registry facts for one Factory model. */
|
|
43
|
+
export interface FactoryDroidModelPolicy {
|
|
44
|
+
wire: FactoryDroidWire;
|
|
45
|
+
/** Registry upstream order; the first entry is the default `x-api-provider`. */
|
|
46
|
+
rotation: readonly string[];
|
|
47
|
+
/** Upstreams eligible per inference region; empty means the region does not serve the model. */
|
|
48
|
+
regionUpstreams: Readonly<Record<FactoryDroidRegion, readonly string[]>>;
|
|
49
|
+
/** Registry limits; EU entries override them where the region narrows the model. */
|
|
50
|
+
limits: FactoryDroidLimits;
|
|
51
|
+
euLimits: Partial<FactoryDroidLimits>;
|
|
52
|
+
creditRates?: FactoryDroidCreditRates;
|
|
53
|
+
/** Bundled catalog row whose list price the model shows. */
|
|
54
|
+
listPriceFrom?: { provider: string; modelId: string };
|
|
55
|
+
/** Provider family whose live routing defaults apply. */
|
|
56
|
+
routingFamily?: string;
|
|
57
|
+
/** Other ids organization policy may use for the model. */
|
|
58
|
+
policyAliases: readonly string[];
|
|
59
|
+
entitlement: FactoryDroidEntitlement;
|
|
60
|
+
/** Native default with no caller effort is thinking off. */
|
|
61
|
+
defaultReasoningOff: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** One registry model: its roster row and resolved policy. */
|
|
65
|
+
export interface FactoryDroidRegistryModel {
|
|
66
|
+
spec: ModelSpec<"factory-droid-agent">;
|
|
67
|
+
policy: FactoryDroidModelPolicy;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function stringList(value: unknown): readonly string[] | undefined {
|
|
71
|
+
return Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === "string") : undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function numberField(source: Record<string, unknown>, key: string): number | undefined {
|
|
75
|
+
const value = source[key];
|
|
76
|
+
return typeof value === "number" ? value : undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function stringField(source: Record<string, unknown>, key: string): string | undefined {
|
|
80
|
+
const value = source[key];
|
|
81
|
+
return typeof value === "string" ? value : undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function readPolicy(spec: ModelSpec<"factory-droid-agent">): FactoryDroidModelPolicy {
|
|
85
|
+
const catalog = resolveCatalogAxes(spec);
|
|
86
|
+
const routedApi = apiRouteFor(PROVIDER, spec.id)?.api;
|
|
87
|
+
const euLimits = isRecord(catalog.regionLimitsEu) ? catalog.regionLimitsEu : {};
|
|
88
|
+
const credits = isRecord(catalog.creditRates) ? catalog.creditRates : {};
|
|
89
|
+
const entitlement = isRecord(catalog.entitlement) ? catalog.entitlement : {};
|
|
90
|
+
const creditInput = numberField(credits, "input");
|
|
91
|
+
const [priceProvider, priceModelId] = stringList(catalog.listPriceFrom) ?? [];
|
|
92
|
+
const euContextWindow = numberField(euLimits, "contextWindow");
|
|
93
|
+
const euMaxTokens = numberField(euLimits, "maxTokens");
|
|
94
|
+
const creditOutput = numberField(credits, "output");
|
|
95
|
+
const creditCacheRead = numberField(credits, "cacheRead");
|
|
96
|
+
const featureFlag = stringField(entitlement, "featureFlag");
|
|
97
|
+
const deprecationFlag = stringField(entitlement, "deprecationFlag");
|
|
98
|
+
const baseVariant = stringField(entitlement, "baseVariant");
|
|
99
|
+
return {
|
|
100
|
+
wire: FACTORY_DROID_WIRES.find(wire => wire === routedApi) ?? "openai-completions",
|
|
101
|
+
rotation: stringList(catalog.upstreamRotation) ?? [],
|
|
102
|
+
regionUpstreams: {
|
|
103
|
+
global: stringList(catalog.regionUpstreamsGlobal) ?? [],
|
|
104
|
+
us: stringList(catalog.regionUpstreamsUs) ?? [],
|
|
105
|
+
eu: stringList(catalog.regionUpstreamsEu) ?? [],
|
|
106
|
+
},
|
|
107
|
+
limits: { contextWindow: spec.contextWindow, maxTokens: spec.maxTokens },
|
|
108
|
+
euLimits: {
|
|
109
|
+
...(euContextWindow !== undefined && { contextWindow: euContextWindow }),
|
|
110
|
+
...(euMaxTokens !== undefined && { maxTokens: euMaxTokens }),
|
|
111
|
+
},
|
|
112
|
+
...(creditInput !== undefined && {
|
|
113
|
+
creditRates: {
|
|
114
|
+
input: creditInput,
|
|
115
|
+
...(creditOutput !== undefined && { output: creditOutput }),
|
|
116
|
+
...(creditCacheRead !== undefined && { cacheRead: creditCacheRead }),
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
...(priceProvider !== undefined && {
|
|
120
|
+
listPriceFrom: { provider: priceProvider, modelId: priceModelId ?? spec.id },
|
|
121
|
+
}),
|
|
122
|
+
...(typeof catalog.routingFamily === "string" && { routingFamily: catalog.routingFamily }),
|
|
123
|
+
policyAliases: stringList(catalog.policyAliases) ?? [],
|
|
124
|
+
entitlement: {
|
|
125
|
+
...(featureFlag !== undefined && { featureFlag }),
|
|
126
|
+
...(deprecationFlag !== undefined && { deprecationFlag }),
|
|
127
|
+
requiresExplicitOptIn: entitlement.requiresExplicitOptIn === true,
|
|
128
|
+
...(baseVariant !== undefined && { baseVariant }),
|
|
129
|
+
},
|
|
130
|
+
defaultReasoningOff: catalog.defaultReasoningOff === true,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The registry roster in declaration order, each row with its resolved policy. */
|
|
135
|
+
export const factoryDroidRegistry = once((): readonly FactoryDroidRegistryModel[] =>
|
|
136
|
+
seedModels<"factory-droid-agent">(PROVIDER).map(spec => ({ spec, policy: readPolicy(spec) })),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
/** Registry policy for a model (by its request id); undefined for ids outside the registry. */
|
|
140
|
+
export function resolveFactoryDroidPolicy(
|
|
141
|
+
model: Pick<ModelSpec<Api>, "id" | "requestModelId">,
|
|
142
|
+
): FactoryDroidModelPolicy | undefined {
|
|
143
|
+
const id = model.requestModelId ?? model.id;
|
|
144
|
+
return factoryDroidRegistry().find(entry => entry.spec.id === id)?.policy;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Effective upstream rotation for an inference region: the registry order,
|
|
149
|
+
* narrowed to the upstreams that region serves (or the model's own region
|
|
150
|
+
* override). An empty result means the region cannot serve the model.
|
|
151
|
+
*/
|
|
152
|
+
export function resolveFactoryDroidRotation(
|
|
153
|
+
policy: FactoryDroidModelPolicy,
|
|
154
|
+
region: string | undefined,
|
|
155
|
+
): readonly string[] {
|
|
156
|
+
const eligible = policy.regionUpstreams[region === "eu" || region === "us" ? region : "global"];
|
|
157
|
+
return policy.rotation.filter(upstream => eligible.includes(upstream));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Native limits for an inference region; EU overrides fall back to the registry defaults. */
|
|
161
|
+
export function factoryDroidRegionalLimits(
|
|
162
|
+
policy: FactoryDroidModelPolicy,
|
|
163
|
+
region: string | undefined,
|
|
164
|
+
): FactoryDroidLimits {
|
|
165
|
+
if (region !== "eu") return policy.limits;
|
|
166
|
+
return {
|
|
167
|
+
contextWindow: policy.euLimits.contextWindow ?? policy.limits.contextWindow,
|
|
168
|
+
maxTokens: policy.euLimits.maxTokens ?? policy.limits.maxTokens,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -23,6 +23,7 @@ export type KnownProvider =
|
|
|
23
23
|
| "deepinfra"
|
|
24
24
|
| "deepseek"
|
|
25
25
|
| "devin"
|
|
26
|
+
| "factory-droid"
|
|
26
27
|
| "firepass"
|
|
27
28
|
| "fireworks"
|
|
28
29
|
| "github-copilot"
|
|
@@ -34,6 +35,7 @@ export type KnownProvider =
|
|
|
34
35
|
| "google-gemini-cli"
|
|
35
36
|
| "google-vertex"
|
|
36
37
|
| "groq"
|
|
38
|
+
| "helmcode"
|
|
37
39
|
| "huggingface"
|
|
38
40
|
| "kilo"
|
|
39
41
|
| "kimi-code"
|
package/src/compat/resolve.ts
CHANGED
|
@@ -31,13 +31,18 @@ import type {
|
|
|
31
31
|
ResolvedOpenAISharedCompat,
|
|
32
32
|
ThinkingConfig,
|
|
33
33
|
} from "../types";
|
|
34
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
isAnthropicSigningProxyUrl,
|
|
36
|
+
isAzureAnthropicRoute,
|
|
37
|
+
isBedrockAnthropicRoute,
|
|
38
|
+
isOfficialAnthropicApiUrl,
|
|
39
|
+
} from "./anthropic";
|
|
35
40
|
import { applyCompatOverrides } from "./apply";
|
|
36
41
|
import { API_COMPAT_RECORDS, AXES, type CompatRecordName } from "./axes";
|
|
37
42
|
import { hasModelScopedEffortsRule, resolveCascade } from "./cascade";
|
|
38
43
|
import { compareRevision, parseRevision, type Revision } from "./revision";
|
|
39
44
|
import { classifyModel, stripThinkingVariantSuffix } from "./taxonomy";
|
|
40
|
-
import type { ModelIdentity, ResolvedAxes, ResolveTarget } from "./types";
|
|
45
|
+
import type { ModelIdentity, RequestPolicy, ResolvedAxes, ResolveTarget } from "./types";
|
|
41
46
|
|
|
42
47
|
/** Result of resolving one model spec through the compat engine. */
|
|
43
48
|
export interface ResolvedModelPolicy<TApi extends Api = Api> {
|
|
@@ -46,6 +51,25 @@ export interface ResolvedModelPolicy<TApi extends Api = Api> {
|
|
|
46
51
|
thinking: ThinkingConfig | undefined;
|
|
47
52
|
/** Catalog-data axis assignments (longContext, priority, …) for generation. */
|
|
48
53
|
catalog: Record<string, unknown>;
|
|
54
|
+
/** Request-shaping directives for the selected upstream. */
|
|
55
|
+
request: RequestPolicy;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Request routing context is separate from model identity and deployment. */
|
|
59
|
+
export interface ResolveRoute {
|
|
60
|
+
upstream?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const REQUEST_AXIS_KEYS = Object.values(AXES)
|
|
64
|
+
.filter(axis => axis.records?.includes("request"))
|
|
65
|
+
.map(axis => axis.key);
|
|
66
|
+
|
|
67
|
+
function resolveRequestPolicy(axes: ResolvedAxes): RequestPolicy {
|
|
68
|
+
const policy: Record<string, unknown> = {};
|
|
69
|
+
for (const key of REQUEST_AXIS_KEYS) {
|
|
70
|
+
if (key in axes.wire) policy[key] = axes.wire[key];
|
|
71
|
+
}
|
|
72
|
+
return policy as RequestPolicy;
|
|
49
73
|
}
|
|
50
74
|
|
|
51
75
|
// ---------------------------------------------------------------------------
|
|
@@ -511,6 +535,7 @@ function detectOpenAICompat(
|
|
|
511
535
|
filterReasoningHistory: d.isOpenRouter && isAnthropicModel,
|
|
512
536
|
thinkingKeep: usesMoonshotKimiPreservedThinking ? "all" : undefined,
|
|
513
537
|
reasoningContentField: d.isClinePass ? "reasoning" : "reasoning_content",
|
|
538
|
+
mistralReasoningContentParts: undefined,
|
|
514
539
|
requiresReasoningContentForToolCalls:
|
|
515
540
|
(facts.is("kimi") && !d.isOpenCodeProvider) ||
|
|
516
541
|
(isDeepseekFamily && reasoningCapable) ||
|
|
@@ -519,6 +544,8 @@ function detectOpenAICompat(
|
|
|
519
544
|
requiresReasoningContentForAllAssistantTurns:
|
|
520
545
|
((isDeepseekFamily && reasoningCapable) || d.isXiaomiMimo) && !d.isOpenRouter,
|
|
521
546
|
allowsSyntheticReasoningContentForToolCalls: (!isDeepseekFamily || !reasoningCapable) && !d.isXiaomiMimo,
|
|
547
|
+
// Keep the typed sparse override key present for DeepSeek proxy replay.
|
|
548
|
+
syntheticReasoningContentFallback: undefined,
|
|
522
549
|
replayReasoningContent: d.isLocalOpenAICompatBackend,
|
|
523
550
|
qwenPreserveThinking:
|
|
524
551
|
(thinkingFormat === "qwen" || thinkingFormat === "qwen-chat-template") && d.isLocalOpenAICompatBackend,
|
|
@@ -751,6 +778,7 @@ function resolveOpenAIResponsesPolicy(
|
|
|
751
778
|
reasoningCapable,
|
|
752
779
|
requiresReasoningContentForAllAssistantTurns: isDeepseekFamily && reasoningCapable && !isOpenRouter,
|
|
753
780
|
allowsSyntheticReasoningContentForToolCalls: !isDeepseekFamily || !reasoningCapable,
|
|
781
|
+
syntheticReasoningContentFallback: undefined,
|
|
754
782
|
replayReasoningContent: false,
|
|
755
783
|
qwenPreserveThinking: false,
|
|
756
784
|
qwenTemplateReasoningEffort: false,
|
|
@@ -853,7 +881,9 @@ function resolveAnthropicPolicy(
|
|
|
853
881
|
const isCopilot = modelMatchesHost(spec, "githubCopilot");
|
|
854
882
|
const isZenmux = modelMatchesHost(spec, "zenmux");
|
|
855
883
|
const requiresThinkingEnabled = modelMatchesHost(spec, "moonshotNative") && facts.kimiMandatoryThinking;
|
|
856
|
-
const
|
|
884
|
+
const bedrockMessagesApi = isBedrockAnthropicRoute(baseUrl);
|
|
885
|
+
// Both routes reject the top-level tool `strict` field.
|
|
886
|
+
const rejectsStrictTools = isAzureAnthropicRoute(baseUrl) || bedrockMessagesApi;
|
|
857
887
|
const signingEndpoint = official || isCopilot || isZenmux || isAnthropicSigningProxyUrl(baseUrl);
|
|
858
888
|
const compat: ResolvedAnthropicCompat = {
|
|
859
889
|
officialEndpoint: official,
|
|
@@ -862,7 +892,9 @@ function resolveAnthropicPolicy(
|
|
|
862
892
|
supportsServerCompaction: false,
|
|
863
893
|
firstPartyProvider: false,
|
|
864
894
|
supportsOutputEffort: true,
|
|
865
|
-
disableStrictTools:
|
|
895
|
+
disableStrictTools: rejectsStrictTools,
|
|
896
|
+
// Present as a key so models.yml `compat` can set it; unset unless detected.
|
|
897
|
+
bedrockMessagesApi: bedrockMessagesApi || undefined,
|
|
866
898
|
disableAdaptiveThinking: false,
|
|
867
899
|
allowAnthropicHeaderOverrides: false,
|
|
868
900
|
supportsEagerToolInputStreaming: official,
|
|
@@ -872,6 +904,7 @@ function resolveAnthropicPolicy(
|
|
|
872
904
|
supportsMidConversationToolChanges: false,
|
|
873
905
|
supportsPerMessageEffort: false,
|
|
874
906
|
supportsThinkingBindingControls: false,
|
|
907
|
+
supportsBetweenToolsThinking: false,
|
|
875
908
|
supportsForcedToolChoice: !requiresThinkingEnabled && !facts.family("fable", "mythos"),
|
|
876
909
|
supportsSamplingParams: !facts.anthropicAdaptiveGenAtLeast("4.7"),
|
|
877
910
|
requiresToolResultId: false,
|
|
@@ -882,6 +915,9 @@ function resolveAnthropicPolicy(
|
|
|
882
915
|
injectClaudeCodeInstruction: true,
|
|
883
916
|
stripImageInput: false,
|
|
884
917
|
thinkingLoopGuard: undefined,
|
|
918
|
+
// Present as keys so models.yml `compat` can set them; unset unless a rule assigns them.
|
|
919
|
+
stripThinkingHistory: undefined,
|
|
920
|
+
fastMode: undefined,
|
|
885
921
|
streamIdleTimeoutMs: spec.compat?.streamIdleTimeoutMs,
|
|
886
922
|
};
|
|
887
923
|
applyWireAxes(compat, axes.wire, "anthropic-messages");
|
|
@@ -1197,7 +1233,15 @@ function fillExplicitThinking<TApi extends Api>(
|
|
|
1197
1233
|
(impliesMandatoryReasoning(facts, spec.id) || isQwenTemplateReasoningEffortCompat(compat)));
|
|
1198
1234
|
const needsDefaultLevel = thinking.defaultLevel === undefined && rule.defaultLevel !== undefined;
|
|
1199
1235
|
const needsPrefixBinding = thinking.prefixBinding === undefined && rule.prefixBinding === true;
|
|
1200
|
-
|
|
1236
|
+
const needsEffortBudgets = thinking.effortBudgets === undefined && rule.effortBudgets !== undefined;
|
|
1237
|
+
if (
|
|
1238
|
+
effortMap === undefined &&
|
|
1239
|
+
!needsDisplay &&
|
|
1240
|
+
!needsRequiresEffort &&
|
|
1241
|
+
!needsDefaultLevel &&
|
|
1242
|
+
!needsPrefixBinding &&
|
|
1243
|
+
!needsEffortBudgets
|
|
1244
|
+
) {
|
|
1201
1245
|
return thinking;
|
|
1202
1246
|
}
|
|
1203
1247
|
const filled: ThinkingConfig = { ...thinking };
|
|
@@ -1206,6 +1250,7 @@ function fillExplicitThinking<TApi extends Api>(
|
|
|
1206
1250
|
if (needsDefaultLevel && rule.defaultLevel !== undefined) filled.defaultLevel = rule.defaultLevel;
|
|
1207
1251
|
if (needsRequiresEffort) filled.requiresEffort = true;
|
|
1208
1252
|
if (needsPrefixBinding) filled.prefixBinding = true;
|
|
1253
|
+
if (needsEffortBudgets) filled.effortBudgets = rule.effortBudgets;
|
|
1209
1254
|
return filled;
|
|
1210
1255
|
}
|
|
1211
1256
|
|
|
@@ -1254,11 +1299,16 @@ export function resolveCatalogAxes(spec: ModelSpec<Api>): Record<string, unknown
|
|
|
1254
1299
|
* Resolves the full policy surface for one model spec: structured identity,
|
|
1255
1300
|
* complete compat record, thinking metadata, and catalog-data corrections.
|
|
1256
1301
|
*/
|
|
1257
|
-
export function resolveModelPolicy<TApi extends Api>(
|
|
1258
|
-
|
|
1302
|
+
export function resolveModelPolicy<TApi extends Api>(
|
|
1303
|
+
spec: ModelSpec<TApi>,
|
|
1304
|
+
route?: ResolveRoute,
|
|
1305
|
+
): ResolvedModelPolicy<TApi>;
|
|
1306
|
+
export function resolveModelPolicy(spec: ModelSpec<Api>, route?: ResolveRoute): ResolvedModelPolicy<Api> {
|
|
1259
1307
|
const identity = resolveIdentity(spec);
|
|
1260
1308
|
const facts = new IdentityFacts(identity);
|
|
1261
|
-
const
|
|
1309
|
+
const target = buildResolveTarget(spec, identity);
|
|
1310
|
+
target.upstream = route?.upstream;
|
|
1311
|
+
const axes = resolveCascade(target);
|
|
1262
1312
|
let compat: CompatOf<Api>;
|
|
1263
1313
|
if (specUsesApi(spec, "openrouter")) {
|
|
1264
1314
|
const chat = resolveOpenAICompletionsPolicy(spec, facts, axes);
|
|
@@ -1292,6 +1342,7 @@ export function resolveModelPolicy(spec: ModelSpec<Api>): ResolvedModelPolicy<Ap
|
|
|
1292
1342
|
compat,
|
|
1293
1343
|
thinking: resolveThinkingPolicy(spec, facts, axes, compat),
|
|
1294
1344
|
catalog: axes.catalog,
|
|
1345
|
+
request: resolveRequestPolicy(axes),
|
|
1295
1346
|
};
|
|
1296
1347
|
}
|
|
1297
1348
|
|