@oxyhq/contracts 0.26.0 → 0.27.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/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/accountGraph.js +4 -3
- package/dist/cjs/index.js +143 -1
- package/dist/cjs/inference/attribution.js +101 -0
- package/dist/cjs/inference/catalogue.js +482 -0
- package/dist/cjs/inference/errors.js +195 -0
- package/dist/cjs/inference/identifiers.js +189 -0
- package/dist/cjs/inference/money.js +145 -0
- package/dist/cjs/inference/priceVersion.js +110 -0
- package/dist/cjs/inference/providerConnection.js +142 -0
- package/dist/cjs/inference/request.js +288 -0
- package/dist/cjs/inference/routingPolicy.js +213 -0
- package/dist/cjs/inference/streamEvents.js +219 -0
- package/dist/cjs/inference/usage.js +291 -0
- package/dist/cjs/inference/version.js +57 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/accountGraph.js +4 -3
- package/dist/esm/index.js +45 -0
- package/dist/esm/inference/attribution.js +98 -0
- package/dist/esm/inference/catalogue.js +479 -0
- package/dist/esm/inference/errors.js +192 -0
- package/dist/esm/inference/identifiers.js +186 -0
- package/dist/esm/inference/money.js +142 -0
- package/dist/esm/inference/priceVersion.js +107 -0
- package/dist/esm/inference/providerConnection.js +139 -0
- package/dist/esm/inference/request.js +285 -0
- package/dist/esm/inference/routingPolicy.js +210 -0
- package/dist/esm/inference/streamEvents.js +216 -0
- package/dist/esm/inference/usage.js +288 -0
- package/dist/esm/inference/version.js +54 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/accountGraph.d.ts +6 -5
- package/dist/types/index.d.ts +23 -0
- package/dist/types/inference/attribution.d.ts +171 -0
- package/dist/types/inference/catalogue.d.ts +1612 -0
- package/dist/types/inference/errors.d.ts +193 -0
- package/dist/types/inference/identifiers.d.ts +149 -0
- package/dist/types/inference/money.d.ts +142 -0
- package/dist/types/inference/priceVersion.d.ts +182 -0
- package/dist/types/inference/providerConnection.d.ts +297 -0
- package/dist/types/inference/request.d.ts +2364 -0
- package/dist/types/inference/routingPolicy.d.ts +426 -0
- package/dist/types/inference/streamEvents.d.ts +906 -0
- package/dist/types/inference/usage.d.ts +1133 -0
- package/dist/types/inference/version.d.ts +54 -0
- package/package.json +1 -1
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The canonical model catalogue.
|
|
4
|
+
*
|
|
5
|
+
* Six things are kept DISTINCT here, because collapsing any pair of them is how
|
|
6
|
+
* a catalogue starts lying to customers:
|
|
7
|
+
*
|
|
8
|
+
* 1. **Publisher** — who released the model (`openai`, `meta`, `alia`).
|
|
9
|
+
* 2. **Model** — a long-lived product identity, `<publisher>/<model>`. Its
|
|
10
|
+
* behaviour changes over time as revisions ship.
|
|
11
|
+
* 3. **Model revision** — an IMMUTABLE point in that history,
|
|
12
|
+
* `<publisher>/<model>@<revision>`. This is what an evaluation result, a
|
|
13
|
+
* model card and an artifact digest actually describe.
|
|
14
|
+
* 4. **Inference provider** — who runs the weights (a third party, Oxy's own
|
|
15
|
+
* hosting, or the customer's own account under BYOK).
|
|
16
|
+
* 5. **Deployment/endpoint** — one concrete servable route: a revision, on a
|
|
17
|
+
* provider, in a region, under a data policy, with a commercial permission.
|
|
18
|
+
* 6. **Routing profile** — a named strategy for CHOOSING among routes
|
|
19
|
+
* (`auto`, `fast`, `quality`). A profile is not a model; it has no weights,
|
|
20
|
+
* no revision and no license, and it is never written in the shape of a
|
|
21
|
+
* model id.
|
|
22
|
+
*
|
|
23
|
+
* `modelCatalogueEntrySchema` is the customer-safe projection Oxy serves from
|
|
24
|
+
* `GET /v1/models`: it repeats the customer-facing fields rather than embedding
|
|
25
|
+
* the operational descriptors, so internal deployment ids, upstream wholesale
|
|
26
|
+
* costs and route health can never reach it by accident of nesting.
|
|
27
|
+
*
|
|
28
|
+
* Decided in: docs/adr/0008-catalogue-concept-separation.md.
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.modelCatalogueEntrySchema = exports.catalogueServingProviderSummarySchema = exports.cataloguePublisherSummarySchema = exports.routingProfileSchema = exports.routingProfileCandidateSchema = exports.modelDeploymentSchema = exports.inferenceProviderSchema = exports.modelRevisionSchema = exports.catalogueModelSchema = exports.modelPublisherSchema = exports.modelSafetyMetadataSchema = exports.modelEvaluationResultSchema = exports.modelDeprecationSchema = exports.commercialPermissionSchema = exports.availabilityScopeSchema = exports.inferenceDataPolicySchema = exports.modelProvenanceSchema = exports.modelLicenseSchema = exports.modelCapabilitiesSchema = exports.inferenceModalitySchema = void 0;
|
|
32
|
+
const zod_1 = require("zod");
|
|
33
|
+
const identifiers_1 = require("./identifiers");
|
|
34
|
+
const priceVersion_1 = require("./priceVersion");
|
|
35
|
+
/* -------------------------------------------------------------------------- */
|
|
36
|
+
/* Shared catalogue vocabulary */
|
|
37
|
+
/* -------------------------------------------------------------------------- */
|
|
38
|
+
/** The modalities a model can consume or produce. */
|
|
39
|
+
exports.inferenceModalitySchema = zod_1.z.enum([
|
|
40
|
+
'text',
|
|
41
|
+
'image',
|
|
42
|
+
'audio',
|
|
43
|
+
'video',
|
|
44
|
+
'embedding',
|
|
45
|
+
]);
|
|
46
|
+
/**
|
|
47
|
+
* What a model can do, in the terms a caller has to decide against before
|
|
48
|
+
* sending a request: can it call tools, does it accept images, will it honour a
|
|
49
|
+
* JSON schema, how much context does it take, how much can it emit.
|
|
50
|
+
*/
|
|
51
|
+
exports.modelCapabilitiesSchema = zod_1.z
|
|
52
|
+
.object({
|
|
53
|
+
inputModalities: zod_1.z.array(exports.inferenceModalitySchema).min(1),
|
|
54
|
+
outputModalities: zod_1.z.array(exports.inferenceModalitySchema).min(1),
|
|
55
|
+
tools: zod_1.z.boolean(),
|
|
56
|
+
parallelToolCalls: zod_1.z.boolean(),
|
|
57
|
+
structuredOutput: zod_1.z.boolean(),
|
|
58
|
+
jsonMode: zod_1.z.boolean(),
|
|
59
|
+
reasoning: zod_1.z.boolean(),
|
|
60
|
+
streaming: zod_1.z.boolean(),
|
|
61
|
+
promptCaching: zod_1.z.boolean(),
|
|
62
|
+
maxContextTokens: zod_1.z.number().int().positive().safe(),
|
|
63
|
+
maxOutputTokens: zod_1.z.number().int().positive().safe(),
|
|
64
|
+
})
|
|
65
|
+
.strict();
|
|
66
|
+
/**
|
|
67
|
+
* License terms, including the two questions that decide whether Oxy may serve
|
|
68
|
+
* a model to third parties at all: is commercial use permitted, and must the
|
|
69
|
+
* base model be attributed.
|
|
70
|
+
*/
|
|
71
|
+
exports.modelLicenseSchema = zod_1.z
|
|
72
|
+
.object({
|
|
73
|
+
/** SPDX identifier where one exists, otherwise the publisher's own name. */
|
|
74
|
+
licenseId: zod_1.z.string().min(1).max(128),
|
|
75
|
+
displayName: zod_1.z.string().min(1).max(200),
|
|
76
|
+
url: identifiers_1.inferenceHttpsUrlSchema.optional(),
|
|
77
|
+
commercialUseAllowed: zod_1.z.boolean(),
|
|
78
|
+
requiresAttribution: zod_1.z.boolean(),
|
|
79
|
+
acceptableUsePolicyUrl: identifiers_1.inferenceHttpsUrlSchema.optional(),
|
|
80
|
+
})
|
|
81
|
+
.strict();
|
|
82
|
+
/**
|
|
83
|
+
* How a model came to exist.
|
|
84
|
+
*
|
|
85
|
+
* `releaseKind` is what stops `alia/*` from becoming a re-badging namespace:
|
|
86
|
+
* a first-party namespace may only carry models Alia actually trained or
|
|
87
|
+
* derived, never a third-party route wearing an Oxy name.
|
|
88
|
+
*/
|
|
89
|
+
exports.modelProvenanceSchema = zod_1.z
|
|
90
|
+
.object({
|
|
91
|
+
releaseKind: zod_1.z.enum([
|
|
92
|
+
'first_party_original',
|
|
93
|
+
'first_party_derived',
|
|
94
|
+
'open_weight',
|
|
95
|
+
'third_party_hosted',
|
|
96
|
+
]),
|
|
97
|
+
/** The model this one was derived/fine-tuned from, where there is one. */
|
|
98
|
+
baseModelId: identifiers_1.modelIdSchema.optional(),
|
|
99
|
+
trainingOrganization: zod_1.z.string().min(1).max(200).optional(),
|
|
100
|
+
})
|
|
101
|
+
.strict();
|
|
102
|
+
/**
|
|
103
|
+
* What happens to the customer's prompts and responses on a given route.
|
|
104
|
+
*
|
|
105
|
+
* Every field here is enforceable by a routing policy, which is the reason they
|
|
106
|
+
* are structured rather than a prose paragraph on a docs page.
|
|
107
|
+
*/
|
|
108
|
+
exports.inferenceDataPolicySchema = zod_1.z
|
|
109
|
+
.object({
|
|
110
|
+
retainsPayloads: zod_1.z.boolean(),
|
|
111
|
+
/** Zero when nothing is retained. */
|
|
112
|
+
retentionDays: zod_1.z.number().int().nonnegative().max(3650),
|
|
113
|
+
trainsOnCustomerData: zod_1.z.boolean(),
|
|
114
|
+
zeroDataRetentionAvailable: zod_1.z.boolean(),
|
|
115
|
+
/** Named subprocessors a payload may pass through, for compliance review. */
|
|
116
|
+
subprocessors: zod_1.z.array(zod_1.z.string().min(1).max(200)).default([]),
|
|
117
|
+
policyUrl: identifiers_1.inferenceHttpsUrlSchema.optional(),
|
|
118
|
+
})
|
|
119
|
+
.strict()
|
|
120
|
+
.superRefine((policy, ctx) => {
|
|
121
|
+
if (!policy.retainsPayloads && policy.retentionDays > 0) {
|
|
122
|
+
ctx.addIssue({
|
|
123
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
124
|
+
path: ['retentionDays'],
|
|
125
|
+
message: 'a route that retains no payloads cannot have a retention window',
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// Training requires having the data to train on. A route claiming both is
|
|
129
|
+
// reporting one of the two fields wrongly, and a routing policy that
|
|
130
|
+
// prohibits training would be enforced against a value that is not true.
|
|
131
|
+
if (!policy.retainsPayloads && policy.trainsOnCustomerData) {
|
|
132
|
+
ctx.addIssue({
|
|
133
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
134
|
+
path: ['trainsOnCustomerData'],
|
|
135
|
+
message: 'a route that retains no payloads cannot train on customer data',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
/**
|
|
140
|
+
* Who a route may be served to. Availability inside Alia never implies
|
|
141
|
+
* permission to resell the same provider/model publicly, which is why this is
|
|
142
|
+
* an explicit scope on the route rather than a boolean derived from "it works".
|
|
143
|
+
*/
|
|
144
|
+
exports.availabilityScopeSchema = zod_1.z.enum([
|
|
145
|
+
'internal_alia',
|
|
146
|
+
'public_payg',
|
|
147
|
+
'enterprise',
|
|
148
|
+
'byok_only',
|
|
149
|
+
'oxy_hosted',
|
|
150
|
+
]);
|
|
151
|
+
/** The commercial basis on which Oxy may serve a route. */
|
|
152
|
+
exports.commercialPermissionSchema = zod_1.z.enum([
|
|
153
|
+
'standard_application_use',
|
|
154
|
+
'public_resale_approved',
|
|
155
|
+
'wholesale_contract',
|
|
156
|
+
'customer_byok',
|
|
157
|
+
'open_weight_hosting',
|
|
158
|
+
]);
|
|
159
|
+
/** Deprecation state, with the replacement a customer should migrate to. */
|
|
160
|
+
exports.modelDeprecationSchema = zod_1.z
|
|
161
|
+
.object({
|
|
162
|
+
status: zod_1.z.enum(['active', 'deprecated', 'retired']),
|
|
163
|
+
replacementModelReference: identifiers_1.modelReferenceSchema.optional(),
|
|
164
|
+
announcedAt: identifiers_1.inferenceTimestampSchema.optional(),
|
|
165
|
+
sunsetAt: identifiers_1.inferenceTimestampSchema.optional(),
|
|
166
|
+
})
|
|
167
|
+
.strict()
|
|
168
|
+
.superRefine((deprecation, ctx) => {
|
|
169
|
+
if (deprecation.status === 'active' && deprecation.sunsetAt !== undefined) {
|
|
170
|
+
ctx.addIssue({
|
|
171
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
172
|
+
path: ['sunsetAt'],
|
|
173
|
+
message: 'an active model has no sunset date; announce the deprecation first',
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
/** One published evaluation result for a revision. */
|
|
178
|
+
exports.modelEvaluationResultSchema = zod_1.z
|
|
179
|
+
.object({
|
|
180
|
+
suite: zod_1.z.string().min(1).max(128),
|
|
181
|
+
metric: zod_1.z.string().min(1).max(128),
|
|
182
|
+
/** Kept as a string: an eval score is published text, not arithmetic. */
|
|
183
|
+
score: zod_1.z.string().min(1).max(64),
|
|
184
|
+
evaluatedAt: identifiers_1.inferenceTimestampSchema.optional(),
|
|
185
|
+
reportUrl: identifiers_1.inferenceHttpsUrlSchema.optional(),
|
|
186
|
+
})
|
|
187
|
+
.strict();
|
|
188
|
+
/**
|
|
189
|
+
* Safety metadata a downstream developer needs, and the EU AI Act / GPAI
|
|
190
|
+
* documentation trail expects to find beside a served model.
|
|
191
|
+
*/
|
|
192
|
+
exports.modelSafetyMetadataSchema = zod_1.z
|
|
193
|
+
.object({
|
|
194
|
+
safetyCardUrl: identifiers_1.inferenceHttpsUrlSchema.optional(),
|
|
195
|
+
contentFilteringDefault: zod_1.z.enum(['none', 'provider_default', 'strict']),
|
|
196
|
+
knownLimitations: zod_1.z.array(zod_1.z.string().min(1).max(500)).default([]),
|
|
197
|
+
/** Content-provenance marking the modality requires, where it requires one. */
|
|
198
|
+
provenanceMarking: zod_1.z.enum(['none', 'visible_watermark', 'invisible_watermark', 'c2pa']),
|
|
199
|
+
})
|
|
200
|
+
.strict();
|
|
201
|
+
/* -------------------------------------------------------------------------- */
|
|
202
|
+
/* 1. Publisher */
|
|
203
|
+
/* -------------------------------------------------------------------------- */
|
|
204
|
+
/** Who released a model. Never a provider, and never a routing profile. */
|
|
205
|
+
exports.modelPublisherSchema = zod_1.z.object({
|
|
206
|
+
/** See `version.ts`: served on its own by the catalogue, so it is versioned. */
|
|
207
|
+
schemaVersion: zod_1.z.literal(1),
|
|
208
|
+
publisherId: zod_1.z.string().min(1).max(128),
|
|
209
|
+
slug: identifiers_1.publisherSlugSchema,
|
|
210
|
+
displayName: zod_1.z.string().min(1).max(200),
|
|
211
|
+
description: zod_1.z.string().max(2000).optional(),
|
|
212
|
+
websiteUrl: identifiers_1.inferenceHttpsUrlSchema.optional(),
|
|
213
|
+
});
|
|
214
|
+
/* -------------------------------------------------------------------------- */
|
|
215
|
+
/* 2. Model */
|
|
216
|
+
/* -------------------------------------------------------------------------- */
|
|
217
|
+
/**
|
|
218
|
+
* A model: the long-lived product identity `<publisher>/<model>`.
|
|
219
|
+
*
|
|
220
|
+
* Carries no provider, no region, no price and no availability — those belong
|
|
221
|
+
* to a DEPLOYMENT, because the same model is served on several routes whose
|
|
222
|
+
* answers to those questions differ.
|
|
223
|
+
*/
|
|
224
|
+
exports.catalogueModelSchema = zod_1.z
|
|
225
|
+
.object({
|
|
226
|
+
/** See `version.ts`: served on its own by the catalogue, so it is versioned. */
|
|
227
|
+
schemaVersion: zod_1.z.literal(1),
|
|
228
|
+
modelId: identifiers_1.modelIdSchema,
|
|
229
|
+
publisher: identifiers_1.publisherSlugSchema,
|
|
230
|
+
slug: identifiers_1.modelSlugSchema,
|
|
231
|
+
displayName: zod_1.z.string().min(1).max(200),
|
|
232
|
+
description: zod_1.z.string().max(4000).optional(),
|
|
233
|
+
capabilities: exports.modelCapabilitiesSchema,
|
|
234
|
+
license: exports.modelLicenseSchema,
|
|
235
|
+
provenance: exports.modelProvenanceSchema,
|
|
236
|
+
knowledgeCutoff: identifiers_1.inferenceDateSchema.optional(),
|
|
237
|
+
releasedOn: identifiers_1.inferenceDateSchema.optional(),
|
|
238
|
+
/** The revision served when a caller names the model without pinning one. */
|
|
239
|
+
currentRevision: identifiers_1.modelRevisionLabelSchema,
|
|
240
|
+
deprecation: exports.modelDeprecationSchema,
|
|
241
|
+
})
|
|
242
|
+
.superRefine((model, ctx) => {
|
|
243
|
+
if (model.modelId !== `${model.publisher}/${model.slug}`) {
|
|
244
|
+
ctx.addIssue({
|
|
245
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
246
|
+
path: ['modelId'],
|
|
247
|
+
message: 'modelId must be exactly <publisher>/<model>',
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
// `alia/*` is reserved for models Alia actually owns or derived. A provider
|
|
251
|
+
// alias published under it would make an Oxy-branded model id mean nothing,
|
|
252
|
+
// and would put an Oxy name on somebody else's weights and license.
|
|
253
|
+
if (model.publisher === identifiers_1.RESERVED_ALIA_PUBLISHER &&
|
|
254
|
+
model.provenance.releaseKind !== 'first_party_original' &&
|
|
255
|
+
model.provenance.releaseKind !== 'first_party_derived') {
|
|
256
|
+
ctx.addIssue({
|
|
257
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
258
|
+
path: ['provenance', 'releaseKind'],
|
|
259
|
+
message: `the ${identifiers_1.RESERVED_ALIA_PUBLISHER}/* namespace is reserved for first-party releases`,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
/* -------------------------------------------------------------------------- */
|
|
264
|
+
/* 3. Model revision */
|
|
265
|
+
/* -------------------------------------------------------------------------- */
|
|
266
|
+
/**
|
|
267
|
+
* An immutable revision of a model.
|
|
268
|
+
*
|
|
269
|
+
* `reference` is the exact string a customer pins (`<publisher>/<model>@<revision>`)
|
|
270
|
+
* and is checked against its parts, so a record cannot claim a reference that
|
|
271
|
+
* resolves elsewhere. Evaluation and safety metadata hang here rather than on
|
|
272
|
+
* the model, because they describe specific weights.
|
|
273
|
+
*/
|
|
274
|
+
exports.modelRevisionSchema = zod_1.z
|
|
275
|
+
.object({
|
|
276
|
+
/** See `version.ts`: served on its own by the catalogue, so it is versioned. */
|
|
277
|
+
schemaVersion: zod_1.z.literal(1),
|
|
278
|
+
revisionId: zod_1.z.string().min(1).max(128),
|
|
279
|
+
modelId: identifiers_1.modelIdSchema,
|
|
280
|
+
revision: identifiers_1.modelRevisionLabelSchema,
|
|
281
|
+
reference: identifiers_1.modelReferenceSchema,
|
|
282
|
+
releasedAt: identifiers_1.inferenceTimestampSchema,
|
|
283
|
+
retiredAt: identifiers_1.inferenceTimestampSchema.optional(),
|
|
284
|
+
/** Digest of the served artifact, where Oxy hosts the weights itself. */
|
|
285
|
+
artifactDigest: zod_1.z
|
|
286
|
+
.string()
|
|
287
|
+
.regex(/^sha256:[a-f0-9]{64}$/, 'artifact digest must be sha256:<64 lowercase hex>')
|
|
288
|
+
.optional(),
|
|
289
|
+
modelCardUrl: identifiers_1.inferenceHttpsUrlSchema.optional(),
|
|
290
|
+
evaluations: zod_1.z.array(exports.modelEvaluationResultSchema).default([]),
|
|
291
|
+
safety: exports.modelSafetyMetadataSchema.optional(),
|
|
292
|
+
})
|
|
293
|
+
.superRefine((revision, ctx) => {
|
|
294
|
+
if (revision.reference !== `${revision.modelId}@${revision.revision}`) {
|
|
295
|
+
ctx.addIssue({
|
|
296
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
297
|
+
path: ['reference'],
|
|
298
|
+
message: 'reference must be exactly <modelId>@<revision>',
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
/* -------------------------------------------------------------------------- */
|
|
303
|
+
/* 4. Inference provider */
|
|
304
|
+
/* -------------------------------------------------------------------------- */
|
|
305
|
+
/** Who runs the weights for a route. */
|
|
306
|
+
exports.inferenceProviderSchema = zod_1.z.object({
|
|
307
|
+
/** See `version.ts`: served on its own by the catalogue, so it is versioned. */
|
|
308
|
+
schemaVersion: zod_1.z.literal(1),
|
|
309
|
+
providerId: zod_1.z.string().min(1).max(128),
|
|
310
|
+
slug: identifiers_1.inferenceProviderSlugSchema,
|
|
311
|
+
displayName: zod_1.z.string().min(1).max(200),
|
|
312
|
+
/**
|
|
313
|
+
* `customer_byok` means the customer's own upstream account is billed
|
|
314
|
+
* directly by the provider and Oxy charges only its platform fee.
|
|
315
|
+
*/
|
|
316
|
+
kind: zod_1.z.enum(['third_party', 'oxy_hosted', 'customer_byok']),
|
|
317
|
+
websiteUrl: identifiers_1.inferenceHttpsUrlSchema.optional(),
|
|
318
|
+
statusPageUrl: identifiers_1.inferenceHttpsUrlSchema.optional(),
|
|
319
|
+
regions: zod_1.z.array(identifiers_1.inferenceRegionSchema).default([]),
|
|
320
|
+
dataPolicy: exports.inferenceDataPolicySchema,
|
|
321
|
+
});
|
|
322
|
+
/* -------------------------------------------------------------------------- */
|
|
323
|
+
/* 5. Deployment / endpoint */
|
|
324
|
+
/* -------------------------------------------------------------------------- */
|
|
325
|
+
/**
|
|
326
|
+
* One concrete servable route: a revision, on a provider, in some regions,
|
|
327
|
+
* under a data policy, with an availability scope and a commercial permission.
|
|
328
|
+
*
|
|
329
|
+
* This is the object a routing policy filters and a route switch names — never
|
|
330
|
+
* a model. Two deployments of the SAME revision are what same-model failover
|
|
331
|
+
* moves between; moving to a deployment of a different revision or model is a
|
|
332
|
+
* cross-model fallback and needs explicit authorization.
|
|
333
|
+
*/
|
|
334
|
+
exports.modelDeploymentSchema = zod_1.z
|
|
335
|
+
.object({
|
|
336
|
+
/** See `version.ts`: exchanged with the data plane on its own. */
|
|
337
|
+
schemaVersion: zod_1.z.literal(1),
|
|
338
|
+
deploymentId: identifiers_1.deploymentIdSchema,
|
|
339
|
+
provider: identifiers_1.inferenceProviderSlugSchema,
|
|
340
|
+
/** Always revision-pinned: a deployment serves specific weights. */
|
|
341
|
+
modelReference: identifiers_1.modelReferenceSchema,
|
|
342
|
+
regions: zod_1.z.array(identifiers_1.inferenceRegionSchema).min(1),
|
|
343
|
+
dataPolicy: exports.inferenceDataPolicySchema,
|
|
344
|
+
availabilityScope: exports.availabilityScopeSchema,
|
|
345
|
+
commercialPermission: exports.commercialPermissionSchema,
|
|
346
|
+
status: zod_1.z.enum(['active', 'degraded', 'disabled', 'retired']),
|
|
347
|
+
/** Reserved capacity for one enterprise account rather than shared. */
|
|
348
|
+
dedicatedCapacity: zod_1.z.boolean(),
|
|
349
|
+
/** The price version customers are charged under on this route. */
|
|
350
|
+
priceVersionId: zod_1.z.string().min(1).max(128).optional(),
|
|
351
|
+
})
|
|
352
|
+
.superRefine((deployment, ctx) => {
|
|
353
|
+
if (!deployment.modelReference.includes('@')) {
|
|
354
|
+
ctx.addIssue({
|
|
355
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
356
|
+
path: ['modelReference'],
|
|
357
|
+
message: 'a deployment must pin an immutable revision (<publisher>/<model>@<revision>)',
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
// A technically callable route is not automatically publicly resellable.
|
|
361
|
+
// Publishing one to pay-as-you-go customers requires a permission state that
|
|
362
|
+
// says somebody reviewed the right to resell it, not merely that it answers.
|
|
363
|
+
if (deployment.availabilityScope === 'public_payg' &&
|
|
364
|
+
deployment.commercialPermission !== 'public_resale_approved' &&
|
|
365
|
+
deployment.commercialPermission !== 'wholesale_contract' &&
|
|
366
|
+
deployment.commercialPermission !== 'open_weight_hosting') {
|
|
367
|
+
ctx.addIssue({
|
|
368
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
369
|
+
path: ['commercialPermission'],
|
|
370
|
+
message: 'a public pay-as-you-go route requires an approved resale permission',
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
if (deployment.availabilityScope === 'byok_only' &&
|
|
374
|
+
deployment.commercialPermission !== 'customer_byok') {
|
|
375
|
+
ctx.addIssue({
|
|
376
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
377
|
+
path: ['commercialPermission'],
|
|
378
|
+
message: 'a BYOK-only route is served under the customer’s own provider terms',
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
// Pricing is what Oxy charges. A BYOK route bills the customer upstream and
|
|
382
|
+
// carries only a platform fee, which is not a per-unit model price.
|
|
383
|
+
if (deployment.priceVersionId !== undefined && deployment.availabilityScope === 'byok_only') {
|
|
384
|
+
ctx.addIssue({
|
|
385
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
386
|
+
path: ['priceVersionId'],
|
|
387
|
+
message: 'a BYOK-only route has no customer model price; the platform fee is separate',
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
/* -------------------------------------------------------------------------- */
|
|
392
|
+
/* 6. Routing profile */
|
|
393
|
+
/* -------------------------------------------------------------------------- */
|
|
394
|
+
/** One candidate a profile may resolve to, and how strongly it is preferred. */
|
|
395
|
+
exports.routingProfileCandidateSchema = zod_1.z
|
|
396
|
+
.object({
|
|
397
|
+
modelReference: identifiers_1.modelReferenceSchema,
|
|
398
|
+
/** Lower sorts first. Ties are broken by the profile's optimisation target. */
|
|
399
|
+
priority: zod_1.z.number().int().nonnegative().max(1000),
|
|
400
|
+
})
|
|
401
|
+
.strict();
|
|
402
|
+
/**
|
|
403
|
+
* A named strategy for choosing among routes — `auto`, `fast`, `quality`.
|
|
404
|
+
*
|
|
405
|
+
* Not a model: it has no publisher, no revision, no license and no weights, and
|
|
406
|
+
* its slug cannot be written as `<publisher>/<model>`. A request that names a
|
|
407
|
+
* profile is asking Oxy to choose; a request that names a model is not, which is
|
|
408
|
+
* the distinction the "never silently substituted" invariant rests on.
|
|
409
|
+
*/
|
|
410
|
+
exports.routingProfileSchema = zod_1.z.object({
|
|
411
|
+
/** See `version.ts`: served on its own by the catalogue, so it is versioned. */
|
|
412
|
+
schemaVersion: zod_1.z.literal(1),
|
|
413
|
+
routingProfileId: zod_1.z.string().min(1).max(128),
|
|
414
|
+
slug: identifiers_1.routingProfileSlugSchema,
|
|
415
|
+
displayName: zod_1.z.string().min(1).max(200),
|
|
416
|
+
description: zod_1.z.string().max(2000).optional(),
|
|
417
|
+
/** What the profile optimises for when several candidates qualify. */
|
|
418
|
+
optimiseFor: zod_1.z.enum(['price', 'latency', 'throughput', 'quality', 'balanced']),
|
|
419
|
+
candidates: zod_1.z.array(exports.routingProfileCandidateSchema).min(1),
|
|
420
|
+
/** A product preset (Alia's "fast" toggle) rather than a customer's own profile. */
|
|
421
|
+
isProductPreset: zod_1.z.boolean(),
|
|
422
|
+
});
|
|
423
|
+
/* -------------------------------------------------------------------------- */
|
|
424
|
+
/* Customer-safe catalogue projection */
|
|
425
|
+
/* -------------------------------------------------------------------------- */
|
|
426
|
+
/** The publisher fields a customer sees beside a model. */
|
|
427
|
+
exports.cataloguePublisherSummarySchema = zod_1.z
|
|
428
|
+
.object({
|
|
429
|
+
slug: identifiers_1.publisherSlugSchema,
|
|
430
|
+
displayName: zod_1.z.string().min(1).max(200),
|
|
431
|
+
websiteUrl: identifiers_1.inferenceHttpsUrlSchema.optional(),
|
|
432
|
+
})
|
|
433
|
+
.strict();
|
|
434
|
+
/**
|
|
435
|
+
* A serving provider as a customer may see it: who ran the request and where.
|
|
436
|
+
*
|
|
437
|
+
* Deliberately has no deployment id, no route id, no health score and no
|
|
438
|
+
* upstream cost — the customer-safe half of the serving boundary, so that
|
|
439
|
+
* exposing attribution can never leak operational topology.
|
|
440
|
+
*/
|
|
441
|
+
exports.catalogueServingProviderSummarySchema = zod_1.z
|
|
442
|
+
.object({
|
|
443
|
+
slug: identifiers_1.inferenceProviderSlugSchema,
|
|
444
|
+
displayName: zod_1.z.string().min(1).max(200),
|
|
445
|
+
regions: zod_1.z.array(identifiers_1.inferenceRegionSchema).default([]),
|
|
446
|
+
dataPolicy: exports.inferenceDataPolicySchema,
|
|
447
|
+
})
|
|
448
|
+
.strict();
|
|
449
|
+
/**
|
|
450
|
+
* One entry of the customer-facing catalogue (`GET /v1/models`).
|
|
451
|
+
*
|
|
452
|
+
* A projection, not a container: it repeats the customer-facing fields instead
|
|
453
|
+
* of embedding the operational descriptors, so no internal identifier can reach
|
|
454
|
+
* a customer by being nested one level deeper than anybody looked.
|
|
455
|
+
*/
|
|
456
|
+
exports.modelCatalogueEntrySchema = zod_1.z.object({
|
|
457
|
+
/** See `version.ts`: this is the public catalogue response shape. */
|
|
458
|
+
schemaVersion: zod_1.z.literal(1),
|
|
459
|
+
modelId: identifiers_1.modelIdSchema,
|
|
460
|
+
publisher: exports.cataloguePublisherSummarySchema,
|
|
461
|
+
displayName: zod_1.z.string().min(1).max(200),
|
|
462
|
+
description: zod_1.z.string().max(4000).optional(),
|
|
463
|
+
currentRevision: identifiers_1.modelRevisionLabelSchema,
|
|
464
|
+
/** Revisions a customer may pin today, newest first. */
|
|
465
|
+
availableRevisions: zod_1.z.array(identifiers_1.modelRevisionLabelSchema).min(1),
|
|
466
|
+
capabilities: exports.modelCapabilitiesSchema,
|
|
467
|
+
license: exports.modelLicenseSchema,
|
|
468
|
+
provenance: exports.modelProvenanceSchema,
|
|
469
|
+
knowledgeCutoff: identifiers_1.inferenceDateSchema.optional(),
|
|
470
|
+
releasedOn: identifiers_1.inferenceDateSchema.optional(),
|
|
471
|
+
regions: zod_1.z.array(identifiers_1.inferenceRegionSchema).default([]),
|
|
472
|
+
servingProviders: zod_1.z.array(exports.catalogueServingProviderSummarySchema).default([]),
|
|
473
|
+
dataPolicy: exports.inferenceDataPolicySchema,
|
|
474
|
+
/** Absent for routes a customer cannot buy per-unit (BYOK-only, internal). */
|
|
475
|
+
pricing: priceVersion_1.priceSnapshotSchema.optional(),
|
|
476
|
+
availabilityScope: exports.availabilityScopeSchema,
|
|
477
|
+
commercialPermission: exports.commercialPermissionSchema,
|
|
478
|
+
deprecation: exports.modelDeprecationSchema,
|
|
479
|
+
evaluations: zod_1.z.array(exports.modelEvaluationResultSchema).default([]),
|
|
480
|
+
safety: exports.modelSafetyMetadataSchema.optional(),
|
|
481
|
+
modelCardUrl: identifiers_1.inferenceHttpsUrlSchema.optional(),
|
|
482
|
+
});
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Inference errors and retryability.
|
|
4
|
+
*
|
|
5
|
+
* One closed set of codes, shared by the Oxy public edge, the data plane and
|
|
6
|
+
* every SDK.
|
|
7
|
+
* Closed because the alternative — a free-form `code` string — makes a client's
|
|
8
|
+
* error handling a guess about the producer's spelling, and makes "is this
|
|
9
|
+
* worth retrying" a decision every consumer re-derives from prose.
|
|
10
|
+
*
|
|
11
|
+
* Retryability is carried explicitly and is CONSTRAINED by the code: a code
|
|
12
|
+
* that can never succeed on a bare retry (an invalid request, a denied
|
|
13
|
+
* permission, an insufficient balance) cannot claim `retryable: true`. Without
|
|
14
|
+
* that constraint the field is advisory, and one producer setting it optimistically
|
|
15
|
+
* turns every client into a retry storm against a request that will never pass.
|
|
16
|
+
*
|
|
17
|
+
* The provider passthrough exists so a customer can see what the upstream said
|
|
18
|
+
* without Oxy having to interpret every provider's error vocabulary — but it is
|
|
19
|
+
* the single most likely place for an upstream credential to escape, because
|
|
20
|
+
* provider errors routinely echo the request that caused them. It is therefore
|
|
21
|
+
* a `.strict()` object of four fields with no room for headers or a request
|
|
22
|
+
* body, and its free text is refused if it looks like it contains a credential.
|
|
23
|
+
*
|
|
24
|
+
* Decided in: docs/adr/0010-public-api-compatibility.md.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.inferenceErrorSchema = exports.providerErrorPassthroughSchema = exports.upstreamErrorCategorySchema = exports.safeErrorTextSchema = exports.NON_RETRYABLE_INFERENCE_ERROR_CODES = exports.inferenceErrorCodeSchema = exports.INFERENCE_ERROR_CODES = void 0;
|
|
28
|
+
const zod_1 = require("zod");
|
|
29
|
+
const identifiers_1 = require("./identifiers");
|
|
30
|
+
/**
|
|
31
|
+
* The closed set of inference error codes.
|
|
32
|
+
*
|
|
33
|
+
* Grouped by who must act: the caller (`invalid_request` … `idempotency_conflict`),
|
|
34
|
+
* the account owner (`insufficient_balance`, `spending_limit_exceeded`,
|
|
35
|
+
* `quota_exceeded`, `byok_credential_invalid`), routing/permission policy
|
|
36
|
+
* (`policy_violation`, `commercial_permission_denied`, `no_route_available`),
|
|
37
|
+
* and the platform or its upstreams (everything from `deployment_unavailable`).
|
|
38
|
+
*/
|
|
39
|
+
exports.INFERENCE_ERROR_CODES = [
|
|
40
|
+
'invalid_request',
|
|
41
|
+
'authentication_failed',
|
|
42
|
+
'permission_denied',
|
|
43
|
+
'insufficient_scope',
|
|
44
|
+
'model_not_found',
|
|
45
|
+
'unsupported_modality',
|
|
46
|
+
'context_length_exceeded',
|
|
47
|
+
'request_too_large',
|
|
48
|
+
'output_limit_exceeded',
|
|
49
|
+
'idempotency_conflict',
|
|
50
|
+
'insufficient_balance',
|
|
51
|
+
'spending_limit_exceeded',
|
|
52
|
+
'quota_exceeded',
|
|
53
|
+
'byok_credential_invalid',
|
|
54
|
+
'policy_violation',
|
|
55
|
+
'commercial_permission_denied',
|
|
56
|
+
'no_route_available',
|
|
57
|
+
'upstream_content_filtered',
|
|
58
|
+
'cancelled',
|
|
59
|
+
'rate_limited',
|
|
60
|
+
'deployment_unavailable',
|
|
61
|
+
'provider_error',
|
|
62
|
+
'provider_timeout',
|
|
63
|
+
'provider_overloaded',
|
|
64
|
+
'service_unavailable',
|
|
65
|
+
'internal_error',
|
|
66
|
+
];
|
|
67
|
+
exports.inferenceErrorCodeSchema = zod_1.z.enum(exports.INFERENCE_ERROR_CODES);
|
|
68
|
+
/**
|
|
69
|
+
* Codes for which an identical retried request cannot succeed.
|
|
70
|
+
*
|
|
71
|
+
* `rate_limited` and `quota_exceeded` sit on opposite sides of this line
|
|
72
|
+
* deliberately: a rate limit clears on its own within the window the response
|
|
73
|
+
* names, while a quota is an account-level ceiling that only a human raises.
|
|
74
|
+
* `cancelled` is here because the caller already withdrew the request; a client
|
|
75
|
+
* that retries it is contradicting its own cancellation.
|
|
76
|
+
*/
|
|
77
|
+
exports.NON_RETRYABLE_INFERENCE_ERROR_CODES = [
|
|
78
|
+
'invalid_request',
|
|
79
|
+
'authentication_failed',
|
|
80
|
+
'permission_denied',
|
|
81
|
+
'insufficient_scope',
|
|
82
|
+
'model_not_found',
|
|
83
|
+
'unsupported_modality',
|
|
84
|
+
'context_length_exceeded',
|
|
85
|
+
'request_too_large',
|
|
86
|
+
'output_limit_exceeded',
|
|
87
|
+
'idempotency_conflict',
|
|
88
|
+
'insufficient_balance',
|
|
89
|
+
'spending_limit_exceeded',
|
|
90
|
+
'quota_exceeded',
|
|
91
|
+
'byok_credential_invalid',
|
|
92
|
+
'policy_violation',
|
|
93
|
+
'commercial_permission_denied',
|
|
94
|
+
'no_route_available',
|
|
95
|
+
'upstream_content_filtered',
|
|
96
|
+
'cancelled',
|
|
97
|
+
];
|
|
98
|
+
const NON_RETRYABLE_CODE_SET = new Set(exports.NON_RETRYABLE_INFERENCE_ERROR_CODES);
|
|
99
|
+
/**
|
|
100
|
+
* Text that looks like it carries a credential.
|
|
101
|
+
*
|
|
102
|
+
* A deliberately narrow set of literal markers — the shapes upstream providers
|
|
103
|
+
* actually echo — rather than an entropy heuristic, which would reject
|
|
104
|
+
* legitimate error text (a request id, a base64 image fragment) and teach
|
|
105
|
+
* producers to strip messages until they pass.
|
|
106
|
+
*/
|
|
107
|
+
const CREDENTIAL_LIKE_TEXT = /(?:bearer\s+[a-z0-9._~+/=-]{8,}|authorization\s*[:=]|api[_-]?key\s*[:=]|\bsk-[a-z0-9_-]{8,}|\bsk_(?:live|test)_[a-z0-9]{8,})/i;
|
|
108
|
+
/**
|
|
109
|
+
* Free text that is safe to hand a customer: bounded, and refused outright if a
|
|
110
|
+
* credential marker appears in it. Applied to BOTH the Oxy message and the
|
|
111
|
+
* upstream one — a leak is no less a leak for having been written by a provider.
|
|
112
|
+
*/
|
|
113
|
+
exports.safeErrorTextSchema = zod_1.z
|
|
114
|
+
.string()
|
|
115
|
+
.min(1)
|
|
116
|
+
.max(2000)
|
|
117
|
+
.refine((value) => !CREDENTIAL_LIKE_TEXT.test(value), 'error text must not contain credential-shaped material');
|
|
118
|
+
/**
|
|
119
|
+
* A coarse classification of an upstream failure (ADR 0010's `upstreamCategory`).
|
|
120
|
+
*
|
|
121
|
+
* Distinct from {@link providerErrorPassthroughSchema}, which carries the
|
|
122
|
+
* upstream's OWN code and text: this is Oxy's reading of what kind of failure it
|
|
123
|
+
* was, in a vocabulary that is the same across every provider, so a client can
|
|
124
|
+
* branch on it without knowing who served the request.
|
|
125
|
+
*/
|
|
126
|
+
exports.upstreamErrorCategorySchema = zod_1.z.enum([
|
|
127
|
+
'rate_limit',
|
|
128
|
+
'quota',
|
|
129
|
+
'timeout',
|
|
130
|
+
'overloaded',
|
|
131
|
+
'server_error',
|
|
132
|
+
'content_filter',
|
|
133
|
+
'invalid_request',
|
|
134
|
+
'authentication',
|
|
135
|
+
'unknown',
|
|
136
|
+
]);
|
|
137
|
+
/**
|
|
138
|
+
* What the upstream provider said, reduced to the four fields a customer can
|
|
139
|
+
* act on.
|
|
140
|
+
*
|
|
141
|
+
* `.strict()` is the security control here, not a tidiness preference: it means
|
|
142
|
+
* a producer cannot widen this by attaching `requestHeaders`, `curl`, `body` or
|
|
143
|
+
* `raw` and have it silently pass. Adding a field is a contract change with a
|
|
144
|
+
* version bump and a review, which is the point.
|
|
145
|
+
*/
|
|
146
|
+
exports.providerErrorPassthroughSchema = zod_1.z
|
|
147
|
+
.object({
|
|
148
|
+
provider: identifiers_1.inferenceProviderSlugSchema,
|
|
149
|
+
/** The upstream HTTP status, when the upstream spoke HTTP. */
|
|
150
|
+
status: zod_1.z.number().int().min(100).max(599).optional(),
|
|
151
|
+
/** The upstream's own error code, verbatim and uninterpreted. */
|
|
152
|
+
code: zod_1.z.string().max(128).optional(),
|
|
153
|
+
/** The upstream's message, subject to the same credential refusal. */
|
|
154
|
+
message: exports.safeErrorTextSchema.optional(),
|
|
155
|
+
})
|
|
156
|
+
.strict();
|
|
157
|
+
/**
|
|
158
|
+
* The error body every inference surface returns and every stream error event
|
|
159
|
+
* carries.
|
|
160
|
+
*
|
|
161
|
+
* `requestId` is always present — an error a customer cannot correlate with a
|
|
162
|
+
* log line is an error they have to reproduce to report.
|
|
163
|
+
*/
|
|
164
|
+
exports.inferenceErrorSchema = zod_1.z
|
|
165
|
+
.object({
|
|
166
|
+
/** See `version.ts`: this shape appears alone on the wire, so it is versioned. */
|
|
167
|
+
schemaVersion: zod_1.z.literal(1),
|
|
168
|
+
code: exports.inferenceErrorCodeSchema,
|
|
169
|
+
message: exports.safeErrorTextSchema,
|
|
170
|
+
retryable: zod_1.z.boolean(),
|
|
171
|
+
requestId: identifiers_1.requestIdSchema,
|
|
172
|
+
/** How long to wait before retrying. Only meaningful when `retryable`. */
|
|
173
|
+
retryAfterMs: zod_1.z.number().int().nonnegative().safe().optional(),
|
|
174
|
+
/** The request field at fault, for `invalid_request`. */
|
|
175
|
+
param: zod_1.z.string().max(128).optional(),
|
|
176
|
+
/** Present only when an upstream provider was reached and failed. */
|
|
177
|
+
upstreamCategory: exports.upstreamErrorCategorySchema.optional(),
|
|
178
|
+
providerError: exports.providerErrorPassthroughSchema.optional(),
|
|
179
|
+
})
|
|
180
|
+
.superRefine((error, ctx) => {
|
|
181
|
+
if (error.retryable && NON_RETRYABLE_CODE_SET.has(error.code)) {
|
|
182
|
+
ctx.addIssue({
|
|
183
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
184
|
+
path: ['retryable'],
|
|
185
|
+
message: `${error.code} can never succeed on an identical retry, so it cannot be retryable`,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (error.retryAfterMs !== undefined && !error.retryable) {
|
|
189
|
+
ctx.addIssue({
|
|
190
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
191
|
+
path: ['retryAfterMs'],
|
|
192
|
+
message: 'retryAfterMs tells a client when to retry, so it requires retryable: true',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
});
|