@polderlabs/bizar 10.23.10 → 10.23.11
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/package.json +1 -1
- package/packages/sdk/dist/router/agent-model-registry.d.ts +7 -2
- package/packages/sdk/dist/router/agent-model-registry.js +38 -10
- package/packages/sdk/dist/router/failover-mirror.mjs +9 -5
- package/packages/sdk/dist/version.d.ts +1 -1
- package/packages/sdk/dist/version.js +1 -1
- package/packages/sdk/package.json +1 -1
package/package.json
CHANGED
|
@@ -225,8 +225,13 @@ export declare function rankUserSelectedForRole(registry: ModelRegistry, role: s
|
|
|
225
225
|
};
|
|
226
226
|
/**
|
|
227
227
|
* Pure sorter extracted for testability. Mutates and returns the input
|
|
228
|
-
* array. Sort key: `(eligible desc,
|
|
229
|
-
*
|
|
228
|
+
* array. Sort key: `(eligible desc, originalIndex asc, capabilityScore desc,
|
|
229
|
+
* hasProfile desc)`. The `originalIndex` primary sort key honours the
|
|
230
|
+
* operator's `userSelected.models` ordering as the authoritative pick
|
|
231
|
+
* sequence — the same contract as the CLI's `resolveDispatchModel`. The
|
|
232
|
+
* capability score is a secondary tiebreaker for entries that share the
|
|
233
|
+
* same `originalIndex` position (i.e., when a profile lookup changes
|
|
234
|
+
* eligibility status without reordering).
|
|
230
235
|
*/
|
|
231
236
|
export declare function compareRankedEntries(a: RankedUserSelectedEntry, b: RankedUserSelectedEntry): number;
|
|
232
237
|
/**
|
|
@@ -85,8 +85,6 @@ export function loadModelRegistry(src = {}) {
|
|
|
85
85
|
if (!isRecord(value))
|
|
86
86
|
registryError("CONFIG_INVALID", `Tier ${name} is invalid.`);
|
|
87
87
|
const ids = modelIds(value.models);
|
|
88
|
-
if (ids.length === 0)
|
|
89
|
-
registryError("CONFIG_INVALID", `Tier ${name} has no candidate models.`);
|
|
90
88
|
tiers.set(name, { tier: name, modelIds: ids, purpose: String(value.purpose || ""), effort: typeof value.effort === "string" ? value.effort : null });
|
|
91
89
|
}
|
|
92
90
|
const roleDefaults = new Map();
|
|
@@ -98,6 +96,25 @@ export function loadModelRegistry(src = {}) {
|
|
|
98
96
|
const configuredEndpoint = typeof raw.endpoint === "string" ? raw.endpoint : typeof gateway.endpoint === "string" ? gateway.endpoint : null;
|
|
99
97
|
const endpoint = process.env.BIZAR_MODEL_ROUTER_URL ?? process.env.ANTHROPIC_BASE_URL ?? configuredEndpoint;
|
|
100
98
|
const userSelected = parseUserSelected(raw.userSelected);
|
|
99
|
+
// Empty tier lists are tolerated when the operator's `userSelected`
|
|
100
|
+
// pool is non-empty: the runtime resolver (resolveTierModel) prefers
|
|
101
|
+
// the operator's authoritative picks and only falls through to a
|
|
102
|
+
// tier's static model list when no user pick matches. The CLI mirror
|
|
103
|
+
// (config/agents/model-assignment.mjs#resolveDispatchModel) applies
|
|
104
|
+
// the same rule; this keeps SDK and CLI symmetric on the canonical
|
|
105
|
+
// template, which ships empty tier lists by design (F-201 follow-up:
|
|
106
|
+
// remove every shipped provider/model default). When `userSelected`
|
|
107
|
+
// is also empty, every tier must still carry at least one candidate
|
|
108
|
+
// so the resolver never dispatches with no model at all.
|
|
109
|
+
if (!(userSelected && userSelected.models.length > 0)) {
|
|
110
|
+
for (const [name, value] of Object.entries(raw.tiers)) {
|
|
111
|
+
if (!isRecord(value))
|
|
112
|
+
registryError("CONFIG_INVALID", `Tier ${name} is invalid.`);
|
|
113
|
+
const ids = modelIds(value.models);
|
|
114
|
+
if (ids.length === 0)
|
|
115
|
+
registryError("CONFIG_INVALID", `Tier ${name} has no candidate models.`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
101
118
|
return { version: String(raw.version || "unknown"), endpoint, configuredEndpoint, gateway, policies: raw.policies, tiers, roleDefaults, ...(userSelected ? { userSelected } : {}) };
|
|
102
119
|
}
|
|
103
120
|
function parseUserSelected(raw) {
|
|
@@ -298,24 +315,35 @@ export function rankUserSelectedForRole(registry, role, requirements = {}) {
|
|
|
298
315
|
return entry;
|
|
299
316
|
});
|
|
300
317
|
ranked.sort(compareRankedEntries);
|
|
301
|
-
|
|
302
|
-
//
|
|
303
|
-
|
|
318
|
+
// Tier-aware filtering (parity with the CLI's
|
|
319
|
+
// resolveDispatchModel#hinted): when the caller asks for a specific
|
|
320
|
+
// tier (the common dispatch path passes the agent's role-default
|
|
321
|
+
// tier), narrow `eligible` to entries whose tier hint matches. When
|
|
322
|
+
// no entry matches the requested tier, fall back to the full eligible
|
|
323
|
+
// ranking so a stale tierHints map never strands a dispatch with no
|
|
324
|
+
// candidates at all — same fail-open shape as the CLI.
|
|
325
|
+
const tierMatched = role ? ranked.filter((entry) => entry.eligible && entry.tier === role) : ranked.filter((entry) => entry.eligible);
|
|
326
|
+
const eligible = tierMatched.length > 0 ? tierMatched : ranked.filter((entry) => entry.eligible);
|
|
304
327
|
return { ranked, eligible };
|
|
305
328
|
}
|
|
306
329
|
/**
|
|
307
330
|
* Pure sorter extracted for testability. Mutates and returns the input
|
|
308
|
-
* array. Sort key: `(eligible desc,
|
|
309
|
-
*
|
|
331
|
+
* array. Sort key: `(eligible desc, originalIndex asc, capabilityScore desc,
|
|
332
|
+
* hasProfile desc)`. The `originalIndex` primary sort key honours the
|
|
333
|
+
* operator's `userSelected.models` ordering as the authoritative pick
|
|
334
|
+
* sequence — the same contract as the CLI's `resolveDispatchModel`. The
|
|
335
|
+
* capability score is a secondary tiebreaker for entries that share the
|
|
336
|
+
* same `originalIndex` position (i.e., when a profile lookup changes
|
|
337
|
+
* eligibility status without reordering).
|
|
310
338
|
*/
|
|
311
339
|
export function compareRankedEntries(a, b) {
|
|
312
340
|
if (a.eligible !== b.eligible)
|
|
313
341
|
return a.eligible ? -1 : 1;
|
|
342
|
+
if (a.originalIndex !== b.originalIndex)
|
|
343
|
+
return a.originalIndex - b.originalIndex;
|
|
314
344
|
if (a.capabilityScore !== b.capabilityScore)
|
|
315
345
|
return b.capabilityScore - a.capabilityScore;
|
|
316
|
-
|
|
317
|
-
return a.hasProfile ? -1 : 1;
|
|
318
|
-
return a.originalIndex - b.originalIndex;
|
|
346
|
+
return a.hasProfile !== b.hasProfile ? (a.hasProfile ? -1 : 1) : 0;
|
|
319
347
|
}
|
|
320
348
|
/**
|
|
321
349
|
* Weighted capability score: reasoning=0.3, toolCall=0.25,
|
|
@@ -175,13 +175,17 @@ export function rankUserSelectedForRole(registry, role, requirements = {}) {
|
|
|
175
175
|
});
|
|
176
176
|
ranked.sort((a, b) => {
|
|
177
177
|
if (a.eligible !== b.eligible) return a.eligible ? -1 : 1;
|
|
178
|
+
if (a.originalIndex !== b.originalIndex) return a.originalIndex - b.originalIndex;
|
|
178
179
|
if (a.capabilityScore !== b.capabilityScore) return b.capabilityScore - a.capabilityScore;
|
|
179
|
-
|
|
180
|
-
return a.originalIndex - b.originalIndex;
|
|
180
|
+
return a.hasProfile !== b.hasProfile ? (a.hasProfile ? -1 : 1) : 0;
|
|
181
181
|
});
|
|
182
|
-
|
|
183
|
-
//
|
|
184
|
-
|
|
182
|
+
// Tier-aware filtering (parity with the CLI's
|
|
183
|
+
// resolveDispatchModel#hinted): narrow `eligible` to entries whose
|
|
184
|
+
// tier hint matches the requested tier; fall back to the full eligible
|
|
185
|
+
// ranking when no tier-matched candidate exists so a stale tierHints
|
|
186
|
+
// map never strands a dispatch with no candidates at all.
|
|
187
|
+
const tierMatched = role ? ranked.filter((entry) => entry.eligible && entry.tier === role) : ranked.filter((entry) => entry.eligible);
|
|
188
|
+
const eligible = tierMatched.length > 0 ? tierMatched : ranked.filter((entry) => entry.eligible);
|
|
185
189
|
return { ranked, eligible };
|
|
186
190
|
}
|
|
187
191
|
|