@ran-sh/dsh-crew 1.4.0 → 1.5.1
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/.claude-plugin/plugin.json +1 -1
- package/lib/client.js +5926 -5451
- package/package.json +1 -1
- package/src/client/index.tsx +159 -7
- package/src/hub/entry.mjs +6 -0
- package/src/hub/index.mjs +50 -0
- package/src/install/install-legacy.mjs +739 -717
- package/src/install/install.mjs +13 -5
- package/src/install/npx-lifecycle.mjs +16 -2
- package/src/jobs.mjs +17 -0
- package/src/model-routing.mjs +250 -147
- package/src/model-schedule.mjs +207 -0
- package/src/multimodal.mjs +0 -0
- package/src/policy.mjs +69 -62
- package/src/release-in-use.mjs +110 -0
- package/src/runtime-identity.mjs +1 -1
- package/src/server.mjs +6 -1
package/src/model-routing.mjs
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// routing boundary.
|
|
6
6
|
|
|
7
7
|
import { rankAdaptiveCandidates } from './adaptive-routing.mjs';
|
|
8
|
+
import { scheduleAdmission } from './model-schedule.mjs';
|
|
8
9
|
|
|
9
10
|
export const DEFAULT_TIER_MODEL_PREFERENCES = Object.freeze({
|
|
10
11
|
flash: 'deepseek-v4-flash',
|
|
@@ -26,36 +27,39 @@ export const MODEL_SELECTION_REASON_CODES = Object.freeze({
|
|
|
26
27
|
PROVIDER_UNAVAILABLE: 'PROVIDER_UNAVAILABLE',
|
|
27
28
|
PREFERRED_MODEL_UNAVAILABLE: 'PREFERRED_MODEL_UNAVAILABLE',
|
|
28
29
|
PREFERRED_MODEL_AMBIGUOUS: 'PREFERRED_MODEL_AMBIGUOUS',
|
|
29
|
-
ADAPTIVE_DEPRIORITIZED: 'ADAPTIVE_DEPRIORITIZED',
|
|
30
|
-
PROVIDER_TOMBSTONED: 'PROVIDER_TOMBSTONED',
|
|
31
|
-
CREDENTIAL_MISSING: 'CREDENTIAL_MISSING',
|
|
32
|
-
QUOTA_EXHAUSTED: 'QUOTA_EXHAUSTED',
|
|
33
|
-
RATE_LIMITED: 'RATE_LIMITED',
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
ADAPTIVE_DEPRIORITIZED: 'ADAPTIVE_DEPRIORITIZED',
|
|
31
|
+
PROVIDER_TOMBSTONED: 'PROVIDER_TOMBSTONED',
|
|
32
|
+
CREDENTIAL_MISSING: 'CREDENTIAL_MISSING',
|
|
33
|
+
QUOTA_EXHAUSTED: 'QUOTA_EXHAUSTED',
|
|
34
|
+
RATE_LIMITED: 'RATE_LIMITED',
|
|
35
|
+
PEAK_RESTRICTED: 'PEAK_RESTRICTED',
|
|
36
|
+
PEAK_ADVISORY: 'PEAK_ADVISORY',
|
|
37
|
+
PROBE_TIMEOUT: 'PROBE_TIMEOUT',
|
|
38
|
+
PROVIDER_INTERNAL_ERROR: 'PROVIDER_INTERNAL_ERROR',
|
|
39
|
+
HARNESS_DEFAULT_INVALID: 'HARNESS_DEFAULT_INVALID',
|
|
37
40
|
HARNESS_DEFAULT_PROVIDER_UNAVAILABLE: 'HARNESS_DEFAULT_PROVIDER_UNAVAILABLE',
|
|
38
41
|
PRIMARY_CANDIDATES_EXHAUSTED: 'PRIMARY_CANDIDATES_EXHAUSTED',
|
|
39
42
|
ESCALATION_CANDIDATES_EXHAUSTED: 'ESCALATION_CANDIDATES_EXHAUSTED',
|
|
40
|
-
NO_AVAILABLE_MODEL: 'NO_AVAILABLE_MODEL',
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
const HEALTH_BLOCK_REASONS = Object.freeze({
|
|
44
|
-
'credential-missing': MODEL_SELECTION_REASON_CODES.CREDENTIAL_MISSING,
|
|
45
|
-
'quota-exhausted': MODEL_SELECTION_REASON_CODES.QUOTA_EXHAUSTED,
|
|
46
|
-
'rate-limited': MODEL_SELECTION_REASON_CODES.RATE_LIMITED,
|
|
47
|
-
timeout: MODEL_SELECTION_REASON_CODES.PROBE_TIMEOUT,
|
|
48
|
-
'internal-error': MODEL_SELECTION_REASON_CODES.PROVIDER_INTERNAL_ERROR,
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
const BLOCKED_MODEL_CODES = Object.freeze({
|
|
52
|
-
[MODEL_SELECTION_REASON_CODES.CREDENTIAL_MISSING]: 'MODEL_BLOCKED_CREDENTIAL',
|
|
53
|
-
[MODEL_SELECTION_REASON_CODES.QUOTA_EXHAUSTED]: 'MODEL_BLOCKED_QUOTA',
|
|
54
|
-
[MODEL_SELECTION_REASON_CODES.RATE_LIMITED]: 'MODEL_BLOCKED_RATE_LIMIT',
|
|
55
|
-
[MODEL_SELECTION_REASON_CODES.PROBE_TIMEOUT]: 'MODEL_BLOCKED_TIMEOUT',
|
|
56
|
-
[MODEL_SELECTION_REASON_CODES.PROVIDER_INTERNAL_ERROR]: 'MODEL_BLOCKED_PROVIDER',
|
|
57
|
-
[MODEL_SELECTION_REASON_CODES.PROVIDER_TOMBSTONED]: 'MODEL_BLOCKED_TOMBSTONED',
|
|
58
|
-
|
|
43
|
+
NO_AVAILABLE_MODEL: 'NO_AVAILABLE_MODEL',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const HEALTH_BLOCK_REASONS = Object.freeze({
|
|
47
|
+
'credential-missing': MODEL_SELECTION_REASON_CODES.CREDENTIAL_MISSING,
|
|
48
|
+
'quota-exhausted': MODEL_SELECTION_REASON_CODES.QUOTA_EXHAUSTED,
|
|
49
|
+
'rate-limited': MODEL_SELECTION_REASON_CODES.RATE_LIMITED,
|
|
50
|
+
timeout: MODEL_SELECTION_REASON_CODES.PROBE_TIMEOUT,
|
|
51
|
+
'internal-error': MODEL_SELECTION_REASON_CODES.PROVIDER_INTERNAL_ERROR,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const BLOCKED_MODEL_CODES = Object.freeze({
|
|
55
|
+
[MODEL_SELECTION_REASON_CODES.CREDENTIAL_MISSING]: 'MODEL_BLOCKED_CREDENTIAL',
|
|
56
|
+
[MODEL_SELECTION_REASON_CODES.QUOTA_EXHAUSTED]: 'MODEL_BLOCKED_QUOTA',
|
|
57
|
+
[MODEL_SELECTION_REASON_CODES.RATE_LIMITED]: 'MODEL_BLOCKED_RATE_LIMIT',
|
|
58
|
+
[MODEL_SELECTION_REASON_CODES.PROBE_TIMEOUT]: 'MODEL_BLOCKED_TIMEOUT',
|
|
59
|
+
[MODEL_SELECTION_REASON_CODES.PROVIDER_INTERNAL_ERROR]: 'MODEL_BLOCKED_PROVIDER',
|
|
60
|
+
[MODEL_SELECTION_REASON_CODES.PROVIDER_TOMBSTONED]: 'MODEL_BLOCKED_TOMBSTONED',
|
|
61
|
+
[MODEL_SELECTION_REASON_CODES.PEAK_RESTRICTED]: 'MODEL_BLOCKED_PEAK',
|
|
62
|
+
});
|
|
59
63
|
|
|
60
64
|
export function normalizeModelRef(raw) {
|
|
61
65
|
if (!raw || typeof raw !== 'object') return null;
|
|
@@ -124,7 +128,7 @@ function traceBase({
|
|
|
124
128
|
};
|
|
125
129
|
}
|
|
126
130
|
|
|
127
|
-
function candidateDecision(ref, source, status, { reasonCode, advertised } = {}) {
|
|
131
|
+
function candidateDecision(ref, source, status, { reasonCode, advertised } = {}) {
|
|
128
132
|
return {
|
|
129
133
|
provider: ref?.provider ?? null,
|
|
130
134
|
model: ref?.model ?? null,
|
|
@@ -133,28 +137,40 @@ function candidateDecision(ref, source, status, { reasonCode, advertised } = {})
|
|
|
133
137
|
...(reasonCode ? { reason_code: reasonCode } : {}),
|
|
134
138
|
...(advertised === false ? { advertised: false } : {}),
|
|
135
139
|
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function healthAdmissionReason(ref, { healthStore, healthGate, tombstones } = {}) {
|
|
139
|
-
if (tombstones?.[ref?.provider] === 'absent') return MODEL_SELECTION_REASON_CODES.PROVIDER_TOMBSTONED;
|
|
140
|
-
if (healthGate !== 'hard-failures' || typeof healthStore?.get !== 'function') return null;
|
|
141
|
-
const observation = healthStore.get(ref?.provider, ref?.model);
|
|
142
|
-
if (observation?.fresh !== true) return null;
|
|
143
|
-
return HEALTH_BLOCK_REASONS[observation.state] ?? null;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function healthAdmissionReason(ref, { healthStore, healthGate, tombstones } = {}) {
|
|
143
|
+
if (tombstones?.[ref?.provider] === 'absent') return MODEL_SELECTION_REASON_CODES.PROVIDER_TOMBSTONED;
|
|
144
|
+
if (healthGate !== 'hard-failures' || typeof healthStore?.get !== 'function') return null;
|
|
145
|
+
const observation = healthStore.get(ref?.provider, ref?.model);
|
|
146
|
+
if (observation?.fresh !== true) return null;
|
|
147
|
+
return HEALTH_BLOCK_REASONS[observation.state] ?? null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Peak/off-peak admission for one candidate.
|
|
152
|
+
*
|
|
153
|
+
* Returns `null` when the model is unrestricted or off-peak; otherwise the
|
|
154
|
+
* schedule's verdict, which the caller applies. A `warn` rule is advisory — the
|
|
155
|
+
* candidate is still selected, and the trace records why.
|
|
156
|
+
*/
|
|
157
|
+
function peakAdmission(schedule, ref, at) {
|
|
158
|
+
if (!schedule) return null;
|
|
159
|
+
return scheduleAdmission(schedule, ref, at);
|
|
160
|
+
}
|
|
155
161
|
|
|
156
|
-
function
|
|
157
|
-
trace.
|
|
162
|
+
function blockedSelection(trace, ref, source, reasonCode) {
|
|
163
|
+
trace.fallback_reason = reasonCode;
|
|
164
|
+
return {
|
|
165
|
+
ok: false,
|
|
166
|
+
code: BLOCKED_MODEL_CODES[reasonCode] ?? 'MODEL_BLOCKED_PROVIDER',
|
|
167
|
+
message: `Model ${ref?.provider ?? 'unknown'}/${ref?.model ?? 'unknown'} is blocked by ${reasonCode}.`,
|
|
168
|
+
selection_trace: trace,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function selectTrace(trace, ref, source, { advertised, fallbackReason, reasonCode } = {}) {
|
|
173
|
+
trace.ordered_candidates.push(candidateDecision(ref, source, 'selected', { advertised, reasonCode }));
|
|
158
174
|
trace.selected = { provider: ref.provider, model: ref.model, source };
|
|
159
175
|
trace.selection_source = source;
|
|
160
176
|
trace.fallback_reason = fallbackReason ?? null;
|
|
@@ -190,6 +206,7 @@ export function buildDirectSelectionTrace({
|
|
|
190
206
|
model,
|
|
191
207
|
source = 'legacy-strict',
|
|
192
208
|
escalationReason = null,
|
|
209
|
+
peakAdvisory = false,
|
|
193
210
|
} = {}) {
|
|
194
211
|
const trace = traceBase({ role, logicalAttempt, modelClassHint, strategy, candidateSet, escalationReason });
|
|
195
212
|
const ref = normalizeModelRef({ provider, model });
|
|
@@ -197,7 +214,9 @@ export function buildDirectSelectionTrace({
|
|
|
197
214
|
trace.fallback_reason = MODEL_SELECTION_REASON_CODES.NO_AVAILABLE_MODEL;
|
|
198
215
|
return trace;
|
|
199
216
|
}
|
|
200
|
-
return selectTrace(trace, ref, source
|
|
217
|
+
return selectTrace(trace, ref, source, {
|
|
218
|
+
reasonCode: peakAdvisory ? MODEL_SELECTION_REASON_CODES.PEAK_ADVISORY : null,
|
|
219
|
+
});
|
|
201
220
|
}
|
|
202
221
|
|
|
203
222
|
/**
|
|
@@ -239,7 +258,7 @@ export function enrichSelectionTrace(trace, {
|
|
|
239
258
|
return base;
|
|
240
259
|
}
|
|
241
260
|
|
|
242
|
-
export function resolveWorkerModel({
|
|
261
|
+
export function resolveWorkerModel({
|
|
243
262
|
tier,
|
|
244
263
|
priority,
|
|
245
264
|
priorityConfigured = false,
|
|
@@ -248,13 +267,15 @@ export function resolveWorkerModel({
|
|
|
248
267
|
fallback = 'harness-default',
|
|
249
268
|
preferredModelId = DEFAULT_TIER_MODEL_PREFERENCES[tier],
|
|
250
269
|
traceContext = {},
|
|
251
|
-
adaptive,
|
|
252
|
-
adaptiveHealth,
|
|
253
|
-
healthStore,
|
|
254
|
-
healthGate,
|
|
255
|
-
allowFallback = true,
|
|
256
|
-
tombstones,
|
|
257
|
-
|
|
270
|
+
adaptive,
|
|
271
|
+
adaptiveHealth,
|
|
272
|
+
healthStore,
|
|
273
|
+
healthGate,
|
|
274
|
+
allowFallback = true,
|
|
275
|
+
tombstones,
|
|
276
|
+
schedule,
|
|
277
|
+
at,
|
|
278
|
+
} = {}) {
|
|
258
279
|
const providers = providerMap(catalog);
|
|
259
280
|
const normalizedPriority = normalizeModelPriority(priority);
|
|
260
281
|
const prioritySource = typeof traceContext.prioritySource === 'string' && traceContext.prioritySource
|
|
@@ -269,37 +290,57 @@ export function resolveWorkerModel({
|
|
|
269
290
|
escalationReason: traceContext.escalationReason ?? null,
|
|
270
291
|
});
|
|
271
292
|
|
|
293
|
+
// One clock read for the whole resolution. Calling `new Date()` per admission
|
|
294
|
+
// would let a resolution that straddles a peak boundary judge candidates
|
|
295
|
+
// against different instants — the filter and the all-blocked rescan below
|
|
296
|
+
// could then disagree about the same candidate.
|
|
297
|
+
const selectionAt = at ?? new Date();
|
|
298
|
+
|
|
272
299
|
// Explicit priority — including an intentionally empty configured list — is
|
|
273
300
|
// authoritative. Adaptive mode records that bypass but never reorders it.
|
|
274
301
|
if (priorityConfigured || normalizedPriority.length > 0) {
|
|
275
302
|
rankForTrace(trace, normalizedPriority, { adaptive, adaptiveHealth, explicitPriority: true });
|
|
276
303
|
}
|
|
277
304
|
|
|
278
|
-
for (let index = 0; index < normalizedPriority.length; index++) {
|
|
279
|
-
const ref = normalizedPriority[index];
|
|
280
|
-
const provider = providers.get(ref.provider);
|
|
305
|
+
for (let index = 0; index < normalizedPriority.length; index++) {
|
|
306
|
+
const ref = normalizedPriority[index];
|
|
307
|
+
const provider = providers.get(ref.provider);
|
|
281
308
|
if (!provider) {
|
|
282
309
|
trace.ordered_candidates.push(candidateDecision(ref, prioritySource, 'skipped', {
|
|
283
310
|
reasonCode: MODEL_SELECTION_REASON_CODES.PROVIDER_UNAVAILABLE,
|
|
284
|
-
}));
|
|
285
|
-
continue;
|
|
286
|
-
}
|
|
287
|
-
const advertised = (provider.models ?? []).some((model) => model?.id === ref.model);
|
|
288
|
-
const healthReason = healthAdmissionReason(ref, { healthStore, healthGate, tombstones });
|
|
289
|
-
if (healthReason) {
|
|
290
|
-
trace.ordered_candidates.push(candidateDecision(ref, prioritySource, 'skipped', {
|
|
291
|
-
reasonCode: healthReason, advertised,
|
|
292
|
-
}));
|
|
293
|
-
if (allowFallback === false) return blockedSelection(trace, ref, prioritySource, healthReason);
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
|
|
311
|
+
}));
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const advertised = (provider.models ?? []).some((model) => model?.id === ref.model);
|
|
315
|
+
const healthReason = healthAdmissionReason(ref, { healthStore, healthGate, tombstones });
|
|
316
|
+
if (healthReason) {
|
|
317
|
+
trace.ordered_candidates.push(candidateDecision(ref, prioritySource, 'skipped', {
|
|
318
|
+
reasonCode: healthReason, advertised,
|
|
319
|
+
}));
|
|
320
|
+
if (allowFallback === false) return blockedSelection(trace, ref, prioritySource, healthReason);
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
// A peak-blocked model is skipped so the next candidate can serve the job;
|
|
324
|
+
// only an explicit no-fallback call turns it into a hard failure.
|
|
325
|
+
const peak = peakAdmission(schedule, ref, selectionAt);
|
|
326
|
+
if (peak?.mode === 'block') {
|
|
327
|
+
trace.ordered_candidates.push(candidateDecision(ref, prioritySource, 'skipped', {
|
|
328
|
+
reasonCode: MODEL_SELECTION_REASON_CODES.PEAK_RESTRICTED, advertised,
|
|
329
|
+
}));
|
|
330
|
+
if (allowFallback === false) return blockedSelection(trace, ref, prioritySource, MODEL_SELECTION_REASON_CODES.PEAK_RESTRICTED);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
selectTrace(trace, ref, prioritySource, {
|
|
334
|
+
advertised,
|
|
335
|
+
reasonCode: peak?.mode === 'warn' ? MODEL_SELECTION_REASON_CODES.PEAK_ADVISORY : null,
|
|
336
|
+
});
|
|
297
337
|
return {
|
|
298
338
|
ok: true,
|
|
299
339
|
...ref,
|
|
300
340
|
source: 'priority',
|
|
301
341
|
matchedPriorityIndex: index,
|
|
302
342
|
...(advertised ? {} : { advertised: false }),
|
|
343
|
+
...(peak?.mode === 'warn' ? { peak_advisory: true } : {}),
|
|
303
344
|
selection_trace: trace,
|
|
304
345
|
};
|
|
305
346
|
}
|
|
@@ -312,47 +353,79 @@ export function resolveWorkerModel({
|
|
|
312
353
|
if ((provider.models ?? []).some((model) => model?.id === preferredModelId)) {
|
|
313
354
|
matches.push({ provider: provider.id, model: preferredModelId });
|
|
314
355
|
}
|
|
315
|
-
}
|
|
316
|
-
if (matches.length === 1) {
|
|
317
|
-
const preferred = matches[0];
|
|
318
|
-
const healthReason = healthAdmissionReason(preferred, { healthStore, healthGate, tombstones });
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
+
}
|
|
357
|
+
if (matches.length === 1) {
|
|
358
|
+
const preferred = matches[0];
|
|
359
|
+
const healthReason = healthAdmissionReason(preferred, { healthStore, healthGate, tombstones });
|
|
360
|
+
const peak = healthReason ? null : peakAdmission(schedule, preferred, selectionAt);
|
|
361
|
+
const blockReason = healthReason ?? (peak?.mode === 'block' ? MODEL_SELECTION_REASON_CODES.PEAK_RESTRICTED : null);
|
|
362
|
+
if (blockReason) {
|
|
363
|
+
trace.ordered_candidates.push(candidateDecision(preferred, 'preferred-default', 'skipped', {
|
|
364
|
+
reasonCode: blockReason,
|
|
365
|
+
}));
|
|
366
|
+
if (allowFallback === false) return blockedSelection(trace, preferred, 'preferred-default', blockReason);
|
|
367
|
+
} else {
|
|
368
|
+
rankForTrace(trace, matches, { adaptive, adaptiveHealth });
|
|
369
|
+
selectTrace(trace, preferred, 'preferred-default', {
|
|
370
|
+
reasonCode: peak?.mode === 'warn' ? MODEL_SELECTION_REASON_CODES.PEAK_ADVISORY : null,
|
|
371
|
+
});
|
|
372
|
+
return {
|
|
373
|
+
ok: true,
|
|
374
|
+
...preferred,
|
|
375
|
+
source: 'preferred-default',
|
|
376
|
+
...(peak?.mode === 'warn' ? { peak_advisory: true } : {}),
|
|
377
|
+
selection_trace: trace,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (matches.length > 1) {
|
|
382
|
+
// Keep each admitted candidate's advisory verdict alongside it: a `warn`
|
|
383
|
+
// rule is only advisory if the caller learns about it, and recomputing it
|
|
384
|
+
// after the pick would re-evaluate a clock already read once.
|
|
385
|
+
const verdicts = new Map();
|
|
386
|
+
const availableMatches = matches.filter((candidate) => {
|
|
387
|
+
const healthReason = healthAdmissionReason(candidate, { healthStore, healthGate, tombstones });
|
|
388
|
+
const peak = healthReason ? null : peakAdmission(schedule, candidate, selectionAt);
|
|
389
|
+
const reason = healthReason ?? (peak?.mode === 'block' ? MODEL_SELECTION_REASON_CODES.PEAK_RESTRICTED : null);
|
|
390
|
+
if (!reason) {
|
|
391
|
+
verdicts.set(modelRefKey(candidate), peak ?? null);
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
trace.ordered_candidates.push(candidateDecision(candidate, 'preferred-default', 'skipped', {
|
|
395
|
+
reasonCode: reason,
|
|
396
|
+
}));
|
|
397
|
+
return false;
|
|
398
|
+
});
|
|
399
|
+
if (availableMatches.length === 0) {
|
|
400
|
+
const blocked = matches.find((candidate) => healthAdmissionReason(candidate, { healthStore, healthGate, tombstones })
|
|
401
|
+
|| peakAdmission(schedule, candidate, selectionAt)?.mode === 'block');
|
|
402
|
+
if (blocked && allowFallback === false) {
|
|
403
|
+
const reason = healthAdmissionReason(blocked, { healthStore, healthGate, tombstones })
|
|
404
|
+
?? MODEL_SELECTION_REASON_CODES.PEAK_RESTRICTED;
|
|
405
|
+
return blockedSelection(trace, blocked, 'preferred-default', reason);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const preferredProvider = normalizeModelRef(harnessDefault)?.provider;
|
|
409
|
+
const deterministicMatch = availableMatches.find((candidate) => candidate.provider === preferredProvider) ?? null;
|
|
410
|
+
// Filtering can leave exactly one admissible candidate while removing the
|
|
411
|
+
// one that matched the Harness Default provider, which leaves both the
|
|
412
|
+
// deterministic match and the adaptive choice empty. Ambiguity only means
|
|
413
|
+
// something with two or more candidates left, so a lone survivor is picked
|
|
414
|
+
// outright rather than reported as ambiguous and dropped.
|
|
415
|
+
const soleMatch = availableMatches.length === 1 ? availableMatches[0] : null;
|
|
416
|
+
const baseline = deterministicMatch
|
|
417
|
+
? [deterministicMatch, ...availableMatches.filter((candidate) => candidate.provider !== deterministicMatch.provider)]
|
|
418
|
+
: availableMatches;
|
|
419
|
+
// Ranking still runs for a lone survivor so `trace.adaptive` reflects the
|
|
420
|
+
// configured policy in this shape too; the result is not consulted, because
|
|
421
|
+
// ranking orders candidates rather than admitting them and must never
|
|
422
|
+
// discard the only one that passed admission.
|
|
423
|
+
const ranked = rankForTrace(trace, soleMatch ? [soleMatch] : baseline, { adaptive, adaptiveHealth });
|
|
424
|
+
const adaptiveChoice = !soleMatch && ranked?.trace.decision_supported ? ranked.candidates[0] : null;
|
|
425
|
+
const match = soleMatch ?? adaptiveChoice ?? deterministicMatch;
|
|
426
|
+
if (match) {
|
|
427
|
+
const decisionOrder = soleMatch ? [] : adaptiveChoice ? ranked.candidates : availableMatches;
|
|
428
|
+
for (const candidate of decisionOrder) {
|
|
356
429
|
if (candidate.provider === match.provider && candidate.model === match.model) continue;
|
|
357
430
|
trace.ordered_candidates.push(candidateDecision(candidate, 'preferred-default', 'skipped', {
|
|
358
431
|
reasonCode: adaptiveChoice
|
|
@@ -360,10 +433,21 @@ export function resolveWorkerModel({
|
|
|
360
433
|
: MODEL_SELECTION_REASON_CODES.PREFERRED_MODEL_AMBIGUOUS,
|
|
361
434
|
}));
|
|
362
435
|
}
|
|
363
|
-
|
|
364
|
-
|
|
436
|
+
const verdict = verdicts.get(modelRefKey(match)) ?? null;
|
|
437
|
+
selectTrace(trace, match, 'preferred-default', {
|
|
438
|
+
reasonCode: verdict?.mode === 'warn' ? MODEL_SELECTION_REASON_CODES.PEAK_ADVISORY : null,
|
|
439
|
+
});
|
|
440
|
+
return {
|
|
441
|
+
ok: true,
|
|
442
|
+
...match,
|
|
443
|
+
source: 'preferred-default',
|
|
444
|
+
...(verdict?.mode === 'warn' ? { peak_advisory: true } : {}),
|
|
445
|
+
selection_trace: trace,
|
|
446
|
+
};
|
|
365
447
|
}
|
|
366
|
-
|
|
448
|
+
// Only the candidates still in play can be ambiguous; one already rejected
|
|
449
|
+
// for a concrete reason keeps that reason and appears once.
|
|
450
|
+
for (const candidate of availableMatches) {
|
|
367
451
|
trace.ordered_candidates.push(candidateDecision(candidate, 'preferred-default', 'skipped', {
|
|
368
452
|
reasonCode: MODEL_SELECTION_REASON_CODES.PREFERRED_MODEL_AMBIGUOUS,
|
|
369
453
|
}));
|
|
@@ -385,37 +469,52 @@ export function resolveWorkerModel({
|
|
|
385
469
|
|
|
386
470
|
if (fallback === 'harness-default') {
|
|
387
471
|
const defaultRef = normalizeModelRef(harnessDefault);
|
|
472
|
+
// The Harness Default is often also a priority or preferred candidate, and
|
|
473
|
+
// that candidate has already been judged. Recording it a second time with a
|
|
474
|
+
// different reason would make the trace contradict itself, so a ref that
|
|
475
|
+
// already has a verdict keeps it.
|
|
476
|
+
const alreadyJudged = defaultRef
|
|
477
|
+
? trace.ordered_candidates.some((row) => row.provider === defaultRef.provider && row.model === defaultRef.model)
|
|
478
|
+
: false;
|
|
388
479
|
if (!defaultRef) {
|
|
389
480
|
trace.ordered_candidates.push(candidateDecision(null, 'harness-default', 'skipped', {
|
|
390
481
|
reasonCode: MODEL_SELECTION_REASON_CODES.HARNESS_DEFAULT_INVALID,
|
|
391
482
|
}));
|
|
483
|
+
} else if (alreadyJudged) {
|
|
484
|
+
// Keep its existing verdict; nothing further to record.
|
|
392
485
|
} else if (!providers.has(defaultRef.provider)) {
|
|
393
486
|
trace.ordered_candidates.push(candidateDecision(defaultRef, 'harness-default', 'skipped', {
|
|
394
487
|
reasonCode: MODEL_SELECTION_REASON_CODES.HARNESS_DEFAULT_PROVIDER_UNAVAILABLE,
|
|
395
488
|
}));
|
|
396
489
|
} else {
|
|
397
|
-
const fallbackReason = traceContext.candidateSet === 'escalation'
|
|
398
|
-
? MODEL_SELECTION_REASON_CODES.ESCALATION_CANDIDATES_EXHAUSTED
|
|
399
|
-
: MODEL_SELECTION_REASON_CODES.PRIMARY_CANDIDATES_EXHAUSTED;
|
|
400
|
-
const healthReason = healthAdmissionReason(defaultRef, { healthStore, healthGate, tombstones });
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
490
|
+
const fallbackReason = traceContext.candidateSet === 'escalation'
|
|
491
|
+
? MODEL_SELECTION_REASON_CODES.ESCALATION_CANDIDATES_EXHAUSTED
|
|
492
|
+
: MODEL_SELECTION_REASON_CODES.PRIMARY_CANDIDATES_EXHAUSTED;
|
|
493
|
+
const healthReason = healthAdmissionReason(defaultRef, { healthStore, healthGate, tombstones });
|
|
494
|
+
const peak = healthReason ? null : peakAdmission(schedule, defaultRef, selectionAt);
|
|
495
|
+
const blockReason = healthReason ?? (peak?.mode === 'block' ? MODEL_SELECTION_REASON_CODES.PEAK_RESTRICTED : null);
|
|
496
|
+
if (blockReason) {
|
|
497
|
+
trace.ordered_candidates.push(candidateDecision(defaultRef, 'harness-default', 'skipped', {
|
|
498
|
+
reasonCode: blockReason,
|
|
499
|
+
}));
|
|
500
|
+
if (allowFallback === false) return blockedSelection(trace, defaultRef, 'harness-default', blockReason);
|
|
501
|
+
} else {
|
|
502
|
+
selectTrace(trace, defaultRef, 'harness-default', {
|
|
503
|
+
fallbackReason,
|
|
504
|
+
reasonCode: peak?.mode === 'warn' ? MODEL_SELECTION_REASON_CODES.PEAK_ADVISORY : null,
|
|
505
|
+
});
|
|
506
|
+
return {
|
|
409
507
|
ok: true,
|
|
410
508
|
...defaultRef,
|
|
411
509
|
source: 'harness-default',
|
|
510
|
+
...(peak?.mode === 'warn' ? { peak_advisory: true } : {}),
|
|
412
511
|
...(typeof harnessDefault.reasoningEffort === 'string' && harnessDefault.reasoningEffort
|
|
413
512
|
? { reasoningEffort: harnessDefault.reasoningEffort }
|
|
414
513
|
: {}),
|
|
415
|
-
selection_trace: trace,
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
}
|
|
514
|
+
selection_trace: trace,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
}
|
|
419
518
|
}
|
|
420
519
|
trace.fallback_reason = MODEL_SELECTION_REASON_CODES.NO_AVAILABLE_MODEL;
|
|
421
520
|
return {
|
|
@@ -435,18 +534,20 @@ export function resolveWorkerModel({
|
|
|
435
534
|
* The output shape matches resolveWorkerModel (provider/model/source/...) plus
|
|
436
535
|
* role + attempt so selection provenance lands in job metadata.
|
|
437
536
|
*/
|
|
438
|
-
export function resolveModel({
|
|
537
|
+
export function resolveModel({
|
|
439
538
|
role = 'worker',
|
|
440
539
|
attempt = 0,
|
|
441
540
|
policy,
|
|
442
541
|
catalog,
|
|
443
542
|
harnessDefault,
|
|
444
|
-
adaptiveHealth,
|
|
445
|
-
healthStore,
|
|
446
|
-
healthGate,
|
|
447
|
-
allowFallback = true,
|
|
448
|
-
tombstones,
|
|
449
|
-
|
|
543
|
+
adaptiveHealth,
|
|
544
|
+
healthStore,
|
|
545
|
+
healthGate,
|
|
546
|
+
allowFallback = true,
|
|
547
|
+
tombstones,
|
|
548
|
+
schedule,
|
|
549
|
+
at,
|
|
550
|
+
} = {}) {
|
|
450
551
|
const p = policy && typeof policy === 'object' ? policy : {};
|
|
451
552
|
const escalated = Number.isInteger(attempt) && attempt > 0;
|
|
452
553
|
const candidates = escalated ? p.escalation_priority : p.priority;
|
|
@@ -464,12 +565,14 @@ export function resolveModel({
|
|
|
464
565
|
harnessDefault,
|
|
465
566
|
fallback: p.fallback === 'harness-default' ? 'harness-default' : 'harness-default',
|
|
466
567
|
preferredModelId,
|
|
467
|
-
adaptive: p.adaptive,
|
|
468
|
-
adaptiveHealth,
|
|
469
|
-
healthStore,
|
|
470
|
-
healthGate,
|
|
471
|
-
allowFallback,
|
|
472
|
-
tombstones,
|
|
568
|
+
adaptive: p.adaptive,
|
|
569
|
+
adaptiveHealth,
|
|
570
|
+
healthStore,
|
|
571
|
+
healthGate,
|
|
572
|
+
allowFallback,
|
|
573
|
+
tombstones,
|
|
574
|
+
schedule,
|
|
575
|
+
at,
|
|
473
576
|
traceContext: {
|
|
474
577
|
role,
|
|
475
578
|
logicalAttempt: attempt,
|