@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,516 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The public catalogue: what `GET /v1/models` and `GET /v1/power-levels` say.
|
|
3
|
+
*
|
|
4
|
+
* Moved from `@oxy.so/contracts` 4.12.0 (`src/inference/catalogue.ts`) by Kaana ADR 0002.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { inferenceDateSchema, inferenceHttpsUrlSchema, inferenceRegionSchema, inferenceTimestampSchema, modelIdSchema, modelReferenceSchema, modelRevisionLabelSchema, publisherSlugSchema, } from './identifiers.js';
|
|
8
|
+
import { audioInputMediaTypeSchema, languageTagSchema, realtimeAudioFormatSchema, } from './audioFormats.js';
|
|
9
|
+
import { priceSnapshotSchema } from './money.js';
|
|
10
|
+
import { inferenceAudioMediaTypeSchema } from './streamEvents.js';
|
|
11
|
+
/** Input/request modalities; structured decisions are an output, never an input. */
|
|
12
|
+
export const inferenceModalitySchema = z.enum(['text', 'image', 'audio', 'video', 'embedding']);
|
|
13
|
+
/** Declared model outputs, separate from request/input modalities. */
|
|
14
|
+
export const inferenceOutputModalitySchema = z.enum([
|
|
15
|
+
...inferenceModalitySchema.options,
|
|
16
|
+
'decisions',
|
|
17
|
+
]);
|
|
18
|
+
/**
|
|
19
|
+
* How hard a reasoning model is asked to think before it answers.
|
|
20
|
+
*
|
|
21
|
+
* A closed, provider-neutral vocabulary: Kaana translates each value into the
|
|
22
|
+
* serving provider's own control. A model advertises the subset it accepts in
|
|
23
|
+
* `modelCapabilitiesSchema.reasoningEfforts`, and Oxy refuses a request for an
|
|
24
|
+
* effort the resolved model does not advertise rather than dropping it.
|
|
25
|
+
*/
|
|
26
|
+
export const reasoningEffortSchema = z.enum(['low', 'medium', 'high']);
|
|
27
|
+
/**
|
|
28
|
+
* A POWER LEVEL: the public name of a product-preset routing profile that asks
|
|
29
|
+
* Oxy to choose a model by capability tier instead of naming one (contract set
|
|
30
|
+
* 3.4.0). Ordered cheapest first; `auto` asks Oxy to pick the cheapest level
|
|
31
|
+
* that suffices for the request.
|
|
32
|
+
*
|
|
33
|
+
* - `instant` — very cheap, fast small models; the least reasoning each model
|
|
34
|
+
* accepts (`low` on a reasoning model, nothing on one without effort control)
|
|
35
|
+
* - `medium` — mid-size models, low reasoning effort
|
|
36
|
+
* - `high` — strong models, medium reasoning effort
|
|
37
|
+
* - `xhigh` — strong models, high reasoning effort
|
|
38
|
+
* - `pro` — frontier models, high reasoning effort
|
|
39
|
+
* - `ultra` — the heaviest frontier models, maximum reasoning effort
|
|
40
|
+
*
|
|
41
|
+
* A power level is a routing profile, never a model: its slug has no `/`, and
|
|
42
|
+
* the response always reports the concrete model that ran.
|
|
43
|
+
*/
|
|
44
|
+
export const powerLevelSchema = z.enum([
|
|
45
|
+
'auto',
|
|
46
|
+
'instant',
|
|
47
|
+
'medium',
|
|
48
|
+
'high',
|
|
49
|
+
'xhigh',
|
|
50
|
+
'pro',
|
|
51
|
+
'ultra',
|
|
52
|
+
]);
|
|
53
|
+
/**
|
|
54
|
+
* The reviewed capability tier of one MODEL, which power levels choose from.
|
|
55
|
+
* `xhigh` and `auto` are not classes: `xhigh` runs `high`-class models at a
|
|
56
|
+
* higher effort, and `auto` chooses a level. Reviewed catalogue data with a
|
|
57
|
+
* cited public benchmark source — never inferred from a model's name.
|
|
58
|
+
*/
|
|
59
|
+
export const modelPowerClassSchema = z.enum(['instant', 'medium', 'high', 'pro', 'ultra']);
|
|
60
|
+
/**
|
|
61
|
+
* The public dialects a request can arrive in — `client.apiFormat` on the
|
|
62
|
+
* envelope, and the list a deployment declares it can execute.
|
|
63
|
+
*
|
|
64
|
+
* Named (contract set 3.2.0) rather than inline on the client metadata so the
|
|
65
|
+
* two uses cannot drift: a capability naming a dialect the envelope cannot
|
|
66
|
+
* carry, or the reverse, would authorize a request no route can serve.
|
|
67
|
+
*/
|
|
68
|
+
export const inferenceApiFormatSchema = z.enum([
|
|
69
|
+
'responses',
|
|
70
|
+
'chat_completions',
|
|
71
|
+
'embeddings',
|
|
72
|
+
'decisions',
|
|
73
|
+
'images_generations',
|
|
74
|
+
'audio_transcriptions',
|
|
75
|
+
'audio_speech',
|
|
76
|
+
'rerank',
|
|
77
|
+
'batches',
|
|
78
|
+
]);
|
|
79
|
+
/**
|
|
80
|
+
* What a realtime session is FOR. The three are different upstream products
|
|
81
|
+
* with different event families and different metering, not options of one:
|
|
82
|
+
*
|
|
83
|
+
* - `conversation` — a speech-to-speech (or text) model holding a dialogue,
|
|
84
|
+
* with responses, tool calls and interruption.
|
|
85
|
+
* - `transcription` — live speech-to-text; the model never responds, it only
|
|
86
|
+
* transcribes committed input audio.
|
|
87
|
+
* - `translation` — live speech-to-speech translation into one target language.
|
|
88
|
+
*/
|
|
89
|
+
export const realtimeSessionKindSchema = z.enum(['conversation', 'transcription', 'translation']);
|
|
90
|
+
/**
|
|
91
|
+
* How a realtime session is carried. One member today: a WebSocket, whose every
|
|
92
|
+
* frame is one JSON message (see `realtime.ts`). WebRTC and SIP are separate
|
|
93
|
+
* transports with separate media handling, and are added as members only when a
|
|
94
|
+
* deployment can actually execute them.
|
|
95
|
+
*/
|
|
96
|
+
export const realtimeSessionTransportSchema = z.enum(['websocket']);
|
|
97
|
+
/**
|
|
98
|
+
* What a deployment can execute as a realtime session. Present only on a model
|
|
99
|
+
* that holds sessions; absent means it holds none, which is the state of every
|
|
100
|
+
* catalogue written before contract set 3.2.0.
|
|
101
|
+
*
|
|
102
|
+
* Two optional fields arrived in contract set 4.1.0 (Alia ADR 0017, Kaana
|
|
103
|
+
* phase 1), and ABSENT means undeclared — a catalogue from an older producer —
|
|
104
|
+
* never "every value":
|
|
105
|
+
*
|
|
106
|
+
* - `inputAudioTranscription` — the model transcribes the caller's speech
|
|
107
|
+
* inside a `conversation` when the session asks for it
|
|
108
|
+
* (`config.inputAudioTranscription`), and delivers each turn as a
|
|
109
|
+
* `transcript.done` with `source: 'input_audio'`. Absent or `false`: a
|
|
110
|
+
* session that asks is refused before anything is held.
|
|
111
|
+
* - `audioFormats` — the realtime encodings a session may name as
|
|
112
|
+
* `config.inputAudioFormat` and `config.outputAudioFormat`. These are not the
|
|
113
|
+
* media types of `audio.audioFormats`, which describe files.
|
|
114
|
+
*/
|
|
115
|
+
export const modelRealtimeCapabilitiesSchema = z
|
|
116
|
+
.object({
|
|
117
|
+
transports: z.array(realtimeSessionTransportSchema).min(1),
|
|
118
|
+
sessionKinds: z.array(realtimeSessionKindSchema).min(1),
|
|
119
|
+
inputAudioTranscription: z.boolean().optional(),
|
|
120
|
+
audioFormats: z
|
|
121
|
+
.object({
|
|
122
|
+
input: z.array(realtimeAudioFormatSchema).min(1),
|
|
123
|
+
output: z.array(realtimeAudioFormatSchema),
|
|
124
|
+
})
|
|
125
|
+
.strict()
|
|
126
|
+
.optional(),
|
|
127
|
+
})
|
|
128
|
+
.strict();
|
|
129
|
+
/**
|
|
130
|
+
* What a model can do, in the terms a caller has to decide against before
|
|
131
|
+
* sending a request: can it call tools, does it accept images, will it honour a
|
|
132
|
+
* JSON schema, how much context does it take, how much can it emit.
|
|
133
|
+
*/
|
|
134
|
+
export const modelCapabilitiesSchema = z
|
|
135
|
+
.object({
|
|
136
|
+
inputModalities: z.array(inferenceModalitySchema).min(1),
|
|
137
|
+
outputModalities: z.array(inferenceOutputModalitySchema).min(1),
|
|
138
|
+
tools: z.boolean(),
|
|
139
|
+
parallelToolCalls: z.boolean(),
|
|
140
|
+
structuredOutput: z.boolean(),
|
|
141
|
+
jsonMode: z.boolean(),
|
|
142
|
+
reasoning: z.boolean(),
|
|
143
|
+
/**
|
|
144
|
+
* The reasoning efforts a request may name for this model, in the order
|
|
145
|
+
* low → high. Empty means the model takes no effort control at all (it may
|
|
146
|
+
* still reason: `reasoning` answers that). Added in contract set 3.1.0;
|
|
147
|
+
* absent on the wire from an older producer, it parses as `[]`.
|
|
148
|
+
*/
|
|
149
|
+
reasoningEfforts: z.array(reasoningEffortSchema).default([]),
|
|
150
|
+
streaming: z.boolean(),
|
|
151
|
+
promptCaching: z.boolean(),
|
|
152
|
+
maxContextTokens: z.number().int().positive().safe(),
|
|
153
|
+
maxOutputTokens: z.number().int().positive().safe(),
|
|
154
|
+
/**
|
|
155
|
+
* The request dialects a route to this model can EXECUTE. Modalities say
|
|
156
|
+
* what a model consumes and produces; they do not say which request shape a
|
|
157
|
+
* route can carry, and catalogue presence is not that evidence either — a
|
|
158
|
+
* speech-to-speech model is `audio` in and out and still cannot answer a
|
|
159
|
+
* `/v1/audio/transcriptions` upload. Oxy authorizes a request only when its
|
|
160
|
+
* `client.apiFormat` is listed here.
|
|
161
|
+
*
|
|
162
|
+
* Added in contract set 3.2.0. ABSENT means undeclared — a catalogue from an
|
|
163
|
+
* older producer — never "every format": a consumer then applies the rules it
|
|
164
|
+
* applied before this field existed, and authorizes no format that needs a
|
|
165
|
+
* declaration (audio output on `chat_completions`, a realtime session).
|
|
166
|
+
*/
|
|
167
|
+
apiFormats: z.array(inferenceApiFormatSchema).min(1).optional(),
|
|
168
|
+
/** Realtime sessions this model holds; absent means none. Contract set 3.2.0. */
|
|
169
|
+
realtime: modelRealtimeCapabilitiesSchema.optional(),
|
|
170
|
+
})
|
|
171
|
+
.strict()
|
|
172
|
+
.superRefine((capabilities, ctx) => {
|
|
173
|
+
const formats = capabilities.apiFormats;
|
|
174
|
+
if (formats !== undefined && new Set(formats).size !== formats.length) {
|
|
175
|
+
ctx.addIssue({
|
|
176
|
+
code: 'custom',
|
|
177
|
+
path: ['apiFormats'],
|
|
178
|
+
message: 'each API format is declared at most once',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const realtime = capabilities.realtime;
|
|
182
|
+
if (realtime === undefined)
|
|
183
|
+
return;
|
|
184
|
+
if (new Set(realtime.sessionKinds).size !== realtime.sessionKinds.length) {
|
|
185
|
+
ctx.addIssue({
|
|
186
|
+
code: 'custom',
|
|
187
|
+
path: ['realtime', 'sessionKinds'],
|
|
188
|
+
message: 'each session kind is declared at most once',
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (new Set(realtime.transports).size !== realtime.transports.length) {
|
|
192
|
+
ctx.addIssue({
|
|
193
|
+
code: 'custom',
|
|
194
|
+
path: ['realtime', 'transports'],
|
|
195
|
+
message: 'each transport is declared at most once',
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
for (const direction of ['input', 'output']) {
|
|
199
|
+
const formats = realtime.audioFormats?.[direction];
|
|
200
|
+
if (formats !== undefined && new Set(formats).size !== formats.length) {
|
|
201
|
+
ctx.addIssue({
|
|
202
|
+
code: 'custom',
|
|
203
|
+
path: ['realtime', 'audioFormats', direction],
|
|
204
|
+
message: 'each realtime audio format is declared at most once',
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// A realtime session consumes audio: a model that declares sessions and no
|
|
209
|
+
// audio input is a declaration no session could honour.
|
|
210
|
+
if (!capabilities.inputModalities.includes('audio')) {
|
|
211
|
+
ctx.addIssue({
|
|
212
|
+
code: 'custom',
|
|
213
|
+
path: ['realtime'],
|
|
214
|
+
message: 'a model that holds realtime sessions consumes audio',
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
/**
|
|
219
|
+
* License terms, including the two questions that decide whether Oxy may serve
|
|
220
|
+
* a model to third parties at all: is commercial use permitted, and must the
|
|
221
|
+
* base model be attributed.
|
|
222
|
+
*/
|
|
223
|
+
export const modelLicenseSchema = z
|
|
224
|
+
.object({
|
|
225
|
+
/** SPDX identifier where one exists, otherwise the publisher's own name. */
|
|
226
|
+
licenseId: z.string().min(1).max(128),
|
|
227
|
+
displayName: z.string().min(1).max(200),
|
|
228
|
+
url: inferenceHttpsUrlSchema.optional(),
|
|
229
|
+
commercialUseAllowed: z.boolean(),
|
|
230
|
+
requiresAttribution: z.boolean(),
|
|
231
|
+
acceptableUsePolicyUrl: inferenceHttpsUrlSchema.optional(),
|
|
232
|
+
})
|
|
233
|
+
.strict();
|
|
234
|
+
/**
|
|
235
|
+
* How a model came to exist.
|
|
236
|
+
*
|
|
237
|
+
* `releaseKind` is what stops `alia/*` from becoming a re-badging namespace:
|
|
238
|
+
* a first-party namespace may only carry models Alia actually trained or
|
|
239
|
+
* derived, never a third-party route wearing an Oxy name.
|
|
240
|
+
*/
|
|
241
|
+
export const modelProvenanceSchema = z
|
|
242
|
+
.object({
|
|
243
|
+
releaseKind: z.enum([
|
|
244
|
+
'first_party_original',
|
|
245
|
+
'first_party_derived',
|
|
246
|
+
'open_weight',
|
|
247
|
+
'third_party_hosted',
|
|
248
|
+
]),
|
|
249
|
+
/** The model this one was derived/fine-tuned from, where there is one. */
|
|
250
|
+
baseModelId: modelIdSchema.optional(),
|
|
251
|
+
trainingOrganization: z.string().min(1).max(200).optional(),
|
|
252
|
+
})
|
|
253
|
+
.strict();
|
|
254
|
+
/**
|
|
255
|
+
* What happens to the customer's prompts and responses on a given route.
|
|
256
|
+
*
|
|
257
|
+
* Every field here is enforceable by a routing policy, which is the reason they
|
|
258
|
+
* are structured rather than a prose paragraph on a docs page.
|
|
259
|
+
*/
|
|
260
|
+
export const inferenceDataPolicySchema = z
|
|
261
|
+
.object({
|
|
262
|
+
retainsPayloads: z.boolean(),
|
|
263
|
+
/** Zero when nothing is retained. */
|
|
264
|
+
retentionDays: z.number().int().nonnegative().max(3650),
|
|
265
|
+
trainsOnCustomerData: z.boolean(),
|
|
266
|
+
zeroDataRetentionAvailable: z.boolean(),
|
|
267
|
+
/** Named subprocessors a payload may pass through, for compliance review. */
|
|
268
|
+
subprocessors: z.array(z.string().min(1).max(200)).default([]),
|
|
269
|
+
policyUrl: inferenceHttpsUrlSchema.optional(),
|
|
270
|
+
})
|
|
271
|
+
.strict()
|
|
272
|
+
.superRefine((policy, ctx) => {
|
|
273
|
+
if (!policy.retainsPayloads && policy.retentionDays > 0) {
|
|
274
|
+
ctx.addIssue({
|
|
275
|
+
code: 'custom',
|
|
276
|
+
path: ['retentionDays'],
|
|
277
|
+
message: 'a route that retains no payloads cannot have a retention window',
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
// Training requires having the data to train on. A route claiming both is
|
|
281
|
+
// reporting one of the two fields wrongly, and a routing policy that
|
|
282
|
+
// prohibits training would be enforced against a value that is not true.
|
|
283
|
+
if (!policy.retainsPayloads && policy.trainsOnCustomerData) {
|
|
284
|
+
ctx.addIssue({
|
|
285
|
+
code: 'custom',
|
|
286
|
+
path: ['trainsOnCustomerData'],
|
|
287
|
+
message: 'a route that retains no payloads cannot train on customer data',
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
/** Deprecation state, with the replacement a customer should migrate to. */
|
|
292
|
+
export const modelDeprecationSchema = z
|
|
293
|
+
.object({
|
|
294
|
+
status: z.enum(['active', 'deprecated', 'retired']),
|
|
295
|
+
replacementModelReference: modelReferenceSchema.optional(),
|
|
296
|
+
announcedAt: inferenceTimestampSchema.optional(),
|
|
297
|
+
sunsetAt: inferenceTimestampSchema.optional(),
|
|
298
|
+
})
|
|
299
|
+
.strict()
|
|
300
|
+
.superRefine((deprecation, ctx) => {
|
|
301
|
+
if (deprecation.status === 'active' && deprecation.sunsetAt !== undefined) {
|
|
302
|
+
ctx.addIssue({
|
|
303
|
+
code: 'custom',
|
|
304
|
+
path: ['sunsetAt'],
|
|
305
|
+
message: 'an active model has no sunset date; announce the deprecation first',
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
/** One published evaluation result for a revision. */
|
|
310
|
+
export const modelEvaluationResultSchema = z
|
|
311
|
+
.object({
|
|
312
|
+
suite: z.string().min(1).max(128),
|
|
313
|
+
metric: z.string().min(1).max(128),
|
|
314
|
+
/** Kept as a string: an eval score is published text, not arithmetic. */
|
|
315
|
+
score: z.string().min(1).max(64),
|
|
316
|
+
evaluatedAt: inferenceTimestampSchema.optional(),
|
|
317
|
+
reportUrl: inferenceHttpsUrlSchema.optional(),
|
|
318
|
+
})
|
|
319
|
+
.strict();
|
|
320
|
+
/**
|
|
321
|
+
* Safety metadata a downstream developer needs, and the EU AI Act / GPAI
|
|
322
|
+
* documentation trail expects to find beside a served model.
|
|
323
|
+
*/
|
|
324
|
+
export const modelSafetyMetadataSchema = z
|
|
325
|
+
.object({
|
|
326
|
+
safetyCardUrl: inferenceHttpsUrlSchema.optional(),
|
|
327
|
+
contentFilteringDefault: z.enum(['none', 'provider_default', 'strict']),
|
|
328
|
+
knownLimitations: z.array(z.string().min(1).max(500)).default([]),
|
|
329
|
+
/** Content-provenance marking the modality requires, where it requires one. */
|
|
330
|
+
provenanceMarking: z.enum(['none', 'visible_watermark', 'invisible_watermark', 'c2pa']),
|
|
331
|
+
})
|
|
332
|
+
.strict();
|
|
333
|
+
/** The publisher fields a customer sees beside a model. */
|
|
334
|
+
export const cataloguePublisherSummarySchema = z
|
|
335
|
+
.object({
|
|
336
|
+
slug: publisherSlugSchema,
|
|
337
|
+
displayName: z.string().min(1).max(200),
|
|
338
|
+
websiteUrl: inferenceHttpsUrlSchema.optional(),
|
|
339
|
+
})
|
|
340
|
+
.strict();
|
|
341
|
+
/* -------------------------------------------------------------------------- */
|
|
342
|
+
/* Audio capabilities (Kaana ADR 0002, decision 9) */
|
|
343
|
+
/* -------------------------------------------------------------------------- */
|
|
344
|
+
/** One voice a speech or realtime model can speak in. */
|
|
345
|
+
export const voiceSchema = z
|
|
346
|
+
.object({
|
|
347
|
+
/** What a speech request names in `voice`. */
|
|
348
|
+
id: z.string().min(1).max(64),
|
|
349
|
+
name: z.string().min(1).max(200),
|
|
350
|
+
/** The languages this voice speaks; each is one of the model's `languages`. */
|
|
351
|
+
languages: z.array(languageTagSchema),
|
|
352
|
+
gender: z.enum(['female', 'male', 'neutral']).optional(),
|
|
353
|
+
})
|
|
354
|
+
.strict();
|
|
355
|
+
/**
|
|
356
|
+
* What an audio model accepts, published so a request that cannot be served
|
|
357
|
+
* is refused BEFORE a grant or a reservation (`invalid_request` with `param`),
|
|
358
|
+
* never by an adapter after money was held.
|
|
359
|
+
*
|
|
360
|
+
* When several providers serve one model line, the published value is the
|
|
361
|
+
* INTERSECTION — what every one of them accepts — so a request that passes
|
|
362
|
+
* here is servable by any route of the line.
|
|
363
|
+
*
|
|
364
|
+
* - `voices` — empty for a model that reads no text aloud (transcription).
|
|
365
|
+
* - `audioFormats.input` — the media types an upload may have (transcription).
|
|
366
|
+
* - `audioFormats.output` — the media types speech can be produced in.
|
|
367
|
+
* - `speed` — absent when the model accepts no speed control; a request that
|
|
368
|
+
* names one is then refused.
|
|
369
|
+
* - `maxInputCharacters` — the longest text one speech request may carry,
|
|
370
|
+
* counted in UTF-16 code units, as JavaScript's `length` counts.
|
|
371
|
+
*
|
|
372
|
+
* A REALTIME model publishes the same shape (contract set 4.1.0): `voices` are
|
|
373
|
+
* what a session's `config.voice` may name, `languages` what it converses in
|
|
374
|
+
* and what `config.inputAudioTranscription.language` may name, and
|
|
375
|
+
* `audioFormats` is empty in both directions because a session carries no
|
|
376
|
+
* files — its encodings are `capabilities.realtime.audioFormats`. An empty
|
|
377
|
+
* `languages` means the provider documents none, not that the model speaks
|
|
378
|
+
* none; a caller that needs a language then cannot choose the model.
|
|
379
|
+
* `gender` is published only where it has been reviewed against the
|
|
380
|
+
* provider's own catalogue; absent is unknown.
|
|
381
|
+
*/
|
|
382
|
+
export const modelAudioCapabilitiesSchema = z
|
|
383
|
+
.object({
|
|
384
|
+
voices: z.array(voiceSchema),
|
|
385
|
+
languages: z.array(languageTagSchema),
|
|
386
|
+
audioFormats: z
|
|
387
|
+
.object({
|
|
388
|
+
input: z.array(audioInputMediaTypeSchema),
|
|
389
|
+
output: z.array(inferenceAudioMediaTypeSchema),
|
|
390
|
+
})
|
|
391
|
+
.strict(),
|
|
392
|
+
speed: z
|
|
393
|
+
.object({
|
|
394
|
+
min: z.number().min(0.25).max(4),
|
|
395
|
+
max: z.number().min(0.25).max(4),
|
|
396
|
+
})
|
|
397
|
+
.strict()
|
|
398
|
+
.optional(),
|
|
399
|
+
maxInputCharacters: z.number().int().positive().safe().optional(),
|
|
400
|
+
})
|
|
401
|
+
.strict()
|
|
402
|
+
.superRefine((audio, ctx) => {
|
|
403
|
+
const ids = audio.voices.map((voice) => voice.id);
|
|
404
|
+
if (new Set(ids).size !== ids.length) {
|
|
405
|
+
ctx.addIssue({ code: 'custom', path: ['voices'], message: 'each voice id is declared once' });
|
|
406
|
+
}
|
|
407
|
+
const languages = new Set(audio.languages);
|
|
408
|
+
if (languages.size !== audio.languages.length) {
|
|
409
|
+
ctx.addIssue({
|
|
410
|
+
code: 'custom',
|
|
411
|
+
path: ['languages'],
|
|
412
|
+
message: 'each language is declared once',
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
for (const [index, voice] of audio.voices.entries()) {
|
|
416
|
+
for (const [languageIndex, language] of voice.languages.entries()) {
|
|
417
|
+
if (!languages.has(language)) {
|
|
418
|
+
ctx.addIssue({
|
|
419
|
+
code: 'custom',
|
|
420
|
+
path: ['voices', index, 'languages', languageIndex],
|
|
421
|
+
message: `voice ${voice.id} speaks ${language}, which the model does not declare`,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (audio.speed !== undefined && audio.speed.min > audio.speed.max) {
|
|
427
|
+
ctx.addIssue({
|
|
428
|
+
code: 'custom',
|
|
429
|
+
path: ['speed'],
|
|
430
|
+
message: 'a speed range cannot end below where it starts',
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
/* -------------------------------------------------------------------------- */
|
|
435
|
+
/* The public catalogue (`GET /v1/models`) */
|
|
436
|
+
/* -------------------------------------------------------------------------- */
|
|
437
|
+
/**
|
|
438
|
+
* One model as `GET /v1/models` and `GET /v1/models/{publisher}/{model}`
|
|
439
|
+
* serve it: modalities, power class, limits, parameters, audio capabilities and
|
|
440
|
+
* the customer price.
|
|
441
|
+
*
|
|
442
|
+
* Derived from Oxy's `modelCatalogueEntrySchema` without what names who serves
|
|
443
|
+
* the model: no serving provider, no deployment, no availability scope and no
|
|
444
|
+
* commercial permission. Those are in the operational catalogue under
|
|
445
|
+
* `@kaana.ai/contracts/internal`.
|
|
446
|
+
*/
|
|
447
|
+
export const modelSchema = z.object({
|
|
448
|
+
/** A whole message on the wire, so it is versioned. */
|
|
449
|
+
schemaVersion: z.literal(1),
|
|
450
|
+
modelId: modelIdSchema,
|
|
451
|
+
publisher: cataloguePublisherSummarySchema,
|
|
452
|
+
displayName: z.string().min(1).max(200),
|
|
453
|
+
description: z.string().max(4000).optional(),
|
|
454
|
+
currentRevision: modelRevisionLabelSchema,
|
|
455
|
+
/** Revisions a customer may pin today, newest first. */
|
|
456
|
+
availableRevisions: z.array(modelRevisionLabelSchema).min(1),
|
|
457
|
+
capabilities: modelCapabilitiesSchema,
|
|
458
|
+
/** Present for a model that consumes or produces audio. */
|
|
459
|
+
audio: modelAudioCapabilitiesSchema.optional(),
|
|
460
|
+
license: modelLicenseSchema,
|
|
461
|
+
provenance: modelProvenanceSchema,
|
|
462
|
+
knowledgeCutoff: inferenceDateSchema.optional(),
|
|
463
|
+
releasedOn: inferenceDateSchema.optional(),
|
|
464
|
+
/** When the model was first published upstream, as reported; never an observation time. */
|
|
465
|
+
releasedAt: inferenceTimestampSchema.optional(),
|
|
466
|
+
/** Attested regions some route of this model serves from; empty means none attested. */
|
|
467
|
+
regions: z.array(inferenceRegionSchema).default([]),
|
|
468
|
+
/** The data policy every route of this model satisfies. */
|
|
469
|
+
dataPolicy: inferenceDataPolicySchema,
|
|
470
|
+
/** Oxy's list price. Absent for a model a customer cannot buy per unit (BYOK-only). */
|
|
471
|
+
pricing: priceSnapshotSchema.optional(),
|
|
472
|
+
deprecation: modelDeprecationSchema,
|
|
473
|
+
evaluations: z.array(modelEvaluationResultSchema).default([]),
|
|
474
|
+
safety: modelSafetyMetadataSchema.optional(),
|
|
475
|
+
modelCardUrl: inferenceHttpsUrlSchema.optional(),
|
|
476
|
+
/** The reviewed power class. Absent: callable by name only, chosen by no power level. */
|
|
477
|
+
powerClass: modelPowerClassSchema.optional(),
|
|
478
|
+
});
|
|
479
|
+
/** `GET /v1/models`. */
|
|
480
|
+
export const modelListSchema = z.object({ data: z.array(modelSchema) }).strict();
|
|
481
|
+
/**
|
|
482
|
+
* One power level as `GET /v1/power-levels` lists it for the calling
|
|
483
|
+
* credential: the level, the class it draws from, the effort it asks for, and
|
|
484
|
+
* the model lines it can currently choose among.
|
|
485
|
+
*
|
|
486
|
+
* `auto` draws from no single class (it chooses a level), so it carries no
|
|
487
|
+
* `powerClass` and lists every line of the levels it may climb to.
|
|
488
|
+
*/
|
|
489
|
+
export const powerLevelEntrySchema = z
|
|
490
|
+
.object({
|
|
491
|
+
level: powerLevelSchema,
|
|
492
|
+
powerClass: modelPowerClassSchema.optional(),
|
|
493
|
+
/** The effort the level asks for when the request names none. */
|
|
494
|
+
reasoningEffort: reasoningEffortSchema.optional(),
|
|
495
|
+
/** Model lines (`publisher/model`) the level can choose today, for this credential. */
|
|
496
|
+
modelLines: z.array(modelIdSchema),
|
|
497
|
+
})
|
|
498
|
+
.strict()
|
|
499
|
+
.superRefine((entry, ctx) => {
|
|
500
|
+
if ((entry.level === 'auto') !== (entry.powerClass === undefined)) {
|
|
501
|
+
ctx.addIssue({
|
|
502
|
+
code: 'custom',
|
|
503
|
+
path: ['powerClass'],
|
|
504
|
+
message: 'every level but auto draws from exactly one power class',
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
if (new Set(entry.modelLines).size !== entry.modelLines.length) {
|
|
508
|
+
ctx.addIssue({
|
|
509
|
+
code: 'custom',
|
|
510
|
+
path: ['modelLines'],
|
|
511
|
+
message: 'each model line is listed once',
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
/** `GET /v1/power-levels`. Replaces Oxy's `/v1/models/routing-profiles`. */
|
|
516
|
+
export const powerLevelListSchema = z.object({ data: z.array(powerLevelEntrySchema) }).strict();
|