modelrig 0.2.0 → 0.4.0

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.
Files changed (81) hide show
  1. package/CHANGELOG.md +204 -0
  2. package/README.md +68 -4
  3. package/dist/adapters/deepseek.d.ts.map +1 -1
  4. package/dist/adapters/deepseek.js +8 -1
  5. package/dist/adapters/gemini.d.ts +16 -0
  6. package/dist/adapters/gemini.d.ts.map +1 -1
  7. package/dist/adapters/gemini.js +44 -3
  8. package/dist/adapters/grok.d.ts +7 -0
  9. package/dist/adapters/grok.d.ts.map +1 -1
  10. package/dist/adapters/grok.js +23 -1
  11. package/dist/adapters/openai-compat.d.ts +7 -0
  12. package/dist/adapters/openai-compat.d.ts.map +1 -1
  13. package/dist/adapters/openai-compat.js +24 -1
  14. package/dist/adapters/openai.d.ts +7 -0
  15. package/dist/adapters/openai.d.ts.map +1 -1
  16. package/dist/adapters/openai.js +23 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +5 -4
  19. package/dist/config.d.ts +7 -1
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/index.d.ts +6 -6
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +28 -25
  24. package/dist/init/init-cli.d.ts.map +1 -1
  25. package/dist/init/init-cli.js +2 -1
  26. package/dist/init/observe-cli.d.ts.map +1 -1
  27. package/dist/init/observe-cli.js +2 -1
  28. package/dist/routes/loader.d.ts +11 -0
  29. package/dist/routes/loader.d.ts.map +1 -1
  30. package/dist/routes/loader.js +29 -0
  31. package/dist/routes/sampling-config.d.ts.map +1 -1
  32. package/dist/routes/sampling-config.js +11 -0
  33. package/dist/run/dispatch.d.ts +14 -2
  34. package/dist/run/dispatch.d.ts.map +1 -1
  35. package/dist/run/dispatch.js +31 -2
  36. package/dist/run/ladder-key.d.ts +40 -0
  37. package/dist/run/ladder-key.d.ts.map +1 -0
  38. package/dist/run/ladder-key.js +39 -0
  39. package/dist/run/raw-support.d.ts +123 -0
  40. package/dist/run/raw-support.d.ts.map +1 -0
  41. package/dist/run/raw-support.js +150 -0
  42. package/dist/run/raw.d.ts +73 -1
  43. package/dist/run/raw.d.ts.map +1 -1
  44. package/dist/run/raw.js +129 -8
  45. package/dist/run/rawonly.d.ts +60 -0
  46. package/dist/run/rawonly.d.ts.map +1 -0
  47. package/dist/run/rawonly.js +120 -0
  48. package/dist/run/record-attempt.d.ts +14 -1
  49. package/dist/run/record-attempt.d.ts.map +1 -1
  50. package/dist/run/record-attempt.js +43 -2
  51. package/dist/run/resolve.d.ts +4 -0
  52. package/dist/run/resolve.d.ts.map +1 -1
  53. package/dist/run/resolve.js +9 -2
  54. package/dist/run/routing-docs.d.ts +73 -0
  55. package/dist/run/routing-docs.d.ts.map +1 -0
  56. package/dist/run/routing-docs.js +222 -0
  57. package/dist/run/run.d.ts.map +1 -1
  58. package/dist/run/run.js +24 -22
  59. package/dist/telemetry/exporter.d.ts.map +1 -1
  60. package/dist/telemetry/exporter.js +6 -0
  61. package/dist/telemetry/ingest-protocol.d.ts +7 -0
  62. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  63. package/dist/telemetry/load-error-report.d.ts.map +1 -1
  64. package/dist/telemetry/load-error-report.js +4 -1
  65. package/dist/telemetry/otel-map.d.ts.map +1 -1
  66. package/dist/telemetry/otel-map.js +8 -0
  67. package/dist/telemetry/store-migrations.d.ts +15 -0
  68. package/dist/telemetry/store-migrations.d.ts.map +1 -0
  69. package/dist/telemetry/store-migrations.js +119 -0
  70. package/dist/telemetry/store.d.ts.map +1 -1
  71. package/dist/telemetry/store.js +26 -93
  72. package/dist/types/failures-teaching.js +1 -1
  73. package/dist/types/provider.d.ts +38 -0
  74. package/dist/types/provider.d.ts.map +1 -1
  75. package/dist/types/route.d.ts +6 -1
  76. package/dist/types/route.d.ts.map +1 -1
  77. package/dist/types/telemetry.d.ts +31 -0
  78. package/dist/types/telemetry.d.ts.map +1 -1
  79. package/dist/validate-cli.d.ts.map +1 -1
  80. package/dist/validate-cli.js +17 -4
  81. package/package.json +1 -1
package/dist/run/raw.js CHANGED
@@ -24,8 +24,26 @@
24
24
  * `AdapterRequest.sampling`) when the caller supplies them; absent, they are
25
25
  * still dropped and the dispatch is byte-identical to pre-CE-6. `max_tokens`
26
26
  * maps to `maxOutputTokens` (unchanged).
27
+ * - CE-2: the raw lane exposes three more provider knobs — `grounding`
28
+ * (native web grounding), `serviceTier`, and `reasoning` (thinking effort).
29
+ * Each is ADDITIVE: absent ⇒ byte-identical dispatch. A DECLARED directive is
30
+ * never silently dropped — `grounding`/`reasoning` on a provider that cannot
31
+ * honor it are rejected fail-closed PRE-DISPATCH (`assertRawKnobsSupported`),
32
+ * with no meter, no provider call, and no telemetry row. `serviceTier`
33
+ * forwards to every adapter's tier path and `meta.requestedTier` now reports
34
+ * the DECLARED tier (an honesty fix — it previously hardcoded `"standard"`).
35
+ * - CE-9: multi-provider parity. `grounding` now also serves grok (xAI Live
36
+ * Search); `reasoning` generalizes beyond gemini WITHOUT a per-model
37
+ * allowlist — openai/deepinfra/fireworks forward `level` → `reasoning_effort`
38
+ * verbatim (provider enforces, rejection classified teachably), grok coarsens
39
+ * (disclosed), gemini stays model-gated, deepseek/anthropic fail closed; a
40
+ * new `responseFormat: "json_object"` knob mime-shapes on the OpenAI-dialect
41
+ * providers + gemini (anthropic fails closed). The provider×knob support set
42
+ * is ONE table (`run/raw-support.ts`) the guard AND the published docs matrix
43
+ * both read — they can never disagree (R5).
27
44
  */
28
45
  Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.assertRawKnobsSupported = assertRawKnobsSupported;
29
47
  exports.buildRawRequest = buildRawRequest;
30
48
  exports.runRaw = runRaw;
31
49
  exports.runRawStream = runRawStream;
@@ -38,6 +56,7 @@ const openai_1 = require("../adapters/openai");
38
56
  const openai_compat_1 = require("../adapters/openai-compat");
39
57
  const cost_1 = require("./cost");
40
58
  const resolve_1 = require("./resolve");
59
+ const raw_support_1 = require("./raw-support");
41
60
  const failures_1 = require("../types/failures");
42
61
  /** Absent a route timeout, the raw lane uses a generous default — a passthrough
43
62
  * should not be shorter-fused than the underlying provider SDK. */
@@ -76,6 +95,72 @@ function buildRawAdapter(provider, apiKey) {
76
95
  }
77
96
  }
78
97
  }
98
+ /**
99
+ * CE-2/CE-9 fail-closed guard (spec §3 F1/F3, CE-9 R1/R2/R4). A DECLARED
100
+ * directive is NEVER silently dropped: a `grounding`/`responseFormat` on a
101
+ * provider that cannot honor it, a `reasoning` on a provider with NO effort
102
+ * control (deepseek/anthropic), or a `grounding.mode` other than the
103
+ * v1-supported `"native"` throws `RigFailureError` `invariant_violation` (the
104
+ * existing TERMINAL class; no new FailureClass member) BEFORE any adapter is
105
+ * built, any provider call is made, or any telemetry row is written.
106
+ *
107
+ * The support facts come from `RAW_KNOB_SUPPORT` (run/raw-support.ts) — the ONE
108
+ * table the published docs matrix also reads, so the guard and the docs can
109
+ * never disagree (R5). What the guard does NOT reject (and why):
110
+ * - reasoning on openai/deepinfra/fireworks is FORWARDED verbatim
111
+ * (`kind: "forward"`) and the provider enforces — a rejection is classified
112
+ * teachably by the adapter (no per-model allowlist, R1).
113
+ * - reasoning on grok is a DISCLOSED coarsening (`kind: "coarsen"`).
114
+ * - reasoning on gemini is model-gated (`kind: "model-gated"`) — the adapter
115
+ * rejects a non-thinking model (`geminiReasoningUnsupported`).
116
+ * Exported so the guard is unit-testable without a network. Idempotent and
117
+ * side-effect-free.
118
+ */
119
+ function assertRawKnobsSupported(input) {
120
+ const support = raw_support_1.RAW_KNOB_SUPPORT[input.provider];
121
+ const groundingMode = input.grounding?.mode;
122
+ if (groundingMode !== undefined) {
123
+ if (groundingMode !== "native") {
124
+ throw new failures_1.RigFailureError({
125
+ class: "invariant_violation",
126
+ message: `unsupported raw-lane grounding mode "${groundingMode}"`,
127
+ provider: input.provider,
128
+ model: input.model,
129
+ fixHint: 'only { mode: "native" } grounding is supported on the raw lane in v1',
130
+ });
131
+ }
132
+ if (!support.grounding.supported) {
133
+ throw new failures_1.RigFailureError({
134
+ class: "invariant_violation",
135
+ message: `native grounding on the raw lane is not supported for provider "${input.provider}"`,
136
+ provider: input.provider,
137
+ model: input.model,
138
+ fixHint: `native grounding on the raw lane is supported on ${(0, raw_support_1.groundingSupportedProviders)().join(", ")} only; remove grounding or switch provider`,
139
+ });
140
+ }
141
+ }
142
+ if (input.reasoning !== undefined && support.reasoning.kind === "fail-closed") {
143
+ throw new failures_1.RigFailureError({
144
+ class: "invariant_violation",
145
+ message: `reasoning control on the raw lane is not supported for provider "${input.provider}"`,
146
+ provider: input.provider,
147
+ model: input.model,
148
+ // Provider-specific teaching straight from the matrix (deepseek →
149
+ // deepseek-reasoner; anthropic → extended thinking unwired).
150
+ fixHint: support.reasoning.fixHint ??
151
+ "reasoning control is not available for this provider on the raw lane; remove reasoning",
152
+ });
153
+ }
154
+ if (input.responseFormat !== undefined && !support.responseFormat.supported) {
155
+ throw new failures_1.RigFailureError({
156
+ class: "invariant_violation",
157
+ message: `responseFormat on the raw lane is not supported for provider "${input.provider}"`,
158
+ provider: input.provider,
159
+ model: input.model,
160
+ fixHint: `responseFormat: "json_object" on the raw lane is supported on ${(0, raw_support_1.responseFormatSupportedProviders)().join(", ")} only; remove responseFormat or switch provider`,
161
+ });
162
+ }
163
+ }
79
164
  /** Pure raw-lane request shaping — exported for unit tests (no network). */
80
165
  function buildRawRequest(candidate, input) {
81
166
  // CE-6: forward temperature/topP through AdapterRequest.sampling ONLY when the
@@ -93,10 +178,22 @@ function buildRawRequest(candidate, input) {
93
178
  systemPrompt: input.systemPrompt,
94
179
  userPrompt: input.userPrompt,
95
180
  outputSchema: null, // raw lane never validates — the product distinction
96
- requestedTier: "standard",
181
+ // CE-2 F2: forward the DECLARED tier (default "standard"); the meta/row
182
+ // honesty fix in finalizeRaw reports this same value, never a hardcode.
183
+ requestedTier: input.serviceTier ?? "standard",
97
184
  timeoutMs: input.timeoutMs ?? DEFAULT_RAW_TIMEOUT_MS,
98
185
  ...(input.maxOutputTokens !== undefined ? { maxOutputTokens: input.maxOutputTokens } : {}),
99
186
  ...(sampling !== undefined ? { sampling } : {}),
187
+ // CE-2 F1: `{ mode: "native" }` → the adapter's native-grounding directive.
188
+ // Any other mode / unsupported provider never reaches here — the guard
189
+ // (assertRawKnobsSupported) rejects it pre-dispatch. Absent ⇒ no key added.
190
+ ...(input.grounding?.mode === "native" ? { grounding: true } : {}),
191
+ // CE-2 F3: forward reasoning verbatim; absent ⇒ no key added (byte-identical).
192
+ ...(input.reasoning !== undefined ? { reasoning: input.reasoning } : {}),
193
+ // CE-9 R4: forward the json_object directive verbatim; absent ⇒ no key added
194
+ // (byte-identical). An unsupported provider (anthropic) never reaches here —
195
+ // the guard (assertRawKnobsSupported) rejects it pre-dispatch.
196
+ ...(input.responseFormat !== undefined ? { responseFormat: input.responseFormat } : {}),
100
197
  ...(input.tools !== undefined ? { tools: input.tools } : {}),
101
198
  ...(input.toolChoice !== undefined ? { toolChoice: input.toolChoice } : {}),
102
199
  ...(input.cache !== undefined ? { cache: input.cache } : {}),
@@ -115,7 +212,7 @@ function recordRawRow(telemetry, p) {
115
212
  routeVersion: 0,
116
213
  provider: p.provider,
117
214
  model: p.model,
118
- requestedTier: "standard",
215
+ requestedTier: p.requestedTier,
119
216
  servedTier: p.usage?.servedTier ?? "standard",
120
217
  tokensIn: p.usage?.tokensIn ?? 0,
121
218
  tokensOut: p.usage?.tokensOut ?? 0,
@@ -127,7 +224,10 @@ function recordRawRow(telemetry, p) {
127
224
  ttfbMs: p.usage?.ttfbMs ?? null,
128
225
  failureClass: p.failureClass,
129
226
  attemptsJson: "{}",
130
- tagsJson: JSON.stringify(p.tags),
227
+ // Rung tag (R1 review, F1): the raw lane is a single primary dispatch. Raw
228
+ // rows carry `lane: "raw"` and are excluded from route rollups, but every row
229
+ // gets a rung for a uniform contract.
230
+ tagsJson: JSON.stringify({ ...p.tags, rung: "primary" }),
131
231
  fingerprintCell: null,
132
232
  servedVariant: null,
133
233
  repairedBy: null,
@@ -137,6 +237,10 @@ function recordRawRow(telemetry, p) {
137
237
  // provenance rule reads any non-null key here as `customer`.
138
238
  cacheKey: p.cacheKey ?? null,
139
239
  prefixFingerprint: null,
240
+ // Ladder key (spec §2.1 R0): raw lane = one row per call, seq 1, index 0.
241
+ callId: p.callId,
242
+ attemptSeq: 1,
243
+ candidateIndex: 0,
140
244
  };
141
245
  telemetry.record(row);
142
246
  }
@@ -149,6 +253,11 @@ function recordRawRow(telemetry, p) {
149
253
  function finalizeRaw(deps, input, result, latencyMs) {
150
254
  const unpricedRatePerTok = (deps.unpricedRateUsdPerMTok ?? cost_1.DEFAULT_UNPRICED_RATE_USD_PER_MTOK) / 1000000;
151
255
  const rawModelId = `${input.provider}/${input.model}`;
256
+ // One call_id for this raw invocation — shared by the (single) recorded row and
257
+ // the returned meta (spec §2.1 R0).
258
+ const callId = (0, node_crypto_1.randomUUID)();
259
+ // CE-2 F2: the declared tier is reported truthfully on the row AND the meta.
260
+ const requestedTier = input.serviceTier ?? "standard";
152
261
  // lane is SDK-owned and cannot be overridden by a caller-supplied tag.
153
262
  const tags = { ...(input.tags ?? {}), lane: "raw" };
154
263
  if (!result.ok) {
@@ -167,9 +276,11 @@ function finalizeRaw(deps, input, result, latencyMs) {
167
276
  ...(result.usage !== undefined ? { usage: result.usage } : {}),
168
277
  costUsd,
169
278
  latencyMs,
279
+ requestedTier,
170
280
  failureClass: result.failure.class,
171
281
  tags,
172
282
  pricingMissing,
283
+ callId,
173
284
  ...(input.cache !== undefined ? { cacheKey: input.cache.key } : {}),
174
285
  });
175
286
  throw new failures_1.RigFailureError(result.failure);
@@ -184,9 +295,11 @@ function finalizeRaw(deps, input, result, latencyMs) {
184
295
  usage: result.usage,
185
296
  costUsd,
186
297
  latencyMs,
298
+ requestedTier,
187
299
  failureClass: null,
188
300
  tags,
189
301
  pricingMissing,
302
+ callId,
190
303
  ...(input.cache !== undefined ? { cacheKey: input.cache.key } : {}),
191
304
  });
192
305
  const toolCalls = result.toolCalls;
@@ -197,7 +310,7 @@ function finalizeRaw(deps, input, result, latencyMs) {
197
310
  routeVersion: 0,
198
311
  provider: input.provider,
199
312
  model: input.model,
200
- requestedTier: "standard",
313
+ requestedTier,
201
314
  servedTier: result.usage.servedTier,
202
315
  tokensIn: result.usage.tokensIn,
203
316
  tokensOut: result.usage.tokensOut,
@@ -208,6 +321,7 @@ function finalizeRaw(deps, input, result, latencyMs) {
208
321
  attemptsByClass: {},
209
322
  servedVariant: null,
210
323
  repairedBy: null,
324
+ callId,
211
325
  ...(isToolTurn ? { toolCalls } : {}),
212
326
  };
213
327
  return { output: isToolTurn ? null : result.text, meta };
@@ -215,6 +329,9 @@ function finalizeRaw(deps, input, result, latencyMs) {
215
329
  /** Buffered raw dispatch (spec §4.2). Records telemetry, returns the RunResult,
216
330
  * or throws `RigFailureError` on an adapter failure. */
217
331
  async function runRaw(deps, input) {
332
+ // CE-2 F1/F3: fail closed on an unsupported declared directive BEFORE building
333
+ // an adapter — no meter, no provider call, no telemetry row.
334
+ assertRawKnobsSupported(input);
218
335
  const adapter = buildRawAdapter(input.provider, input.apiKey);
219
336
  const candidate = (0, resolve_1.mintRawCandidate)(input.provider, input.model);
220
337
  const started = Date.now();
@@ -251,11 +368,15 @@ function runRawStream(deps, input) {
251
368
  // the events iterator surfaces the same error to whoever drains it.
252
369
  result.catch(() => { });
253
370
  async function* events() {
254
- const adapter = buildRawAdapter(input.provider, input.apiKey);
255
- const candidate = (0, resolve_1.mintRawCandidate)(input.provider, input.model);
256
- const req = buildRawRequest(candidate, input);
257
- const started = Date.now();
258
371
  try {
372
+ // CE-2 F1/F3: fail closed on an unsupported declared directive BEFORE any
373
+ // adapter/dispatch — surfaced through the handle (result rejects, the
374
+ // events iterator throws), so no meter, no provider call, no telemetry row.
375
+ assertRawKnobsSupported(input);
376
+ const adapter = buildRawAdapter(input.provider, input.apiKey);
377
+ const candidate = (0, resolve_1.mintRawCandidate)(input.provider, input.model);
378
+ const req = buildRawRequest(candidate, input);
379
+ const started = Date.now();
259
380
  let adapterResult;
260
381
  if (adapter.stream !== undefined) {
261
382
  let final = null;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * runRaw-only construction — CE-8 (ruling R1,
3
+ * `docs/ModelRig/customer-efficiency/ce8-runraw-only-rig-spec-plan.md`).
4
+ *
5
+ * A Lane-B customer who only calls `rig.runRaw`/`rig.runRawStream` has no routes.
6
+ * With `routesDir: null`, `createRig` SKIPS route loading, the `rig.yaml`
7
+ * manifest, and the serveable-candidate gate — so a stray route file (or a route
8
+ * whose provider key is absent in this deployment) can no longer down a rig that
9
+ * never serves it. The raw lane is unchanged; every route-dependent surface
10
+ * (`run`/`runStream`/`runBundle`/`replay`/`bakeoff`/`verifySwap`) fails with a
11
+ * TERMINAL `invariant_violation` that teaches the fix — Promise surfaces reject,
12
+ * the two streaming surfaces hand back a handle whose `result`/`events` fail
13
+ * (never a synchronous throw, matching the real `runStream` contract).
14
+ *
15
+ * This module holds the small pieces of that opt-out (the empty store, the
16
+ * teaching failure, the throwing route methods) so `index.ts` stays under the
17
+ * 700-LOC cap — `createRig` remains the ONE construction path (a separate
18
+ * `createRawRig()` factory was considered and REJECTED, spec §2 R1).
19
+ */
20
+ import type { RigConfig } from "../config";
21
+ import { type LoadedRoutes } from "../routes/loader";
22
+ import { RigFailureError } from "../types/failures";
23
+ import type { Rig } from "../index";
24
+ /** The teaching remediation for a route-lane call on a runRaw-only rig (spec §2
25
+ * R1). One constant so the failure and the docs stay in lock-step. */
26
+ export declare const RAW_ONLY_FIX_HINT = "this rig was constructed runRaw-only (routesDir: null); give createRig a routesDir to serve routes";
27
+ /** Build the TERMINAL failure a route-dependent surface throws when the rig was
28
+ * constructed runRaw-only. Reuses `invariant_violation` (already TERMINAL + in
29
+ * the teaching table) — no new FailureClass (spec §3 rationale). */
30
+ export declare function rawOnlyRouteFailure(surface: string): RigFailureError;
31
+ /** Narrow a config's `routesDir` to a string for a route-REQUIRED context (the
32
+ * route-oriented CLI subcommands: bakeoff/watch/swap/init/observe/validate),
33
+ * throwing the teaching failure if the rig was built runRaw-only.
34
+ * `loadConfigFromEnv` always yields a string (R2), so this is unreachable for
35
+ * env-built configs — it guards a hand-built `routesDir: null` reaching a
36
+ * route-only command, and satisfies the `string | null` type at those seams. */
37
+ export declare function requireRoutesDir(config: RigConfig, surface: string): string;
38
+ /** The empty route store for a runRaw-only rig: `list()` is `[]` (so
39
+ * serveability, capture-detection, and the exported route mirror are all no-ops)
40
+ * and `get()` teaches, though no public surface reaches it — every route method
41
+ * is overridden by {@link rawOnlyRouteMethods}. */
42
+ export declare function emptyRouteStore(): LoadedRoutes;
43
+ /** Load routes from a real (string) `routesDir`, mirroring a malformed-bundle
44
+ * diagnosis to the console before re-throwing — the byte-for-byte behaviour the
45
+ * string path had inline in `createRig` before CE-8 extracted it here. */
46
+ export declare function loadRoutesOrReport(config: RigConfig, routesDir: string): LoadedRoutes;
47
+ /** The route-dependent slice of the {@link Rig} surface, replaced wholesale when
48
+ * `routesDir === null` so every one of them throws the teaching failure rather
49
+ * than dereferencing an empty store. Spread LAST over the real methods in
50
+ * `createRig`, so later keys win. */
51
+ type RouteMethods = Pick<Rig, "run" | "runStream" | "runBundle" | "runBundleStream" | "replay" | "bakeoff" | "bakeoffFromEvalCases" | "verifySwap">;
52
+ /** The throwing overrides for a runRaw-only rig (spec §2 R1). Each names the
53
+ * surface + the route/task the caller asked for, so the terminal error points at
54
+ * exactly the call that needs a `routesDir`. Promise-returning surfaces REJECT
55
+ * (matching the routed lane, where errors surface as rejections); the two
56
+ * streaming surfaces return a handle synchronously, so they throw synchronously
57
+ * — the caller never reaches an event to await. */
58
+ export declare function rawOnlyRouteMethods(): RouteMethods;
59
+ export {};
60
+ //# sourceMappingURL=rawonly.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rawonly.d.ts","sourceRoot":"","sources":["../../src/run/rawonly.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAyC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAE5F,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC;sEACsE;AACtE,eAAO,MAAM,iBAAiB,uGACwE,CAAC;AAEvG;;oEAEoE;AACpE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAMpE;AAsBD;;;;;gFAKgF;AAChF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAG3E;AAED;;;mDAGmD;AACnD,wBAAgB,eAAe,IAAI,YAAY,CAS9C;AAED;;0EAE0E;AAC1E,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,CASrF;AAED;;;qCAGqC;AACrC,KAAK,YAAY,GAAG,IAAI,CACtB,GAAG,EACD,KAAK,GACL,WAAW,GACX,WAAW,GACX,iBAAiB,GACjB,QAAQ,GACR,SAAS,GACT,sBAAsB,GACtB,YAAY,CACf,CAAC;AAEF;;;;;mDAKmD;AACnD,wBAAgB,mBAAmB,IAAI,YAAY,CAYlD"}
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ /**
3
+ * runRaw-only construction — CE-8 (ruling R1,
4
+ * `docs/ModelRig/customer-efficiency/ce8-runraw-only-rig-spec-plan.md`).
5
+ *
6
+ * A Lane-B customer who only calls `rig.runRaw`/`rig.runRawStream` has no routes.
7
+ * With `routesDir: null`, `createRig` SKIPS route loading, the `rig.yaml`
8
+ * manifest, and the serveable-candidate gate — so a stray route file (or a route
9
+ * whose provider key is absent in this deployment) can no longer down a rig that
10
+ * never serves it. The raw lane is unchanged; every route-dependent surface
11
+ * (`run`/`runStream`/`runBundle`/`replay`/`bakeoff`/`verifySwap`) fails with a
12
+ * TERMINAL `invariant_violation` that teaches the fix — Promise surfaces reject,
13
+ * the two streaming surfaces hand back a handle whose `result`/`events` fail
14
+ * (never a synchronous throw, matching the real `runStream` contract).
15
+ *
16
+ * This module holds the small pieces of that opt-out (the empty store, the
17
+ * teaching failure, the throwing route methods) so `index.ts` stays under the
18
+ * 700-LOC cap — `createRig` remains the ONE construction path (a separate
19
+ * `createRawRig()` factory was considered and REJECTED, spec §2 R1).
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.RAW_ONLY_FIX_HINT = void 0;
23
+ exports.rawOnlyRouteFailure = rawOnlyRouteFailure;
24
+ exports.requireRoutesDir = requireRoutesDir;
25
+ exports.emptyRouteStore = emptyRouteStore;
26
+ exports.loadRoutesOrReport = loadRoutesOrReport;
27
+ exports.rawOnlyRouteMethods = rawOnlyRouteMethods;
28
+ const loader_1 = require("../routes/loader");
29
+ const load_error_report_1 = require("../telemetry/load-error-report");
30
+ const failures_1 = require("../types/failures");
31
+ /** The teaching remediation for a route-lane call on a runRaw-only rig (spec §2
32
+ * R1). One constant so the failure and the docs stay in lock-step. */
33
+ exports.RAW_ONLY_FIX_HINT = "this rig was constructed runRaw-only (routesDir: null); give createRig a routesDir to serve routes";
34
+ /** Build the TERMINAL failure a route-dependent surface throws when the rig was
35
+ * constructed runRaw-only. Reuses `invariant_violation` (already TERMINAL + in
36
+ * the teaching table) — no new FailureClass (spec §3 rationale). */
37
+ function rawOnlyRouteFailure(surface) {
38
+ return new failures_1.RigFailureError({
39
+ class: "invariant_violation",
40
+ message: `${surface} needs routes, but this rig was constructed runRaw-only (routesDir: null)`,
41
+ fixHint: exports.RAW_ONLY_FIX_HINT,
42
+ });
43
+ }
44
+ /** A `RunStreamHandle` that fails the runRaw-only way: `result` rejects and the
45
+ * `events` iterator throws — matching the real `runStream` contract, which NEVER
46
+ * throws synchronously (a stream surface always hands back a handle, errors ride
47
+ * `result`/`events`). A synchronous throw here would break the caller's
48
+ * `const h = rig.runStream(...); await h.result` shape. */
49
+ function rawOnlyStreamHandle(surface) {
50
+ const err = rawOnlyRouteFailure(surface);
51
+ const result = Promise.reject(err);
52
+ // Keep the rejection handled so an unconsumed handle never becomes an
53
+ // unhandledRejection; the events iterator surfaces the same error.
54
+ result.catch(() => undefined);
55
+ const events = {
56
+ // eslint-disable-next-line @typescript-eslint/require-await
57
+ async *[Symbol.asyncIterator]() {
58
+ throw err;
59
+ },
60
+ };
61
+ return { events, result };
62
+ }
63
+ /** Narrow a config's `routesDir` to a string for a route-REQUIRED context (the
64
+ * route-oriented CLI subcommands: bakeoff/watch/swap/init/observe/validate),
65
+ * throwing the teaching failure if the rig was built runRaw-only.
66
+ * `loadConfigFromEnv` always yields a string (R2), so this is unreachable for
67
+ * env-built configs — it guards a hand-built `routesDir: null` reaching a
68
+ * route-only command, and satisfies the `string | null` type at those seams. */
69
+ function requireRoutesDir(config, surface) {
70
+ if (config.routesDir === null)
71
+ throw rawOnlyRouteFailure(surface);
72
+ return config.routesDir;
73
+ }
74
+ /** The empty route store for a runRaw-only rig: `list()` is `[]` (so
75
+ * serveability, capture-detection, and the exported route mirror are all no-ops)
76
+ * and `get()` teaches, though no public surface reaches it — every route method
77
+ * is overridden by {@link rawOnlyRouteMethods}. */
78
+ function emptyRouteStore() {
79
+ return {
80
+ get(route) {
81
+ throw rawOnlyRouteFailure(`get("${route}")`);
82
+ },
83
+ list() {
84
+ return Object.freeze([]);
85
+ },
86
+ };
87
+ }
88
+ /** Load routes from a real (string) `routesDir`, mirroring a malformed-bundle
89
+ * diagnosis to the console before re-throwing — the byte-for-byte behaviour the
90
+ * string path had inline in `createRig` before CE-8 extracted it here. */
91
+ function loadRoutesOrReport(config, routesDir) {
92
+ try {
93
+ return (0, loader_1.loadRoutesWithExtra)(routesDir, config.extraRoutesDir);
94
+ }
95
+ catch (err) {
96
+ // A malformed bundle is teachable, not just fatal: mirror the diagnosis to
97
+ // the console before re-throwing (the rig still cannot serve).
98
+ if (err instanceof loader_1.RouteConfigError)
99
+ (0, load_error_report_1.reportLoadError)(config, err);
100
+ throw err;
101
+ }
102
+ }
103
+ /** The throwing overrides for a runRaw-only rig (spec §2 R1). Each names the
104
+ * surface + the route/task the caller asked for, so the terminal error points at
105
+ * exactly the call that needs a `routesDir`. Promise-returning surfaces REJECT
106
+ * (matching the routed lane, where errors surface as rejections); the two
107
+ * streaming surfaces return a handle synchronously, so they throw synchronously
108
+ * — the caller never reaches an event to await. */
109
+ function rawOnlyRouteMethods() {
110
+ return {
111
+ run: (route) => Promise.reject(rawOnlyRouteFailure(`run("${route}")`)),
112
+ runStream: (route) => rawOnlyStreamHandle(`runStream("${route}")`),
113
+ runBundle: (bundle) => Promise.reject(rawOnlyRouteFailure(`runBundle("${bundle.route}")`)),
114
+ runBundleStream: (bundle) => rawOnlyStreamHandle(`runBundleStream("${bundle.route}")`),
115
+ replay: (route) => Promise.reject(rawOnlyRouteFailure(`replay("${route}")`)),
116
+ bakeoff: (route) => Promise.reject(rawOnlyRouteFailure(`bakeoff("${route}")`)),
117
+ bakeoffFromEvalCases: (task) => Promise.reject(rawOnlyRouteFailure(`bakeoffFromEvalCases("${task}")`)),
118
+ verifySwap: (route) => Promise.reject(rawOnlyRouteFailure(`verifySwap("${route}")`)),
119
+ };
120
+ }
@@ -13,11 +13,17 @@ import type { AttemptTelemetry } from "./dispatch";
13
13
  import type { ActiveRunContext } from "../artifacts/run-context";
14
14
  import type { RetryLedger } from "./retry";
15
15
  import type { RouteBundle, RunOptions, RunTags } from "../types/route";
16
- import type { TelemetryStore } from "../types/telemetry";
16
+ import type { TelemetryRung, TelemetryStore } from "../types/telemetry";
17
+ import type { LadderKeyMinter } from "./ladder-key";
17
18
  export interface RecordAttemptDeps {
18
19
  readonly telemetry: TelemetryStore;
19
20
  readonly route: RouteBundle;
20
21
  readonly ledger: RetryLedger;
22
+ /** Per-call ladder-key minter (spec §2.1 R0) — every row this recorder writes
23
+ * is stamped with the call's id, its 1-based attempt_seq, and the current
24
+ * candidate index. Shared by reference with the run loop, which advances the
25
+ * candidate index as the iterator moves. */
26
+ readonly ladder: LadderKeyMinter;
21
27
  readonly effectiveTags: RunTags;
22
28
  readonly servedVariant: string | null;
23
29
  /** Active run context (E2 chunk A) or null when the artifact namespace is off. */
@@ -33,6 +39,13 @@ export interface RecordAttemptDeps {
33
39
  * uncached), exactly matching the request builder's suppression. */
34
40
  readonly droppedCacheHandle: ReadonlySet<string>;
35
41
  }
42
+ /**
43
+ * Classify a recorded row's ladder rung (R1 review, F1). `primaryDispatched`
44
+ * carries the candidate indices already given a primary dispatch on this call —
45
+ * mutated here so the next same-index non-repair dispatch reads `retry`.
46
+ * Exported for unit tests; the run loop reaches it only through the recorder.
47
+ */
48
+ export declare function rungOf(attempt: AttemptTelemetry, preDispatch: boolean, candidateIndex: number | null, primaryDispatched: Set<number>): TelemetryRung;
36
49
  /** Build the run loop's `recordAttempt(attempt) => inferenceId` over this run's
37
50
  * state. Records one inference row and, with an active run context, mirrors it
38
51
  * as a ground-truth step (fail-open). */
@@ -1 +1 @@
1
- {"version":3,"file":"record-attempt.d.ts","sourceRoot":"","sources":["../../src/run/record-attempt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIvE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC;;qFAEiF;IACjF,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IACrC;;;wEAGoE;IACpE,QAAQ,CAAC,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAClD;AAED;;yCAEyC;AACzC,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,CAAC,OAAO,EAAE,gBAAgB,KAAK,MAAM,CA6ElG"}
1
+ {"version":3,"file":"record-attempt.d.ts","sourceRoot":"","sources":["../../src/run/record-attempt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,KAAK,EAAgB,aAAa,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGtF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B;;;gDAG4C;IAC5C,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC;;qFAEiF;IACjF,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IACrC;;;wEAGoE;IACpE,QAAQ,CAAC,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAClD;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CACpB,OAAO,EAAE,gBAAgB,EACzB,WAAW,EAAE,OAAO,EACpB,cAAc,EAAE,MAAM,GAAG,IAAI,EAC7B,iBAAiB,EAAE,GAAG,CAAC,MAAM,CAAC,GAC7B,aAAa,CAUf;AAED;;yCAEyC;AACzC,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,CAAC,OAAO,EAAE,gBAAgB,KAAK,MAAM,CA+FlG"}
@@ -11,17 +11,45 @@
11
11
  * in-flight E4 / capture-write work (plan coordination note).
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.rungOf = rungOf;
14
15
  exports.createRecordAttempt = createRecordAttempt;
15
16
  const node_crypto_1 = require("node:crypto");
16
17
  const failures_1 = require("../types/failures");
17
18
  const cache_stamp_1 = require("./cache-stamp");
18
19
  const cache_handle_1 = require("./cache-handle");
20
+ /**
21
+ * Classify a recorded row's ladder rung (R1 review, F1). `primaryDispatched`
22
+ * carries the candidate indices already given a primary dispatch on this call —
23
+ * mutated here so the next same-index non-repair dispatch reads `retry`.
24
+ * Exported for unit tests; the run loop reaches it only through the recorder.
25
+ */
26
+ function rungOf(attempt, preDispatch, candidateIndex, primaryDispatched) {
27
+ if (attempt.rung !== undefined)
28
+ return attempt.rung; // explicit (tool-call path)
29
+ if (attempt.repairedBy !== undefined)
30
+ return "repair";
31
+ if (preDispatch)
32
+ return "predispatch_fail";
33
+ // A real primary/retry candidate dispatch. First time this index is dispatched
34
+ // ⇒ primary; any later dispatch of the same index ⇒ retry.
35
+ if (candidateIndex === null)
36
+ return "primary";
37
+ if (primaryDispatched.has(candidateIndex))
38
+ return "retry";
39
+ primaryDispatched.add(candidateIndex);
40
+ return "primary";
41
+ }
19
42
  /** Build the run loop's `recordAttempt(attempt) => inferenceId` over this run's
20
43
  * state. Records one inference row and, with an active run context, mirrors it
21
44
  * as a ground-truth step (fail-open). */
22
45
  function createRecordAttempt(deps) {
23
46
  const { telemetry, route, ledger, effectiveTags, servedVariant, runCtx, autoStepKey } = deps;
24
- const { cache, droppedCacheHandle } = deps;
47
+ const { cache, droppedCacheHandle, ladder } = deps;
48
+ // The candidate indices for which a real primary dispatch has already been
49
+ // recorded on THIS call — so the SECOND non-repair dispatch of a candidate
50
+ // stamps `retry` rather than a second `primary` (R1 review, F1). Held in the
51
+ // per-call closure (the recorder is built once per invocation).
52
+ const primaryDispatched = new Set();
25
53
  return function recordAttempt(attempt) {
26
54
  // Request-path cache substrate (WS-C): derived from the SAME pure gate the
27
55
  // dispatcher stamps the request with, so `cache_key`/`prefix_fingerprint` on
@@ -53,6 +81,16 @@ function createRecordAttempt(deps) {
53
81
  const dropped = droppedCacheHandle.has(`${attempt.candidate.provider} ${attempt.candidate.model}`);
54
82
  const customer = isPrimary && !preDispatch ? (0, cache_handle_1.customerCacheHandle)(cache, attempt.candidate, dropped) : null;
55
83
  const cacheKey = customer?.key ?? cacheStamp?.cacheKey ?? null;
84
+ // Ladder key (spec §2.1 R0): one stamp per row — shared call_id, monotonic
85
+ // attempt_seq, and the current candidate_index (the candidate being repaired
86
+ // for a repair row, since the iterator has not advanced during its turn).
87
+ const ladderKey = ladder.stamp();
88
+ // Rung tag (R1 review, F1): the row's place on the ladder, so the rollup and
89
+ // ladder view never guess. Precedence: an explicit hint from the caller
90
+ // (only the tool-call path sets one) wins; then a repair row, then a
91
+ // pre-dispatch terminal failure, then primary-vs-retry by whether this
92
+ // candidate index was already dispatched on this call.
93
+ const rung = rungOf(attempt, preDispatch, ladderKey.candidateIndex, primaryDispatched);
56
94
  const row = {
57
95
  id: (0, node_crypto_1.randomUUID)(),
58
96
  ts: new Date().toISOString(),
@@ -75,11 +113,14 @@ function createRecordAttempt(deps) {
75
113
  ttfbMs: attempt.ttfbMs ?? null,
76
114
  failureClass: attempt.failure?.class ?? null,
77
115
  attemptsJson: JSON.stringify(ledger.snapshot()),
78
- tagsJson: JSON.stringify(effectiveTags),
116
+ tagsJson: JSON.stringify({ ...effectiveTags, rung }),
79
117
  fingerprintCell: route.fingerprintCell,
80
118
  servedVariant,
81
119
  repairedBy: attempt.repairedBy ?? null,
82
120
  pricingMissing: attempt.pricingMissing ?? false,
121
+ callId: ladderKey.callId,
122
+ attemptSeq: ladderKey.attemptSeq,
123
+ candidateIndex: ladderKey.candidateIndex,
83
124
  };
84
125
  telemetry.record(row);
85
126
  // Mirror this inference into a ground-truth step attempt (fail-open — the
@@ -12,6 +12,10 @@ export type CapabilityLookup = (provider: ProviderId, model: string) => Readonly
12
12
  export interface CandidateIterator {
13
13
  /** Next eligible candidate, or null when the set is exhausted. */
14
14
  next(): CandidateRef | null;
15
+ /** Position in the EFFECTIVE eligible list of the candidate the most recent
16
+ * `next()` returned (0 = first eligible; ladder key, spec §2.1). `-1` before
17
+ * the first `next()`; unchanged by a `next()` that returns null. */
18
+ readonly index: number;
15
19
  }
16
20
  /**
17
21
  * Request-time candidate filters (G-2) — optional, applied by resolveCandidates
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/run/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAElF,2EAA2E;AAC3E,MAAM,MAAM,gBAAgB,GAAG,CAC7B,QAAQ,EAAE,UAAU,EACpB,KAAK,EAAE,MAAM,KACV,WAAW,CAAC,cAAc,CAAC,CAAC;AAEjC,MAAM,WAAW,iBAAiB;IAChC,kEAAkE;IAClE,IAAI,IAAI,YAAY,GAAG,IAAI,CAAC;CAC7B;AA6CD;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;CACzC;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC;;oFAEgF;IAChF,QAAQ,CAAC,uBAAuB,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACzF,gEAAgE;IAChE,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;CACjD;AAED;kFACkF;AAClF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS;QAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACtF,QAAQ,CAAC,WAAW,EAAE,SAAS;QAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3F,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,WAAW,EAClB,YAAY,EAAE,gBAAgB,EAC9B,cAAc,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,EACtE,eAAe,UAAQ,EACvB,MAAM,CAAC,EAAE,eAAe,EACxB,YAAY,CAAC,EAAE,mBAAmB,GACjC,iBAAiB,CAkFnB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,OAAO,GAAG,WAAW,CAM5F;AAED;mFACmF;AACnF,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,UAAU,GAAG,eAAe,GAAG,SAAS,CAUxF;AAED;;gDAEgD;AAChD,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAMtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAElF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE;IAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC9D,YAAY,GAAG,IAAI,CAMrB"}
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/run/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAElF,2EAA2E;AAC3E,MAAM,MAAM,gBAAgB,GAAG,CAC7B,QAAQ,EAAE,UAAU,EACpB,KAAK,EAAE,MAAM,KACV,WAAW,CAAC,cAAc,CAAC,CAAC;AAEjC,MAAM,WAAW,iBAAiB;IAChC,kEAAkE;IAClE,IAAI,IAAI,YAAY,GAAG,IAAI,CAAC;IAC5B;;wEAEoE;IACpE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AA6CD;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;CACzC;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC;;oFAEgF;IAChF,QAAQ,CAAC,uBAAuB,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACzF,gEAAgE;IAChE,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;CACjD;AAED;kFACkF;AAClF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS;QAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACtF,QAAQ,CAAC,WAAW,EAAE,SAAS;QAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3F,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,WAAW,EAClB,YAAY,EAAE,gBAAgB,EAC9B,cAAc,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,EACtE,eAAe,UAAQ,EACvB,MAAM,CAAC,EAAE,eAAe,EACxB,YAAY,CAAC,EAAE,mBAAmB,GACjC,iBAAiB,CAwFnB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,OAAO,GAAG,WAAW,CAM5F;AAED;mFACmF;AACnF,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,UAAU,GAAG,eAAe,GAAG,SAAS,CAUxF;AAED;;gDAEgD;AAChD,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAMtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAElF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE;IAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC9D,YAAY,GAAG,IAAI,CAMrB"}
@@ -124,10 +124,17 @@ function resolveCandidates(route, capabilities, costUsdPerMTok, searchAvailable
124
124
  // minted. The cast below is the single sanctioned exception to the
125
125
  // "no `as CandidateRef`" grep (scoped to run/resolve.ts in CI).
126
126
  const refs = ordered.map((c) => Object.freeze({ provider: c.provider, model: c.model }));
127
- let index = 0;
127
+ let cursor = 0;
128
+ let lastIndex = -1;
128
129
  return {
129
130
  next() {
130
- return index < refs.length ? refs[index++] : null;
131
+ if (cursor >= refs.length)
132
+ return null;
133
+ lastIndex = cursor;
134
+ return refs[cursor++];
135
+ },
136
+ get index() {
137
+ return lastIndex;
131
138
  },
132
139
  };
133
140
  }