@kaana.ai/contracts 0.0.0-stage → 4.3.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/LICENSE +673 -0
- package/NOTICE +21 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +29 -0
- package/dist/internal/attribution.d.ts +116 -0
- package/dist/internal/attribution.js +82 -0
- package/dist/internal/canonical.d.ts +31 -0
- package/dist/internal/canonical.js +57 -0
- package/dist/internal/catalogue.d.ts +1030 -0
- package/dist/internal/catalogue.js +349 -0
- package/dist/internal/credentialControl.d.ts +329 -0
- package/dist/internal/credentialControl.js +256 -0
- package/dist/internal/decisions.d.ts +245 -0
- package/dist/internal/decisions.js +69 -0
- package/dist/internal/economics.d.ts +27 -0
- package/dist/internal/economics.js +26 -0
- package/dist/internal/embeddings.d.ts +164 -0
- package/dist/internal/embeddings.js +59 -0
- package/dist/internal/errors.d.ts +185 -0
- package/dist/internal/errors.js +211 -0
- package/dist/internal/grant.d.ts +696 -0
- package/dist/internal/grant.js +437 -0
- package/dist/internal/identifiers.d.ts +63 -0
- package/dist/internal/identifiers.js +65 -0
- package/dist/internal/privateAutoExecution.d.ts +158 -0
- package/dist/internal/privateAutoExecution.js +188 -0
- package/dist/internal/realtime.d.ts +1005 -0
- package/dist/internal/realtime.js +187 -0
- package/dist/internal/request.d.ts +1079 -0
- package/dist/internal/request.js +404 -0
- package/dist/internal/routing.d.ts +90 -0
- package/dist/internal/routing.js +139 -0
- package/dist/internal/scopedExecution.d.ts +74 -0
- package/dist/internal/scopedExecution.js +61 -0
- package/dist/internal/settlement.d.ts +205 -0
- package/dist/internal/settlement.js +71 -0
- package/dist/internal/streamEvents.d.ts +422 -0
- package/dist/internal/streamEvents.js +171 -0
- package/dist/internal/usage.d.ts +127 -0
- package/dist/internal/usage.js +109 -0
- package/dist/internal.d.ts +30 -0
- package/dist/internal.js +30 -0
- package/dist/public/audio.d.ts +347 -0
- package/dist/public/audio.js +236 -0
- package/dist/public/audioFormats.d.ts +64 -0
- package/dist/public/audioFormats.js +60 -0
- package/dist/public/catalogue.d.ts +914 -0
- package/dist/public/catalogue.js +516 -0
- package/dist/public/chatCompletions.d.ts +381 -0
- package/dist/public/chatCompletions.js +321 -0
- package/dist/public/decisions.d.ts +182 -0
- package/dist/public/decisions.js +257 -0
- package/dist/public/embeddings.d.ts +34 -0
- package/dist/public/embeddings.js +66 -0
- package/dist/public/errors.d.ts +195 -0
- package/dist/public/errors.js +274 -0
- package/dist/public/generations.d.ts +330 -0
- package/dist/public/generations.js +121 -0
- package/dist/public/identifiers.d.ts +139 -0
- package/dist/public/identifiers.js +160 -0
- package/dist/public/images.d.ts +36 -0
- package/dist/public/images.js +36 -0
- package/dist/public/messages.d.ts +297 -0
- package/dist/public/messages.js +251 -0
- package/dist/public/money.d.ts +308 -0
- package/dist/public/money.js +237 -0
- package/dist/public/policy.d.ts +19 -0
- package/dist/public/policy.js +20 -0
- package/dist/public/realtime.d.ts +2006 -0
- package/dist/public/realtime.js +797 -0
- package/dist/public/responses.d.ts +253 -0
- package/dist/public/responses.js +95 -0
- package/dist/public/streamEvents.d.ts +372 -0
- package/dist/public/streamEvents.js +157 -0
- package/dist/public/version.d.ts +177 -0
- package/dist/public/version.js +177 -0
- package/openapi.json +6967 -0
- package/package.json +44 -4
- package/README.md +0 -3
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messages, content parts, tools and generation controls shared by every
|
|
3
|
+
* public request and by the signed envelope.
|
|
4
|
+
*
|
|
5
|
+
* Moved from `@oxy.so/contracts` 4.12.0 (`src/inference/request.ts`) by Kaana ADR 0002.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { reasoningEffortSchema } from './catalogue.js';
|
|
9
|
+
/**
|
|
10
|
+
* Where binary or remote content comes from.
|
|
11
|
+
*
|
|
12
|
+
* `url` is fetched by the data plane; `inline` carries base64 the customer sent
|
|
13
|
+
* with the request. Both are transient: neither is persisted by default, and
|
|
14
|
+
* neither appears in a receipt, a log line or a telemetry event.
|
|
15
|
+
*/
|
|
16
|
+
export const inferenceContentSourceSchema = z.discriminatedUnion('kind', [
|
|
17
|
+
z.object({ kind: z.literal('url'), url: z.string().min(1).max(4096) }).strict(),
|
|
18
|
+
z
|
|
19
|
+
.object({
|
|
20
|
+
kind: z.literal('inline'),
|
|
21
|
+
mediaType: z.string().min(1).max(255),
|
|
22
|
+
data: z.string().min(1),
|
|
23
|
+
})
|
|
24
|
+
.strict(),
|
|
25
|
+
]);
|
|
26
|
+
/**
|
|
27
|
+
* One part of a message's content. A message is always a list of parts.
|
|
28
|
+
*
|
|
29
|
+
* ## `refusal` is a member and `reasoning` is not, and the asymmetry is the point
|
|
30
|
+
*
|
|
31
|
+
* A model that DECLINES says why, and those words are meant for the customer:
|
|
32
|
+
* they are the difference between "rephrase this" and "stop asking". Before this
|
|
33
|
+
* member existed there was nowhere in an {@link InferenceMessage} to put them, so
|
|
34
|
+
* a non-streaming fold kept `finishReason: 'refusal'` and dropped the sentence —
|
|
35
|
+
* the customer learned they were refused and not why, while a streaming caller of
|
|
36
|
+
* the same request got the whole explanation on the `refusal` delta channel.
|
|
37
|
+
*
|
|
38
|
+
* Reasoning is the opposite case and stays absent. It is the model's private
|
|
39
|
+
* working, and a `text` part is where somebody would put it — which renders
|
|
40
|
+
* private reasoning to the customer AS the answer, the product bug the delta
|
|
41
|
+
* channels exist to prevent. An opaque per-block reasoning blob crossing this
|
|
42
|
+
* boundary needs a home nobody has chosen yet, and inventing one here would
|
|
43
|
+
* choose it by accident.
|
|
44
|
+
*
|
|
45
|
+
* Both public dialects can carry a refusal, which is what separates the two
|
|
46
|
+
* cases at the boundary as well as in principle: `refusal` is OpenAI's OWN field
|
|
47
|
+
* in both of its shapes (`delta.refusal` streaming, `message.refusal`
|
|
48
|
+
* non-streaming), while reasoning has no OpenAI field at all — the
|
|
49
|
+
* `reasoning_content`/`reasoning` spellings an OpenAI-compatible provider emits
|
|
50
|
+
* are provider extensions. So carrying the refusal costs no dialect its
|
|
51
|
+
* standard-client parseability, and carrying reasoning would.
|
|
52
|
+
*
|
|
53
|
+
* The part is a MEMBER rather than a field because a refusal need not have text:
|
|
54
|
+
* an Anthropic `stop_reason: "refusal"` maps to the finish reason and separates
|
|
55
|
+
* no words from the answer, while an OpenAI-compatible `refusal` does. A required
|
|
56
|
+
* field would force the first provider to invent a sentence.
|
|
57
|
+
*/
|
|
58
|
+
export const inferenceContentPartSchema = z.discriminatedUnion('type', [
|
|
59
|
+
z.object({ type: z.literal('text'), text: z.string() }).strict(),
|
|
60
|
+
z
|
|
61
|
+
.object({
|
|
62
|
+
type: z.literal('image'),
|
|
63
|
+
source: inferenceContentSourceSchema,
|
|
64
|
+
/** Provider-independent hint; providers that ignore it are unaffected. */
|
|
65
|
+
detail: z.enum(['auto', 'low', 'high']).optional(),
|
|
66
|
+
})
|
|
67
|
+
.strict(),
|
|
68
|
+
z.object({ type: z.literal('audio'), source: inferenceContentSourceSchema }).strict(),
|
|
69
|
+
z
|
|
70
|
+
.object({
|
|
71
|
+
type: z.literal('file'),
|
|
72
|
+
source: inferenceContentSourceSchema,
|
|
73
|
+
filename: z.string().max(255).optional(),
|
|
74
|
+
})
|
|
75
|
+
.strict(),
|
|
76
|
+
/**
|
|
77
|
+
* A model's explanation for declining. Its own part, never a `text` one, so no
|
|
78
|
+
* renderer can present a refusal as the answer that was asked for.
|
|
79
|
+
*/
|
|
80
|
+
z.object({ type: z.literal('refusal'), text: z.string() }).strict(),
|
|
81
|
+
]);
|
|
82
|
+
/**
|
|
83
|
+
* A tool call an assistant made, in the normalized form.
|
|
84
|
+
*
|
|
85
|
+
* `arguments` is the JSON TEXT the model emitted, not a parsed object: models
|
|
86
|
+
* emit invalid JSON often enough that parsing it here would turn a recoverable
|
|
87
|
+
* model mistake into a rejected message.
|
|
88
|
+
*/
|
|
89
|
+
export const inferenceToolCallSchema = z
|
|
90
|
+
.object({
|
|
91
|
+
id: z.string().min(1).max(128),
|
|
92
|
+
name: z.string().min(1).max(128),
|
|
93
|
+
arguments: z.string(),
|
|
94
|
+
})
|
|
95
|
+
.strict();
|
|
96
|
+
export const inferenceMessageRoleSchema = z.enum([
|
|
97
|
+
'system',
|
|
98
|
+
'developer',
|
|
99
|
+
'user',
|
|
100
|
+
'assistant',
|
|
101
|
+
'tool',
|
|
102
|
+
]);
|
|
103
|
+
/**
|
|
104
|
+
* One normalized message.
|
|
105
|
+
*
|
|
106
|
+
* The role-specific fields are refined rather than modelled as a discriminated
|
|
107
|
+
* union so the shape stays the one a caller recognises from the OpenAI-style
|
|
108
|
+
* dialects; the refinement is what stops `toolCallId` from riding on a user
|
|
109
|
+
* message, where every provider would silently ignore it.
|
|
110
|
+
*/
|
|
111
|
+
export const inferenceMessageSchema = z
|
|
112
|
+
.object({
|
|
113
|
+
role: inferenceMessageRoleSchema,
|
|
114
|
+
content: z.array(inferenceContentPartSchema),
|
|
115
|
+
/** Participant name, where the dialect supports naming participants. */
|
|
116
|
+
name: z.string().max(128).optional(),
|
|
117
|
+
/** The tool call this message answers. Required on, and only on, `tool`. */
|
|
118
|
+
toolCallId: z.string().min(1).max(128).optional(),
|
|
119
|
+
/** Tool calls the assistant made. Only on `assistant`. */
|
|
120
|
+
toolCalls: z.array(inferenceToolCallSchema).optional(),
|
|
121
|
+
})
|
|
122
|
+
.strict()
|
|
123
|
+
.superRefine((message, ctx) => {
|
|
124
|
+
if (message.role === 'tool' && message.toolCallId === undefined) {
|
|
125
|
+
ctx.addIssue({
|
|
126
|
+
code: 'custom',
|
|
127
|
+
path: ['toolCallId'],
|
|
128
|
+
message: 'a tool message must name the tool call it answers',
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (message.role !== 'tool' && message.toolCallId !== undefined) {
|
|
132
|
+
ctx.addIssue({
|
|
133
|
+
code: 'custom',
|
|
134
|
+
path: ['toolCallId'],
|
|
135
|
+
message: 'only a tool message answers a tool call',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (message.role !== 'assistant' && message.toolCalls !== undefined) {
|
|
139
|
+
ctx.addIssue({
|
|
140
|
+
code: 'custom',
|
|
141
|
+
path: ['toolCalls'],
|
|
142
|
+
message: 'only an assistant message makes tool calls',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
// Same rule as `toolCalls`, for the same reason: only the assistant can
|
|
146
|
+
// decline, so a refusal on any other role is a part every provider would
|
|
147
|
+
// silently ignore — and one a renderer might not.
|
|
148
|
+
if (message.role !== 'assistant') {
|
|
149
|
+
for (const [index, part] of message.content.entries()) {
|
|
150
|
+
if (part.type === 'refusal') {
|
|
151
|
+
ctx.addIssue({
|
|
152
|
+
code: 'custom',
|
|
153
|
+
path: ['content', index, 'type'],
|
|
154
|
+
message: 'only an assistant message carries a refusal',
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
/** Sampling parameters, all optional: absent means the route's own default. */
|
|
161
|
+
export const samplingParametersSchema = z
|
|
162
|
+
.object({
|
|
163
|
+
temperature: z.number().min(0).max(2).optional(),
|
|
164
|
+
topP: z.number().min(0).max(1).optional(),
|
|
165
|
+
topK: z.number().int().positive().safe().optional(),
|
|
166
|
+
frequencyPenalty: z.number().min(-2).max(2).optional(),
|
|
167
|
+
presencePenalty: z.number().min(-2).max(2).optional(),
|
|
168
|
+
/** A seed makes a request reproducible on providers that honour one. */
|
|
169
|
+
seed: z.number().int().safe().optional(),
|
|
170
|
+
stopSequences: z.array(z.string().min(1).max(256)).max(8).optional(),
|
|
171
|
+
})
|
|
172
|
+
.strict();
|
|
173
|
+
/**
|
|
174
|
+
* A tool the model may call. `parameters` is a JSON Schema document, carried
|
|
175
|
+
* as an opaque object: validating the customer's JSON Schema against a meta
|
|
176
|
+
* schema here would reject documents providers accept.
|
|
177
|
+
*/
|
|
178
|
+
export const toolDefinitionSchema = z
|
|
179
|
+
.object({
|
|
180
|
+
type: z.literal('function'),
|
|
181
|
+
name: z.string().min(1).max(128),
|
|
182
|
+
description: z.string().max(2000).optional(),
|
|
183
|
+
parameters: z.record(z.string(), z.unknown()),
|
|
184
|
+
/** Ask the provider to enforce the schema, where it supports enforcement. */
|
|
185
|
+
strict: z.boolean().optional(),
|
|
186
|
+
})
|
|
187
|
+
.strict();
|
|
188
|
+
/** Whether, and which, tool the model must call. */
|
|
189
|
+
export const toolChoiceSchema = z.union([
|
|
190
|
+
z.enum(['auto', 'none', 'required']),
|
|
191
|
+
z.object({ type: z.literal('function'), name: z.string().min(1).max(128) }).strict(),
|
|
192
|
+
]);
|
|
193
|
+
/** Structured-output request: free text, any JSON object, or a named schema. */
|
|
194
|
+
export const responseFormatSchema = z.discriminatedUnion('type', [
|
|
195
|
+
z.object({ type: z.literal('text') }).strict(),
|
|
196
|
+
z.object({ type: z.literal('json_object') }).strict(),
|
|
197
|
+
z
|
|
198
|
+
.object({
|
|
199
|
+
type: z.literal('json_schema'),
|
|
200
|
+
name: z.string().min(1).max(128),
|
|
201
|
+
schema: z.record(z.string(), z.unknown()),
|
|
202
|
+
strict: z.boolean(),
|
|
203
|
+
})
|
|
204
|
+
.strict(),
|
|
205
|
+
]);
|
|
206
|
+
/**
|
|
207
|
+
* The caller's reasoning control. Strict, like every leaf: a provider-specific
|
|
208
|
+
* knob (`budget_tokens`, `summary`) is refused rather than silently dropped.
|
|
209
|
+
*
|
|
210
|
+
* Oxy forwards it only after checking the resolved model advertises the effort
|
|
211
|
+
* in its catalogue `reasoningEfforts`; Kaana translates it per provider.
|
|
212
|
+
*/
|
|
213
|
+
export const inferenceReasoningSchema = z.object({ effort: reasoningEffortSchema }).strict();
|
|
214
|
+
/** Parameters for text-to-speech, preserved in the signed request. */
|
|
215
|
+
export const inferenceSpeechParametersSchema = z
|
|
216
|
+
.object({
|
|
217
|
+
voice: z.string().min(1).max(64),
|
|
218
|
+
responseFormat: z.enum(['mp3', 'opus', 'aac', 'flac', 'wav', 'pcm']),
|
|
219
|
+
speed: z.number().min(0.25).max(4).optional(),
|
|
220
|
+
})
|
|
221
|
+
.strict();
|
|
222
|
+
/**
|
|
223
|
+
* Spoken output from a CONVERSATIONAL model (contract set 3.2.0) — an audio chat
|
|
224
|
+
* model answering in its own voice, as opposed to `speech`, which reads given
|
|
225
|
+
* text aloud. Presence is the request: a chat request carrying this asks for
|
|
226
|
+
* the answer as audio AND its transcript, which is the only combination the
|
|
227
|
+
* audio chat models produce (OpenAI's `modalities: ["text", "audio"]`).
|
|
228
|
+
*
|
|
229
|
+
* The audio streams as `audio` events and the words as `delta` events on the
|
|
230
|
+
* `output_audio_transcript` channel — never as `output_text`, because a
|
|
231
|
+
* transcript of speech the customer is already hearing is not a second answer.
|
|
232
|
+
* `pcm` is the only format an audio chat model can STREAM; the refinement on the
|
|
233
|
+
* envelope holds that rather than leaving a provider to refuse it mid-flight.
|
|
234
|
+
*/
|
|
235
|
+
export const inferenceAudioOutputParametersSchema = z
|
|
236
|
+
.object({
|
|
237
|
+
voice: z.string().min(1).max(64),
|
|
238
|
+
format: z.enum(['wav', 'mp3', 'flac', 'opus', 'pcm']),
|
|
239
|
+
})
|
|
240
|
+
.strict();
|
|
241
|
+
/**
|
|
242
|
+
* Customer-supplied cost-attribution labels, echoed on the generation record.
|
|
243
|
+
*
|
|
244
|
+
* Bounded in both directions because they are stored and read back: sixteen
|
|
245
|
+
* keys is a team/feature/environment tag set, not a place to smuggle a payload.
|
|
246
|
+
*
|
|
247
|
+
* Moved from the Oxy edge (`packages/api/src/schemas/inferenceEdge.schemas.ts`).
|
|
248
|
+
*/
|
|
249
|
+
export const requestLabelsSchema = z
|
|
250
|
+
.record(z.string(), z.string().max(256))
|
|
251
|
+
.refine((labels) => Object.keys(labels).length <= 16, 'at most 16 labels may be attached to one request');
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Money, prices and usage units.
|
|
3
|
+
*
|
|
4
|
+
* Moved from `@oxy.so/contracts` 4.12.0 (`src/inference/money.ts`) by Kaana ADR 0002.
|
|
5
|
+
* `priceSnapshotSchema` comes from `priceVersion.ts`; the price VERSION record
|
|
6
|
+
* itself stays in `@oxy.so/contracts`, because Oxy owns the price lists.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
/** ISO 4217 alpha-3 currency code, e.g. `USD`. */
|
|
10
|
+
export declare const currencyCodeSchema: z.ZodString;
|
|
11
|
+
/**
|
|
12
|
+
* The declared fractional scale of every amount and price in this contract, and
|
|
13
|
+
* of the `NUMERIC` columns the ledger stores them in.
|
|
14
|
+
*
|
|
15
|
+
* Twelve digits is sub-minor-unit precision by a wide margin: at $3 per million
|
|
16
|
+
* input tokens, one token costs `0.000003000000`, which this scale represents
|
|
17
|
+
* exactly. Amounts are compared and summed NUMERICALLY, never as text — `3.0`
|
|
18
|
+
* and `3.000000000000` are one amount written two ways.
|
|
19
|
+
*/
|
|
20
|
+
export declare const INFERENCE_MONEY_SCALE = 12;
|
|
21
|
+
/**
|
|
22
|
+
* An exact non-negative decimal, carried as a STRING so no parse step can turn
|
|
23
|
+
* it into a float on the way past. Up to 18 integer digits and
|
|
24
|
+
* {@link INFERENCE_MONEY_SCALE} fractional digits.
|
|
25
|
+
*
|
|
26
|
+
* Non-negative: direction is carried by the SHAPE — a receipt debits, a refund
|
|
27
|
+
* credits — so a stray sign can never silently invert an entry.
|
|
28
|
+
*
|
|
29
|
+
* No exponent form: `1e-6` and `0.000001` are the same number, but only one of
|
|
30
|
+
* them survives a naive string comparison, a cache key or a log grep intact.
|
|
31
|
+
*
|
|
32
|
+
* Branded, so a bare `string` is not assignable and an amount cannot arrive
|
|
33
|
+
* from string concatenation that was never checked. Producers construct one
|
|
34
|
+
* with `exactDecimalSchema.parse(value)`.
|
|
35
|
+
*/
|
|
36
|
+
export declare const exactDecimalSchema: z.core.$ZodBranded<z.ZodString, "ExactDecimal", "out">;
|
|
37
|
+
/**
|
|
38
|
+
* An amount of money: the exact decimal plus the currency it is in.
|
|
39
|
+
*
|
|
40
|
+
* `.strict()` so a payload carrying a convenience float beside the exact value
|
|
41
|
+
* (`{ amount: '18.06', amountFloat: 18.06 }`) is REJECTED rather than stripped.
|
|
42
|
+
* A stripped float is the more dangerous outcome: it disappears silently here
|
|
43
|
+
* and survives in the producer, where it is the value somebody eventually
|
|
44
|
+
* displays.
|
|
45
|
+
*/
|
|
46
|
+
export declare const moneySchema: z.ZodObject<{
|
|
47
|
+
amount: z.core.$ZodBranded<z.ZodString, "ExactDecimal", "out">;
|
|
48
|
+
currency: z.ZodString;
|
|
49
|
+
}, z.core.$strict>;
|
|
50
|
+
/**
|
|
51
|
+
* The closed set of units inference is metered in.
|
|
52
|
+
*
|
|
53
|
+
* **The units PARTITION a request: every unit counts material no other unit
|
|
54
|
+
* counts.** `cached_input_tokens` is not part of `input_tokens`, and
|
|
55
|
+
* `reasoning_tokens` is not part of `output_tokens` — they are siblings, not
|
|
56
|
+
* subsets. A request whose 10 000-token prompt was served 9 000 tokens from
|
|
57
|
+
* cache is reported as `input_tokens: 1000` beside `cached_input_tokens: 9000`,
|
|
58
|
+
* never as `input_tokens: 10000` beside it.
|
|
59
|
+
*
|
|
60
|
+
* That belongs to the definition rather than to a convention somewhere else,
|
|
61
|
+
* because settlement applies a price to EVERY reported unit and sums them
|
|
62
|
+
* (`inferenceLedger.service.ts`'s `computeCharge`). Under the partition rule
|
|
63
|
+
* that sum IS the request's cost, and a cached token can carry its own — lower
|
|
64
|
+
* — price. Under the nested reading the same sum charges the cached and
|
|
65
|
+
* reasoning tokens twice: once inside their parent and once on their own line.
|
|
66
|
+
* It fails silently, because every total still looks plausible and the receipt
|
|
67
|
+
* is still internally consistent, and on a reasoning model the reasoning tokens
|
|
68
|
+
* can dominate the completion, so the error is not marginal.
|
|
69
|
+
*
|
|
70
|
+
* **Every OpenAI-compatible provider reports the other way round**:
|
|
71
|
+
* `prompt_tokens` INCLUDES `prompt_tokens_details.cached_tokens`, and
|
|
72
|
+
* `completion_tokens` INCLUDES `completion_tokens_details.reasoning_tokens`.
|
|
73
|
+
* Normalising is the data plane's job and it is subtraction:
|
|
74
|
+
*
|
|
75
|
+
* ```text
|
|
76
|
+
* input_tokens = prompt_tokens - prompt_tokens_details.cached_tokens
|
|
77
|
+
* output_tokens = completion_tokens - completion_tokens_details.reasoning_tokens
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* **Audio tokens are siblings too** (added in contract set 3.2.0). A model that
|
|
81
|
+
* consumes or produces audio AS TOKENS — OpenAI's audio chat and Realtime
|
|
82
|
+
* models — reports them inside the same totals, and they are priced several
|
|
83
|
+
* times higher than text tokens, so folding them into `input_tokens` would
|
|
84
|
+
* under-charge by exactly the audio. They are subtracted out the same way:
|
|
85
|
+
*
|
|
86
|
+
* ```text
|
|
87
|
+
* cached_audio_input_tokens = prompt_tokens_details.cached_tokens_details.audio_tokens
|
|
88
|
+
* audio_input_tokens = prompt_tokens_details.audio_tokens - cached_audio_input_tokens
|
|
89
|
+
* input_tokens = prompt_tokens - (text AND audio cached) - audio_input_tokens
|
|
90
|
+
* audio_output_tokens = completion_tokens_details.audio_tokens
|
|
91
|
+
* output_tokens = completion_tokens - reasoning_tokens - audio_output_tokens
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* `audio_input_milliseconds` and `audio_output_milliseconds` remain the units
|
|
95
|
+
* for audio metered by DURATION (file transcription, speech synthesis). A
|
|
96
|
+
* report carries one reading of a given audio, never both: the same audio
|
|
97
|
+
* reported as tokens and as milliseconds is the double charge this rule exists
|
|
98
|
+
* to prevent.
|
|
99
|
+
*
|
|
100
|
+
* **`session_milliseconds` is session wall-clock time** (added in contract set
|
|
101
|
+
* 3.3.0): wall-clock milliseconds a realtime session was open with the upstream
|
|
102
|
+
* provider; reported only by a provider that bills session time, and never
|
|
103
|
+
* together with a per-audio reading of the same span. xAI's Voice Agent API
|
|
104
|
+
* bills a session using server-side voice activity detection for its whole
|
|
105
|
+
* duration, speech or silence, which is neither audio sent nor audio received:
|
|
106
|
+
* reporting that clock as `audio_input_milliseconds` would misdescribe the
|
|
107
|
+
* charge, and reporting both would bill the same span twice. It is measured by
|
|
108
|
+
* the data plane from the upstream session's open to its close, never
|
|
109
|
+
* estimated from the audio, and a provider billed by the audio it carries (or
|
|
110
|
+
* by tokens) never reports it.
|
|
111
|
+
*
|
|
112
|
+
* **`input_transcription_milliseconds` is a second model's reading** (added in
|
|
113
|
+
* contract set 4.1.0): milliseconds of a realtime session's input audio that
|
|
114
|
+
* the deployment's own TRANSCRIPTION model transcribed, when the provider bills
|
|
115
|
+
* that model apart from the session model. OpenAI runs input transcription on
|
|
116
|
+
* a separate ASR model "billed according to the ASR model's pricing rather
|
|
117
|
+
* than the realtime model's", so the session model's `audio_input_tokens` and
|
|
118
|
+
* this unit are two charges for two models, each read once — which is why the
|
|
119
|
+
* partition rule holds: no other unit counts the transcription model's work.
|
|
120
|
+
* Reporting the ASR duration as `audio_input_milliseconds` beside the session
|
|
121
|
+
* model's audio tokens would be the "same audio as tokens and as milliseconds"
|
|
122
|
+
* this rule forbids, and folding it into the tokens would price it at the
|
|
123
|
+
* wrong model's rate. A provider whose session price already includes the
|
|
124
|
+
* transcription (xAI's Voice Agent) never reports it. A transcription model
|
|
125
|
+
* billed by TOKENS is a charge this unit does not express: the data plane
|
|
126
|
+
* declares only duration-billed transcription models and ends a session whose
|
|
127
|
+
* provider reports tokens for one, rather than approximate the charge.
|
|
128
|
+
*
|
|
129
|
+
* No refinement in this package can enforce it, and saying so is part of the
|
|
130
|
+
* rule: a nested report and a disjoint one are the same four non-negative
|
|
131
|
+
* integers, so no predicate over a single report can tell them apart. The two
|
|
132
|
+
* structural guards that DO exist — refining `cached <= input` and
|
|
133
|
+
* `reasoning <= output`, or deriving the parents instead of reporting them —
|
|
134
|
+
* both encode the nested reading, which is the one this rule rejects. What IS
|
|
135
|
+
* enforceable is the arithmetic that depends on the rule, and that is where the
|
|
136
|
+
* enforcement lives — `inferenceLedger.service.test.ts` prices a report in
|
|
137
|
+
* which cached and reasoning tokens are both non-zero and asserts the exact
|
|
138
|
+
* total, which the nested reading cannot produce.
|
|
139
|
+
*
|
|
140
|
+
* Where the public surface has to speak a nested dialect, the sum is put back
|
|
141
|
+
* at the boundary rather than the internal reading being bent to it
|
|
142
|
+
* (`routes/inferenceEdge.ts` renders `prompt_tokens` as
|
|
143
|
+
* `input_tokens + cached_input_tokens`).
|
|
144
|
+
*
|
|
145
|
+
* Time is carried in integer MILLISECONDS rather than seconds so that no unit
|
|
146
|
+
* quantity is ever fractional: a 12.5-second transcription is `12500`, exactly,
|
|
147
|
+
* and the "units are integers" rule holds for every modality instead of holding
|
|
148
|
+
* for tokens and being quietly broken by audio.
|
|
149
|
+
*/
|
|
150
|
+
export declare const USAGE_UNITS: readonly ["input_tokens", "cached_input_tokens", "output_tokens", "reasoning_tokens", "requests", "images", "audio_input_milliseconds", "audio_output_milliseconds", "video_milliseconds", "characters", "embeddings", "audio_input_tokens", "cached_audio_input_tokens", "audio_output_tokens", "session_milliseconds", "input_transcription_milliseconds"];
|
|
151
|
+
export declare const usageUnitSchema: z.ZodEnum<{
|
|
152
|
+
input_tokens: "input_tokens";
|
|
153
|
+
cached_input_tokens: "cached_input_tokens";
|
|
154
|
+
output_tokens: "output_tokens";
|
|
155
|
+
reasoning_tokens: "reasoning_tokens";
|
|
156
|
+
requests: "requests";
|
|
157
|
+
images: "images";
|
|
158
|
+
audio_input_milliseconds: "audio_input_milliseconds";
|
|
159
|
+
audio_output_milliseconds: "audio_output_milliseconds";
|
|
160
|
+
video_milliseconds: "video_milliseconds";
|
|
161
|
+
characters: "characters";
|
|
162
|
+
embeddings: "embeddings";
|
|
163
|
+
audio_input_tokens: "audio_input_tokens";
|
|
164
|
+
cached_audio_input_tokens: "cached_audio_input_tokens";
|
|
165
|
+
audio_output_tokens: "audio_output_tokens";
|
|
166
|
+
session_milliseconds: "session_milliseconds";
|
|
167
|
+
input_transcription_milliseconds: "input_transcription_milliseconds";
|
|
168
|
+
}>;
|
|
169
|
+
/**
|
|
170
|
+
* A metered quantity of ONE unit. Never money — a quantity carries no price and
|
|
171
|
+
* no currency, so a consumer cannot mistake a token count for an amount owed.
|
|
172
|
+
*/
|
|
173
|
+
export declare const usageQuantitySchema: z.ZodObject<{
|
|
174
|
+
unit: z.ZodEnum<{
|
|
175
|
+
input_tokens: "input_tokens";
|
|
176
|
+
cached_input_tokens: "cached_input_tokens";
|
|
177
|
+
output_tokens: "output_tokens";
|
|
178
|
+
reasoning_tokens: "reasoning_tokens";
|
|
179
|
+
requests: "requests";
|
|
180
|
+
images: "images";
|
|
181
|
+
audio_input_milliseconds: "audio_input_milliseconds";
|
|
182
|
+
audio_output_milliseconds: "audio_output_milliseconds";
|
|
183
|
+
video_milliseconds: "video_milliseconds";
|
|
184
|
+
characters: "characters";
|
|
185
|
+
embeddings: "embeddings";
|
|
186
|
+
audio_input_tokens: "audio_input_tokens";
|
|
187
|
+
cached_audio_input_tokens: "cached_audio_input_tokens";
|
|
188
|
+
audio_output_tokens: "audio_output_tokens";
|
|
189
|
+
session_milliseconds: "session_milliseconds";
|
|
190
|
+
input_transcription_milliseconds: "input_transcription_milliseconds";
|
|
191
|
+
}>;
|
|
192
|
+
quantity: z.ZodNumber;
|
|
193
|
+
}, z.core.$strict>;
|
|
194
|
+
/**
|
|
195
|
+
* Where a metered quantity came from.
|
|
196
|
+
*
|
|
197
|
+
* Kept explicit because the three are not interchangeable when a charge is
|
|
198
|
+
* disputed: `provider_reported` is the upstream's own count, `oxy_measured` is
|
|
199
|
+
* counted by the platform (streamed bytes, wall-clock milliseconds), and
|
|
200
|
+
* `estimated` is a reconstruction used when a provider returned no usage at
|
|
201
|
+
* all. An estimate that is indistinguishable from a reported number is an
|
|
202
|
+
* estimate nobody can later reconcile or refund against.
|
|
203
|
+
*/
|
|
204
|
+
export declare const USAGE_SOURCES: readonly ["provider_reported", "oxy_measured", "estimated"];
|
|
205
|
+
export declare const usageSourceSchema: z.ZodEnum<{
|
|
206
|
+
provider_reported: "provider_reported";
|
|
207
|
+
oxy_measured: "oxy_measured";
|
|
208
|
+
estimated: "estimated";
|
|
209
|
+
}>;
|
|
210
|
+
/**
|
|
211
|
+
* A price for one unit, as `amount` per `per` units — `per` because a price
|
|
212
|
+
* quoted per single token would need more fractional digits than it is worth
|
|
213
|
+
* ("$3.00 per 1000000 input_tokens" is how every provider quotes it, and how
|
|
214
|
+
* every customer reads it).
|
|
215
|
+
*/
|
|
216
|
+
export declare const unitPriceSchema: z.ZodObject<{
|
|
217
|
+
unit: z.ZodEnum<{
|
|
218
|
+
input_tokens: "input_tokens";
|
|
219
|
+
cached_input_tokens: "cached_input_tokens";
|
|
220
|
+
output_tokens: "output_tokens";
|
|
221
|
+
reasoning_tokens: "reasoning_tokens";
|
|
222
|
+
requests: "requests";
|
|
223
|
+
images: "images";
|
|
224
|
+
audio_input_milliseconds: "audio_input_milliseconds";
|
|
225
|
+
audio_output_milliseconds: "audio_output_milliseconds";
|
|
226
|
+
video_milliseconds: "video_milliseconds";
|
|
227
|
+
characters: "characters";
|
|
228
|
+
embeddings: "embeddings";
|
|
229
|
+
audio_input_tokens: "audio_input_tokens";
|
|
230
|
+
cached_audio_input_tokens: "cached_audio_input_tokens";
|
|
231
|
+
audio_output_tokens: "audio_output_tokens";
|
|
232
|
+
session_milliseconds: "session_milliseconds";
|
|
233
|
+
input_transcription_milliseconds: "input_transcription_milliseconds";
|
|
234
|
+
}>;
|
|
235
|
+
amount: z.core.$ZodBranded<z.ZodString, "ExactDecimal", "out">;
|
|
236
|
+
per: z.ZodNumber;
|
|
237
|
+
currency: z.ZodString;
|
|
238
|
+
}, z.core.$strict>;
|
|
239
|
+
/**
|
|
240
|
+
* The price snapshot a settled receipt keeps.
|
|
241
|
+
*
|
|
242
|
+
* The unit prices are COPIED onto the receipt, not just referenced, so a receipt
|
|
243
|
+
* remains readable even if the price version record is later archived, and so
|
|
244
|
+
* that a mistake in the copy is visible as a disagreement with the version it
|
|
245
|
+
* names rather than silently invisible.
|
|
246
|
+
*/
|
|
247
|
+
export declare const priceSnapshotSchema: z.ZodObject<{
|
|
248
|
+
priceVersionId: z.ZodString;
|
|
249
|
+
currency: z.ZodString;
|
|
250
|
+
unitPrices: z.ZodArray<z.ZodObject<{
|
|
251
|
+
unit: z.ZodEnum<{
|
|
252
|
+
input_tokens: "input_tokens";
|
|
253
|
+
cached_input_tokens: "cached_input_tokens";
|
|
254
|
+
output_tokens: "output_tokens";
|
|
255
|
+
reasoning_tokens: "reasoning_tokens";
|
|
256
|
+
requests: "requests";
|
|
257
|
+
images: "images";
|
|
258
|
+
audio_input_milliseconds: "audio_input_milliseconds";
|
|
259
|
+
audio_output_milliseconds: "audio_output_milliseconds";
|
|
260
|
+
video_milliseconds: "video_milliseconds";
|
|
261
|
+
characters: "characters";
|
|
262
|
+
embeddings: "embeddings";
|
|
263
|
+
audio_input_tokens: "audio_input_tokens";
|
|
264
|
+
cached_audio_input_tokens: "cached_audio_input_tokens";
|
|
265
|
+
audio_output_tokens: "audio_output_tokens";
|
|
266
|
+
session_milliseconds: "session_milliseconds";
|
|
267
|
+
input_transcription_milliseconds: "input_transcription_milliseconds";
|
|
268
|
+
}>;
|
|
269
|
+
amount: z.core.$ZodBranded<z.ZodString, "ExactDecimal", "out">;
|
|
270
|
+
per: z.ZodNumber;
|
|
271
|
+
currency: z.ZodString;
|
|
272
|
+
}, z.core.$strict>>;
|
|
273
|
+
}, z.core.$strict>;
|
|
274
|
+
/**
|
|
275
|
+
* What a request consumed, as a public response reports it: one quantity per
|
|
276
|
+
* unit, each unit at most once, the units partitioning the request (see
|
|
277
|
+
* {@link USAGE_UNITS}). New in contract set 4.0.0.
|
|
278
|
+
*/
|
|
279
|
+
export declare const usageSchema: z.ZodArray<z.ZodObject<{
|
|
280
|
+
unit: z.ZodEnum<{
|
|
281
|
+
input_tokens: "input_tokens";
|
|
282
|
+
cached_input_tokens: "cached_input_tokens";
|
|
283
|
+
output_tokens: "output_tokens";
|
|
284
|
+
reasoning_tokens: "reasoning_tokens";
|
|
285
|
+
requests: "requests";
|
|
286
|
+
images: "images";
|
|
287
|
+
audio_input_milliseconds: "audio_input_milliseconds";
|
|
288
|
+
audio_output_milliseconds: "audio_output_milliseconds";
|
|
289
|
+
video_milliseconds: "video_milliseconds";
|
|
290
|
+
characters: "characters";
|
|
291
|
+
embeddings: "embeddings";
|
|
292
|
+
audio_input_tokens: "audio_input_tokens";
|
|
293
|
+
cached_audio_input_tokens: "cached_audio_input_tokens";
|
|
294
|
+
audio_output_tokens: "audio_output_tokens";
|
|
295
|
+
session_milliseconds: "session_milliseconds";
|
|
296
|
+
input_transcription_milliseconds: "input_transcription_milliseconds";
|
|
297
|
+
}>;
|
|
298
|
+
quantity: z.ZodNumber;
|
|
299
|
+
}, z.core.$strict>>;
|
|
300
|
+
export type CurrencyCode = z.infer<typeof currencyCodeSchema>;
|
|
301
|
+
export type ExactDecimal = z.infer<typeof exactDecimalSchema>;
|
|
302
|
+
export type Money = z.infer<typeof moneySchema>;
|
|
303
|
+
export type UsageUnit = z.infer<typeof usageUnitSchema>;
|
|
304
|
+
export type UsageSource = z.infer<typeof usageSourceSchema>;
|
|
305
|
+
export type UsageQuantity = z.infer<typeof usageQuantitySchema>;
|
|
306
|
+
export type UnitPrice = z.infer<typeof unitPriceSchema>;
|
|
307
|
+
export type PriceSnapshot = z.infer<typeof priceSnapshotSchema>;
|
|
308
|
+
export type Usage = z.infer<typeof usageSchema>;
|