@dereekb/openrouter 14.7.0 → 14.9.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dereekb/openrouter",
3
- "version": "14.7.0",
3
+ "version": "14.9.0",
4
4
  "sideEffects": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -19,19 +19,24 @@
19
19
  "types": "./index.d.ts",
20
20
  "import": "./index.esm.js",
21
21
  "default": "./index.esm.js"
22
+ },
23
+ "./decision": {
24
+ "types": "./decision.d.ts",
25
+ "import": "./decision.esm.js",
26
+ "default": "./decision.esm.js"
22
27
  }
23
28
  },
24
29
  "peerDependencies": {
25
- "@dereekb/firebase": "14.7.0",
26
- "@dereekb/firebase-server": "14.7.0",
27
- "@dereekb/model": "14.7.0",
28
- "@dereekb/util": "14.7.0",
30
+ "@dereekb/firebase": "14.9.0",
31
+ "@dereekb/firebase-server": "14.9.0",
32
+ "@dereekb/model": "14.9.0",
33
+ "@dereekb/util": "14.9.0",
29
34
  "@nestjs/common": "^12.0.1",
30
- "@openrouter/sdk": "^1.3.0",
35
+ "@openrouter/sdk": "^1.3.8",
31
36
  "arktype": "^2.2.0"
32
37
  },
33
38
  "devDependencies": {
34
- "@dereekb/nestjs": "14.7.0"
39
+ "@dereekb/nestjs": "14.9.0"
35
40
  },
36
41
  "module": "./index.esm.js",
37
42
  "main": "./index.esm.js",
@@ -0,0 +1,26 @@
1
+ /**
2
+ * `@dereekb/openrouter/decision` — the System One (Jev) decision layer, loadable WITHOUT evaluating
3
+ * `@openrouter/sdk`.
4
+ *
5
+ * The root `@dereekb/openrouter` entry re-exports SDK values (`callModel`, `responsesSend`,
6
+ * `systemOneCreate`, …), so importing any value from it evaluates the SDK — a measurable cold-start cost
7
+ * that a test runner isolating each spec file pays once per file. This entry carries everything a decision
8
+ * is BUILT from and READ with, and nothing that sends one:
9
+ *
10
+ * - the declaration vocabulary — Choice / Score / Noul questions, answers, confidence bands, validation;
11
+ * - the request, the wire mapping (`openRouterDecisionRequestBody`), the reply reader
12
+ * (`readOpenRouterDecisionAnswers`) and its faults, and the usage flattening;
13
+ * - the model config and the model ids, including the System One routing guard
14
+ * (`isOpenRouterSystemOneModelId`).
15
+ *
16
+ * The transport, `openRouterDecision`, stays on the root entry because it is the one `systemOneCreate`
17
+ * caller. A consumer that owns its own transport pairs this entry with its own lazily-loaded client.
18
+ *
19
+ * Everything here is ALSO exported from the root entry, from the same modules — the two entries share one
20
+ * chunk, so a class such as `OpenRouterDecisionAnswerFaultError` has one identity whichever entry a value
21
+ * was imported through.
22
+ */
23
+ export * from './lib/openrouter.config';
24
+ export * from './lib/openrouter.decision';
25
+ export * from './lib/openrouter.decision.question';
26
+ export * from './lib/openrouter.type';
@@ -1,5 +1,8 @@
1
1
  export * from './openrouter.call';
2
2
  export * from './openrouter.config';
3
+ export * from './openrouter.decision';
4
+ export * from './openrouter.decision.call';
5
+ export * from './openrouter.decision.question';
3
6
  export * from './openrouter.embedding';
4
7
  export * from './openrouter.generation';
5
8
  export * from './openrouter.input';
@@ -2,7 +2,7 @@ import { type Maybe } from '@dereekb/util';
2
2
  import { type CallModelInput, type OpenResponsesResult, type OpenRouterCore, type RequestOptions, type StateAccessor, type Tool, ModelResult } from './openrouter.sdk';
3
3
  import { type OpenRouterHostedToolConfig, type OpenRouterModelConfig } from './openrouter.config';
4
4
  import { type OpenRouterPromptRequest } from './openrouter.request';
5
- import { type OpenRouterGenerationId, type OpenRouterRunError, type OpenRouterRunUsage } from './openrouter.type';
5
+ import { type OpenRouterGenerationId, type OpenRouterModelId, type OpenRouterRunError, type OpenRouterRunUsage } from './openrouter.type';
6
6
  /**
7
7
  * A model config split into the part that goes on the request and the part that controls how the
8
8
  * request is executed.
@@ -106,14 +106,31 @@ export interface OpenRouterCallModelInputParams<TTools extends readonly Tool[] =
106
106
  */
107
107
  readonly state?: Maybe<StateAccessor<TTools>>;
108
108
  }
109
+ /**
110
+ * Raised when a request naming a System One model is sent down the completion arm.
111
+ *
112
+ * Its own error type rather than a generic one because the fix is specific and mechanical — the call
113
+ * goes to `openRouterDecision` — and an error a reader can act on without opening the source is worth
114
+ * a named class.
115
+ */
116
+ export declare class OpenRouterSystemOneModelOnCompletionArmError extends Error {
117
+ readonly model: OpenRouterModelId;
118
+ constructor(model: OpenRouterModelId);
119
+ }
109
120
  /**
110
121
  * Converts a built request into the `/responses` request body.
111
122
  *
112
123
  * This is the whole wire body minus the SDK-only keys (`tools`/`state`/`stopWhen` on the `callModel`
113
124
  * path), so both dispatch paths assemble the request the same way and cannot drift.
114
125
  *
126
+ * It is also where a System One model is refused, because it is the ONE point every dispatch path
127
+ * converges on — `openRouterCallModelInput`, `sendOpenRouterResponsesRequest` and
128
+ * `openRouterModelResultForRequest` all build their body here — so no route to the completion arm can
129
+ * be added later that quietly skips the check.
130
+ *
115
131
  * @param request - The built request.
116
132
  * @returns The request body, in the SDK's camelCase request surface.
133
+ * @throws {OpenRouterSystemOneModelOnCompletionArmError} When the config names a System One model.
117
134
  */
118
135
  export declare function openRouterResponsesRequestBody(request: OpenRouterPromptRequest): Record<string, unknown>;
119
136
  /**
@@ -289,6 +289,10 @@ export interface OpenRouterModelConfig {
289
289
  export declare function mergeOpenRouterModelConfig(configs: Maybe<OpenRouterModelConfig>[]): OpenRouterModelConfig;
290
290
  /**
291
291
  * Result of validating an {@link OpenRouterModelConfig}.
292
+ *
293
+ * Also the package's general validation shape — `validateOpenRouterDecisionQuestions` and
294
+ * `validateOpenRouterDecisionRequest` report through it too, so a caller publishing a prompt can gather
295
+ * config problems and question problems into one surface rather than branching on which produced them.
292
296
  */
293
297
  export interface OpenRouterModelConfigValidation {
294
298
  /**
@@ -304,11 +308,24 @@ export interface OpenRouterModelConfigValidation {
304
308
  */
305
309
  readonly warnings: string[];
306
310
  }
311
+ /**
312
+ * Options for {@link validateOpenRouterModelConfig}.
313
+ */
314
+ export interface ValidateOpenRouterModelConfigOptions {
315
+ /**
316
+ * Whether the config belongs to a DECISION rather than a completion.
317
+ *
318
+ * Only the model check differs, and it inverts: a decision requires a System One model and a
319
+ * completion refuses one. Defaults to false.
320
+ */
321
+ readonly decision?: Maybe<boolean>;
322
+ }
307
323
  /**
308
324
  * Validates a merged model config, catching the misconfigurations that fail silently at runtime
309
325
  * rather than loudly.
310
326
  *
311
327
  * @param config - The merged config to check.
328
+ * @param options - Which arm the config is for.
312
329
  * @returns The validation result.
313
330
  */
314
- export declare function validateOpenRouterModelConfig(config: Maybe<OpenRouterModelConfig>): OpenRouterModelConfigValidation;
331
+ export declare function validateOpenRouterModelConfig(config: Maybe<OpenRouterModelConfig>, options?: Maybe<ValidateOpenRouterModelConfigOptions>): OpenRouterModelConfigValidation;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The TRANSPORT half of the decision layer: {@link openRouterDecision}, the one caller of the SDK's
3
+ * `systemOneCreate`.
4
+ *
5
+ * Kept apart from `openrouter.decision.ts` (declaring, validating, the wire mapping, reading a reply) so
6
+ * that everything a decision is built from can be loaded without evaluating `@openrouter/sdk` — see the
7
+ * `@dereekb/openrouter/decision` entry. A caller that owns its own transport (its own timeout, retry
8
+ * policy or error shape) builds the body with `openRouterDecisionRequestBody` and reads the reply with
9
+ * `readOpenRouterDecisionAnswers`, and never needs this file.
10
+ */
11
+ import { type Maybe } from '@dereekb/util';
12
+ import { type OpenRouterDecisionRequest } from './openrouter.decision';
13
+ import { type OpenRouterDecisionAnswers, type OpenRouterDecisionQuestions } from './openrouter.decision.question';
14
+ import { type DecisionsResponse, type OpenRouterCore, type RequestOptions } from './openrouter.sdk';
15
+ import { type OpenRouterGenerationId, type OpenRouterModelId, type OpenRouterRunUsage } from './openrouter.type';
16
+ /**
17
+ * A normalized result of one decision.
18
+ *
19
+ * Field names deliberately match `OpenRouterCallResult` wherever the two have the same meaning, so a
20
+ * caller reading `model` / `generationIds` / `usage` does not have to learn a second vocabulary for the
21
+ * second arm. There is no `outputText` or `error`: a decision has no prose output, and the route reports
22
+ * a failure as a thrown transport error rather than as a field on a 200.
23
+ */
24
+ export interface OpenRouterDecisionResult<Q extends OpenRouterDecisionQuestions = OpenRouterDecisionQuestions> {
25
+ /**
26
+ * The answers, membership-checked against the declared questions.
27
+ */
28
+ readonly answers: OpenRouterDecisionAnswers<Q>;
29
+ /**
30
+ * The model that actually served the decision — a VERSIONED slug even when an alias was asked for,
31
+ * which is what makes an answer reproducible.
32
+ */
33
+ readonly model?: Maybe<OpenRouterModelId>;
34
+ /**
35
+ * The provider that served it.
36
+ */
37
+ readonly provider?: Maybe<string>;
38
+ /**
39
+ * Generation ids produced, for auditing. At most one on this route.
40
+ */
41
+ readonly generationIds: OpenRouterGenerationId[];
42
+ /**
43
+ * Token/cost usage.
44
+ */
45
+ readonly usage?: Maybe<OpenRouterRunUsage>;
46
+ /**
47
+ * The raw response, for anything the normalized shape drops.
48
+ */
49
+ readonly response: DecisionsResponse;
50
+ }
51
+ /**
52
+ * Params for {@link openRouterDecision}.
53
+ */
54
+ export interface OpenRouterDecisionParams<Q extends OpenRouterDecisionQuestions = OpenRouterDecisionQuestions> {
55
+ /**
56
+ * The OpenRouter client.
57
+ */
58
+ readonly client: OpenRouterCore;
59
+ /**
60
+ * The built request.
61
+ */
62
+ readonly request: OpenRouterDecisionRequest<Q>;
63
+ /**
64
+ * Additional request options, merged under the config's `requestTimeoutMs`.
65
+ */
66
+ readonly options?: Maybe<RequestOptions>;
67
+ }
68
+ /**
69
+ * Asks a decision and returns its answers.
70
+ *
71
+ * The request is validated first, so a model that cannot serve a decision is refused HERE rather than by
72
+ * the route — the mirror of the refusal `callModelForOpenRouterRequest` makes for a System One model on
73
+ * the completion arm. Which surface a request belongs to is a property of the request, not a choice the
74
+ * caller makes at the call site.
75
+ *
76
+ * @param params - The client, request, and options.
77
+ * @returns The normalized decision result.
78
+ * @throws {OpenRouterDecisionDeclarationError} When the request cannot be asked as declared.
79
+ * @throws {OpenRouterDecisionAnswerFaultError} When the reply did not answer the declared questions.
80
+ */
81
+ export declare function openRouterDecision<Q extends OpenRouterDecisionQuestions = OpenRouterDecisionQuestions>(params: OpenRouterDecisionParams<Q>): Promise<OpenRouterDecisionResult<Q>>;
@@ -0,0 +1,286 @@
1
+ import { type Maybe } from '@dereekb/util';
2
+ import { type OpenRouterModelConfig, type OpenRouterModelConfigValidation } from './openrouter.config';
3
+ import { type OpenRouterDecisionAnswer, type OpenRouterDecisionAnswers, type OpenRouterDecisionEntry, type OpenRouterDecisionQuestion, type OpenRouterDecisionQuestionId, type OpenRouterDecisionQuestions, type OpenRouterDecisionState } from './openrouter.decision.question';
4
+ import { type OpenRouterResolvedPrompt } from './openrouter.prompt';
5
+ import { type OpenRouterRequestTrace } from './openrouter.request';
6
+ import type { DecisionsRequest, DecisionsResponse } from './openrouter.sdk';
7
+ import { type OpenRouterRunUsage } from './openrouter.type';
8
+ /**
9
+ * How far a distribution's probabilities may sum from 1 before the reply is read as malformed.
10
+ *
11
+ * Wide enough to absorb the float rounding a JSON round-trip introduces, narrow enough that a
12
+ * distribution missing an option does not pass.
13
+ */
14
+ export declare const OPENROUTER_DECISION_DISTRIBUTION_SUM_TOLERANCE = 0.05;
15
+ /**
16
+ * A caller-chosen id grouping related decisions for observability.
17
+ *
18
+ * Never sent to the provider — it exists for broadcast grouping and private logging only.
19
+ */
20
+ export type OpenRouterDecisionSessionId = string;
21
+ /**
22
+ * A decision request: the config naming the model, the state to judge, and the questions to ask of it.
23
+ *
24
+ * The decisions analogue of `OpenRouterPromptRequest`, and deliberately a separate type rather than a
25
+ * mode on it. The two share no content at all — one carries messages and an output contract, the other
26
+ * carries a state and an answer space — so folding them together would produce a type where half the
27
+ * fields are meaningless on any given request.
28
+ */
29
+ export interface OpenRouterDecisionRequest<Q extends OpenRouterDecisionQuestions = OpenRouterDecisionQuestions> {
30
+ /**
31
+ * The merged model config. Only `model`, `provider` and `user` reach the wire — see
32
+ * {@link splitOpenRouterDecisionModelConfig}.
33
+ */
34
+ readonly config: OpenRouterModelConfig;
35
+ /**
36
+ * The content to judge.
37
+ */
38
+ readonly state: OpenRouterDecisionState;
39
+ /**
40
+ * The declared questions.
41
+ */
42
+ readonly questions: Q;
43
+ /**
44
+ * Observability grouping id.
45
+ */
46
+ readonly sessionId?: Maybe<OpenRouterDecisionSessionId>;
47
+ /**
48
+ * Trace metadata, carried exactly as it is on a completion request.
49
+ */
50
+ readonly trace?: Maybe<OpenRouterRequestTrace>;
51
+ }
52
+ /**
53
+ * Params for {@link openRouterDecisionRequest}.
54
+ */
55
+ export interface OpenRouterDecisionRequestParams<Q extends OpenRouterDecisionQuestions = OpenRouterDecisionQuestions> {
56
+ /**
57
+ * The resolved prompt version supplying the base config and any STORED questions.
58
+ */
59
+ readonly prompt?: Maybe<OpenRouterResolvedPrompt>;
60
+ /**
61
+ * The content to judge.
62
+ */
63
+ readonly state: OpenRouterDecisionState;
64
+ /**
65
+ * Questions declared by the caller for THIS call, merged over the prompt's stored questions by id.
66
+ *
67
+ * Dynamic questions are the normal case for a decision whose answer space is derived from data the
68
+ * caller holds — a shortlist of rows, a set of candidates — which cannot be written down in advance.
69
+ */
70
+ readonly questions?: Maybe<Q>;
71
+ /**
72
+ * Per-call config overrides, applied on top of the prompt's config.
73
+ */
74
+ readonly overrides?: Maybe<OpenRouterModelConfig>;
75
+ /**
76
+ * Observability grouping id.
77
+ */
78
+ readonly sessionId?: Maybe<OpenRouterDecisionSessionId>;
79
+ /**
80
+ * Trace metadata.
81
+ */
82
+ readonly trace?: Maybe<OpenRouterRequestTrace>;
83
+ }
84
+ /**
85
+ * Raised when a decision cannot be asked as declared.
86
+ *
87
+ * Every declaration problem fails HERE — before a request is built, naming the question — rather than as
88
+ * a 4xx about a request body. The distinction matters because the fixes are different: a malformed
89
+ * declaration is a code or authoring bug, while a 4xx is an operational one.
90
+ */
91
+ export declare class OpenRouterDecisionDeclarationError extends Error {
92
+ readonly errors: string[];
93
+ constructor(errors: string[]);
94
+ }
95
+ /**
96
+ * Builds a decision request from an optional resolved prompt plus the caller's state and questions.
97
+ *
98
+ * Stored questions and caller questions are merged BY ID with the caller winning, the same composition
99
+ * a prompt's static seed messages and dynamic `input` already use. What differs is the reason: there is
100
+ * no prompt cache on this route, so the merge is about where a question is authored — a fixed taxonomy
101
+ * belongs in a version an operator can edit, a per-call candidate set can only come from code.
102
+ *
103
+ * @param params - The prompt, state, questions, overrides, session id, and trace.
104
+ * @returns The built request.
105
+ * @throws {OpenRouterDecisionDeclarationError} When the merged question map cannot be asked.
106
+ */
107
+ export declare function openRouterDecisionRequest<Q extends OpenRouterDecisionQuestions = OpenRouterDecisionQuestions>(params: OpenRouterDecisionRequestParams<Q>): OpenRouterDecisionRequest<Q>;
108
+ /**
109
+ * The parts of an {@link OpenRouterModelConfig} that reach a decisions request, and the parts that
110
+ * cannot.
111
+ */
112
+ export interface OpenRouterSplitDecisionModelConfig {
113
+ /**
114
+ * The parameters the decisions route accepts.
115
+ */
116
+ readonly requestConfig: Pick<OpenRouterModelConfig, 'model' | 'provider' | 'user'>;
117
+ /**
118
+ * Per-request wall-clock timeout, when the config set one.
119
+ */
120
+ readonly requestTimeoutMs?: Maybe<number>;
121
+ /**
122
+ * Names of the config keys the decisions route has no equivalent for, in the order declared.
123
+ */
124
+ readonly dropped: string[];
125
+ }
126
+ /**
127
+ * Splits a model config into the parameters a decisions request accepts and the ones it does not.
128
+ *
129
+ * The decisions route takes only `model`, `provider` and `user`. Everything else on
130
+ * {@link OpenRouterModelConfig} describes a completion — an output format, a reasoning budget, a tool
131
+ * set, a temperature — and none of it has a meaning here: a decision has no free-form output to shape
132
+ * and no tools to call. Those keys are reported in `dropped` rather than quietly discarded, because a
133
+ * `temperature` a caller believes is in effect is exactly the kind of thing that is only noticed when
134
+ * an answer is already wrong.
135
+ *
136
+ * `models` (the fallback chain) is dropped for a sharper reason: the decisions route takes a single
137
+ * `model`, so a chain authored here does not degrade to its first entry — it simply does not exist.
138
+ *
139
+ * @param config - The merged model config.
140
+ * @returns The split config.
141
+ */
142
+ export declare function splitOpenRouterDecisionModelConfig(config: Maybe<OpenRouterModelConfig>): OpenRouterSplitDecisionModelConfig;
143
+ /**
144
+ * Validates a decision request before it is sent.
145
+ *
146
+ * The mirror of `validateOpenRouterModelConfig` for this arm, and the reason both exist: the model slug
147
+ * is the ONLY thing that says which of OpenRouter's two inference surfaces a request belongs to, so it
148
+ * is checked on both, and neither arm can be entered with a model the other one owns.
149
+ *
150
+ * @param request - The request to check.
151
+ * @returns The validation result.
152
+ */
153
+ export declare function validateOpenRouterDecisionRequest(request: Maybe<OpenRouterDecisionRequest>): OpenRouterModelConfigValidation;
154
+ /**
155
+ * One declared question in the shape the wire carries it.
156
+ *
157
+ * All three primitives collapse onto a single `criteria` key, which is why this mapping exists at all:
158
+ * the declaration types name what each primitive's criteria MEAN (`options`, `levels`, `means`), and the
159
+ * wire does not.
160
+ */
161
+ export type OpenRouterDecisionWireQuestion = {
162
+ readonly type: 'choice';
163
+ readonly instructions: OpenRouterDecisionEntry;
164
+ readonly criteria: Readonly<Record<string, Maybe<OpenRouterDecisionEntry>>>;
165
+ } | {
166
+ readonly type: 'score';
167
+ readonly instructions: OpenRouterDecisionEntry;
168
+ readonly criteria: readonly OpenRouterDecisionEntry[];
169
+ } | {
170
+ readonly type: 'noul';
171
+ readonly instructions: OpenRouterDecisionEntry;
172
+ readonly criteria?: {
173
+ readonly true: OpenRouterDecisionEntry;
174
+ readonly false: OpenRouterDecisionEntry;
175
+ };
176
+ };
177
+ /**
178
+ * Converts one declared question to its wire shape.
179
+ *
180
+ * A pure rename. Three details carry meaning and are not incidental:
181
+ *
182
+ * - an option with no description becomes an explicit `null`, which is how the wire spells "undescribed
183
+ * option" — omitting the key would instead remove the option from the answer space;
184
+ * - a Noul with no `means` omits `criteria` ENTIRELY rather than sending an empty object;
185
+ * - a structured entry is passed through by IDENTITY, never copied or re-serialized, because the wire
186
+ * carries declaration guidance verbatim and a normalising round-trip is exactly the kind of silent
187
+ * edit this mapping must not make.
188
+ *
189
+ * @param question - The declared question.
190
+ * @returns The wire question.
191
+ */
192
+ export declare function openRouterDecisionWireQuestion(question: OpenRouterDecisionQuestion): OpenRouterDecisionWireQuestion;
193
+ /**
194
+ * Converts a built request into the `/systemone` request body.
195
+ *
196
+ * @param request - The built request.
197
+ * @returns The request body, in the SDK's request surface.
198
+ */
199
+ export declare function openRouterDecisionRequestBody(request: OpenRouterDecisionRequest): DecisionsRequest;
200
+ /**
201
+ * What is wrong with one answer in a reply.
202
+ */
203
+ export type OpenRouterDecisionFaultKind = 'answer-missing' | 'answer-mistyped' | 'choice-off-option' | 'value-out-of-range';
204
+ /**
205
+ * One thing wrong with a reply.
206
+ */
207
+ export interface OpenRouterDecisionFault {
208
+ readonly kind: OpenRouterDecisionFaultKind;
209
+ readonly question: OpenRouterDecisionQuestionId;
210
+ readonly detail: string;
211
+ }
212
+ /**
213
+ * Raised when a reply did not answer the questions that were declared.
214
+ *
215
+ * This is always a defect in the RESPONSE and never a judgement the model made. "None of these fits" is
216
+ * not a fault — it is said through a Noul the caller declared for it, and arrives as an ANSWER. A caller
217
+ * that catches this is handling a malformed reply, not a negative one.
218
+ */
219
+ export declare class OpenRouterDecisionAnswerFaultError extends Error {
220
+ readonly faults: OpenRouterDecisionFault[];
221
+ constructor(faults: OpenRouterDecisionFault[]);
222
+ }
223
+ /**
224
+ * Config for {@link readOpenRouterDecisionAnswers}.
225
+ */
226
+ export interface ReadOpenRouterDecisionAnswersConfig<Q extends OpenRouterDecisionQuestions> {
227
+ /**
228
+ * The questions that were declared.
229
+ */
230
+ readonly questions: Q;
231
+ /**
232
+ * The answers as returned.
233
+ */
234
+ readonly raw: Readonly<Record<string, Maybe<OpenRouterDecisionAnswer>>>;
235
+ }
236
+ /**
237
+ * Either the answers, or everything wrong with them.
238
+ */
239
+ export type OpenRouterDecisionAnswersRead<Q extends OpenRouterDecisionQuestions> = {
240
+ readonly answers: OpenRouterDecisionAnswers<Q>;
241
+ readonly faults?: undefined;
242
+ } | {
243
+ readonly answers?: undefined;
244
+ readonly faults: OpenRouterDecisionFault[];
245
+ };
246
+ /**
247
+ * Membership-checks a reply against the questions that were declared.
248
+ *
249
+ * This is the transport's GUARANTEE, and the reason no consumer re-checks: past this point a `choice` is
250
+ * one of the declared options, a `score` is inside the declared range, and a `noul` is a probability. It
251
+ * is what a decision has instead of the parse-and-salvage a completion needs — the answer space was
252
+ * declared, so an answer either sits inside it or the reply is broken.
253
+ *
254
+ * Every fault is collected rather than the first, because a reply that lost one answer has usually lost
255
+ * more than one and reporting them one round-trip at a time is no way to debug a declaration.
256
+ *
257
+ * An ABSENT distribution or confidence is checked for nothing: only `choice` / `score` / `noul` are
258
+ * guaranteed on the wire, so their absence is a real reply.
259
+ *
260
+ * @param config - The declared questions and the raw answers.
261
+ * @returns The answers, or the faults.
262
+ */
263
+ export declare function readOpenRouterDecisionAnswers<Q extends OpenRouterDecisionQuestions>(config: ReadOpenRouterDecisionAnswersConfig<Q>): OpenRouterDecisionAnswersRead<Q>;
264
+ /**
265
+ * Flattens a decisions reply's usage into the package's usage shape.
266
+ *
267
+ * Reported through the SAME {@link OpenRouterRunUsage} a completion reports, so the cost ledger needs no
268
+ * second counting site and a run's spend is readable without knowing which arm served it.
269
+ *
270
+ * Two things about this route's numbers, both measured rather than assumed:
271
+ *
272
+ * - `outputTokens` is REPORTED and non-zero, but it is not billed. `cost` is input alone at the model's
273
+ * input rate — verified live at 350 input tokens and $0.042/Mtok giving exactly $0.0000147, with 34
274
+ * output tokens on the same reply. So a cost-per-token derived from `totalTokens` here is wrong.
275
+ * - `cost` is FINAL when it arrives. A completion's is settled server-side afterwards and refined by the
276
+ * broadcast webhook; a decision's is synchronous, which is why a decision run needs no reconciliation.
277
+ *
278
+ * A measurement the reply did not report is OMITTED rather than carried as `undefined`, because a spread
279
+ * `cost: undefined` reads downstream as "cost zero" rather than "cost unknown".
280
+ *
281
+ * @param usage - The usage as returned.
282
+ * @returns The flattened usage.
283
+ *
284
+ * @__NO_SIDE_EFFECTS__
285
+ */
286
+ export declare function openRouterRunUsageFromDecisionsUsage(usage: Maybe<DecisionsResponse['usage']>): Maybe<OpenRouterRunUsage>;