@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,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Identifiers, references and wire primitives shared by every Oxy↔data-plane
|
|
4
|
+
* inference contract.
|
|
5
|
+
*
|
|
6
|
+
* Two kinds of identifier live here, and the difference matters:
|
|
7
|
+
*
|
|
8
|
+
* - **Principal identifiers** owned by Oxy (`accountId`, `applicationId`,
|
|
9
|
+
* `credentialId`, the optional delegated `userId`). The data plane may store
|
|
10
|
+
* them as immutable references; it never owns, mints or mutates them.
|
|
11
|
+
* - **Catalogue references** (`<publisher>/<model>`, `<publisher>/<model>@<revision>`,
|
|
12
|
+
* a routing-profile slug). These are the strings a customer types, so their
|
|
13
|
+
* grammar is part of the public contract, not an implementation detail.
|
|
14
|
+
*
|
|
15
|
+
* Platform-agnostic — zod only. Every regex here is plain ASCII: this package
|
|
16
|
+
* is imported by React Native apps running on Hermes, which rejects Unicode
|
|
17
|
+
* property escapes (`\p{…}`) at runtime.
|
|
18
|
+
*
|
|
19
|
+
* Decided in: docs/adr/0007-canonical-request-attribution.md, docs/adr/0008-catalogue-concept-separation.md.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.RESERVED_ALIA_PUBLISHER = exports.inferenceRegionSchema = exports.deploymentIdSchema = exports.inferenceProviderSlugSchema = exports.routingProfileSlugSchema = exports.modelReferenceSchema = exports.modelRevisionLabelSchema = exports.modelIdSchema = exports.modelSlugSchema = exports.publisherSlugSchema = exports.inferenceHttpsUrlSchema = exports.inferenceDateSchema = exports.inferenceTimestampSchema = exports.inferenceEnvironmentSchema = exports.idempotencyKeySchema = exports.generationIdSchema = exports.requestIdSchema = exports.oxyCredentialIdSchema = exports.oxyApplicationIdSchema = exports.delegatedUserIdSchema = exports.oxyAccountIdSchema = void 0;
|
|
23
|
+
const zod_1 = require("zod");
|
|
24
|
+
/* -------------------------------------------------------------------------- */
|
|
25
|
+
/* Principal identifiers */
|
|
26
|
+
/* -------------------------------------------------------------------------- */
|
|
27
|
+
/**
|
|
28
|
+
* An Oxy account id — the account that owns the workload and is financially
|
|
29
|
+
* responsible for it.
|
|
30
|
+
*
|
|
31
|
+
* Branded, and that brand is load-bearing rather than decorative: it is the
|
|
32
|
+
* type-level half of the rule that a delegated end-user identity can never
|
|
33
|
+
* become the billing identity (see {@link delegatedUserIdSchema} and
|
|
34
|
+
* `billingPrincipalSchema`). A plain `string` — and therefore any id read out
|
|
35
|
+
* of a header, a JWT claim or a request body — is not assignable to it; the
|
|
36
|
+
* only way to obtain one is to parse a value through this schema.
|
|
37
|
+
*/
|
|
38
|
+
exports.oxyAccountIdSchema = zod_1.z.string().min(1).max(64).brand();
|
|
39
|
+
/**
|
|
40
|
+
* A delegated end-user identity (Alia's `X-Oxy-User-Id`), branded with a
|
|
41
|
+
* DIFFERENT brand from {@link oxyAccountIdSchema} so the two cannot be
|
|
42
|
+
* substituted for one another in either direction, in any consumer, without a
|
|
43
|
+
* cast that review would catch.
|
|
44
|
+
*
|
|
45
|
+
* It exists for attribution and product-side personalisation only. It is never
|
|
46
|
+
* a billing principal, never an access-control principal, and its presence
|
|
47
|
+
* never changes which account is charged.
|
|
48
|
+
*/
|
|
49
|
+
exports.delegatedUserIdSchema = zod_1.z.string().min(1).max(64).brand();
|
|
50
|
+
/**
|
|
51
|
+
* An Oxy `Application._id`. Not branded: no invariant in this contract turns on
|
|
52
|
+
* confusing it with another id, and brand inflation costs every producer a
|
|
53
|
+
* parse call for no safety. The two ids above are branded because ADR 0007's
|
|
54
|
+
* rule is exactly that they must not be interchangeable.
|
|
55
|
+
*/
|
|
56
|
+
exports.oxyApplicationIdSchema = zod_1.z.string().min(1).max(64);
|
|
57
|
+
/** An Oxy `ApplicationCredential._id` — the credential used for this request. */
|
|
58
|
+
exports.oxyCredentialIdSchema = zod_1.z.string().min(1).max(64);
|
|
59
|
+
/**
|
|
60
|
+
* A request id generated by the data plane. Correlates the Oxy edge, the data
|
|
61
|
+
* plane, the financial ledger and the customer-visible receipt, so it appears on
|
|
62
|
+
* every stream event and every ledger record.
|
|
63
|
+
*/
|
|
64
|
+
exports.requestIdSchema = zod_1.z.string().min(1).max(128);
|
|
65
|
+
/**
|
|
66
|
+
* A generation id generated by the data plane, present when a request produced
|
|
67
|
+
* a generation that can be looked up later (`GET /v1/generations/:id`).
|
|
68
|
+
*/
|
|
69
|
+
exports.generationIdSchema = zod_1.z.string().min(1).max(128);
|
|
70
|
+
/**
|
|
71
|
+
* A caller-supplied idempotency key. Every reserve/settle/refund call is keyed
|
|
72
|
+
* on one so a retry, a redelivered event or a duplicated webhook can never
|
|
73
|
+
* charge twice.
|
|
74
|
+
*/
|
|
75
|
+
exports.idempotencyKeySchema = zod_1.z.string().min(1).max(255);
|
|
76
|
+
/** Credential environments. A credential is issued into exactly one of them. */
|
|
77
|
+
exports.inferenceEnvironmentSchema = zod_1.z.enum(['development', 'staging', 'production']);
|
|
78
|
+
/* -------------------------------------------------------------------------- */
|
|
79
|
+
/* Wire primitives */
|
|
80
|
+
/* -------------------------------------------------------------------------- */
|
|
81
|
+
/**
|
|
82
|
+
* An instant, as an ISO 8601 string in UTC (`2026-08-15T09:41:00.000Z`).
|
|
83
|
+
*
|
|
84
|
+
* Validated rather than left free-form — unlike the session contracts, which
|
|
85
|
+
* carry legacy expiry strings no consumer interprets, every instant here is
|
|
86
|
+
* read: a reservation expires, a price version starts applying, a receipt is
|
|
87
|
+
* settled. One canonical spelling (UTC, `Z`) so two records that describe the
|
|
88
|
+
* same moment compare and sort as equal, which a mix of offsets would not.
|
|
89
|
+
*/
|
|
90
|
+
exports.inferenceTimestampSchema = zod_1.z.string().datetime();
|
|
91
|
+
/**
|
|
92
|
+
* A calendar DATE with no instant attached (`2026-05-01`) — a knowledge cutoff
|
|
93
|
+
* or a release date, which are published as days and become wrong when a
|
|
94
|
+
* timezone is invented for them.
|
|
95
|
+
*/
|
|
96
|
+
exports.inferenceDateSchema = zod_1.z
|
|
97
|
+
.string()
|
|
98
|
+
.regex(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/, 'must be an ISO 8601 calendar date (YYYY-MM-DD)');
|
|
99
|
+
/** An absolute https URL, for model cards, licenses and provider documentation. */
|
|
100
|
+
exports.inferenceHttpsUrlSchema = zod_1.z
|
|
101
|
+
.string()
|
|
102
|
+
.max(2048)
|
|
103
|
+
.regex(/^https:\/\/[^\s]+$/, 'must be an absolute https URL');
|
|
104
|
+
/* -------------------------------------------------------------------------- */
|
|
105
|
+
/* Catalogue references */
|
|
106
|
+
/* -------------------------------------------------------------------------- */
|
|
107
|
+
/** One path segment of a canonical model id: a lowercase, URL-safe slug. */
|
|
108
|
+
const SLUG_PATTERN = '[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?';
|
|
109
|
+
/** A revision label. Case-preserving, because upstream revisions often are. */
|
|
110
|
+
const REVISION_PATTERN = '[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?';
|
|
111
|
+
/** A publisher slug, e.g. `openai`, `anthropic`, `meta`, `alia`. */
|
|
112
|
+
exports.publisherSlugSchema = zod_1.z
|
|
113
|
+
.string()
|
|
114
|
+
.max(64)
|
|
115
|
+
.regex(new RegExp(`^${SLUG_PATTERN}$`), 'publisher must be a lowercase URL-safe slug');
|
|
116
|
+
/** A model slug within its publisher's namespace, e.g. `gpt-5`, `llama-3.1-70b`. */
|
|
117
|
+
exports.modelSlugSchema = zod_1.z
|
|
118
|
+
.string()
|
|
119
|
+
.max(64)
|
|
120
|
+
.regex(new RegExp(`^${SLUG_PATTERN}$`), 'model must be a lowercase URL-safe slug');
|
|
121
|
+
/**
|
|
122
|
+
* A canonical model id, `<publisher>/<model>`. This names a MODEL — a
|
|
123
|
+
* long-lived product identity whose behaviour changes as revisions ship. It
|
|
124
|
+
* does not name a revision, a deployment or a provider.
|
|
125
|
+
*/
|
|
126
|
+
exports.modelIdSchema = zod_1.z
|
|
127
|
+
.string()
|
|
128
|
+
.max(129)
|
|
129
|
+
.regex(new RegExp(`^${SLUG_PATTERN}/${SLUG_PATTERN}$`), 'model id must be <publisher>/<model>');
|
|
130
|
+
/** An immutable revision label, unique within its model, e.g. `2026-05-01`. */
|
|
131
|
+
exports.modelRevisionLabelSchema = zod_1.z
|
|
132
|
+
.string()
|
|
133
|
+
.max(64)
|
|
134
|
+
.regex(new RegExp(`^${REVISION_PATTERN}$`), 'revision must be a URL-safe label');
|
|
135
|
+
/**
|
|
136
|
+
* A model reference as a customer writes it: `<publisher>/<model>` (the model's
|
|
137
|
+
* current revision, chosen by Oxy) or `<publisher>/<model>@<revision>` (an
|
|
138
|
+
* immutable revision the customer pinned).
|
|
139
|
+
*
|
|
140
|
+
* Both forms name a CONCRETE MODEL. Neither can name a routing profile — see
|
|
141
|
+
* {@link routingProfileSlugSchema} — which is what makes "a request for a
|
|
142
|
+
* concrete model is never silently replaced with a different model" a
|
|
143
|
+
* distinction the type system can carry rather than a convention.
|
|
144
|
+
*/
|
|
145
|
+
exports.modelReferenceSchema = zod_1.z
|
|
146
|
+
.string()
|
|
147
|
+
.max(194)
|
|
148
|
+
.regex(new RegExp(`^${SLUG_PATTERN}/${SLUG_PATTERN}(?:@${REVISION_PATTERN})?$`), 'model reference must be <publisher>/<model> or <publisher>/<model>@<revision>');
|
|
149
|
+
/**
|
|
150
|
+
* A routing-profile slug, e.g. `auto`, `fast`, `quality`.
|
|
151
|
+
*
|
|
152
|
+
* Deliberately refuses a `/`, so a profile can never be written in the shape of
|
|
153
|
+
* a model id and no caller can be confused about whether they asked for a
|
|
154
|
+
* concrete model or for Oxy to choose one. Modes like `auto`/`fast`/`quality`
|
|
155
|
+
* are profiles or product presets; they are never model objects.
|
|
156
|
+
*/
|
|
157
|
+
exports.routingProfileSlugSchema = zod_1.z
|
|
158
|
+
.string()
|
|
159
|
+
.max(64)
|
|
160
|
+
.regex(new RegExp(`^${SLUG_PATTERN}$`), 'routing profile must be a lowercase URL-safe slug');
|
|
161
|
+
/** An inference provider slug, e.g. `openai`, `bedrock`, `oxy-hosted`. */
|
|
162
|
+
exports.inferenceProviderSlugSchema = zod_1.z
|
|
163
|
+
.string()
|
|
164
|
+
.max(64)
|
|
165
|
+
.regex(new RegExp(`^${SLUG_PATTERN}$`), 'provider must be a lowercase URL-safe slug');
|
|
166
|
+
/**
|
|
167
|
+
* A deployment/endpoint id. Opaque to customers: which concrete endpoint served
|
|
168
|
+
* a request is the data plane's operational detail, and only the customer-safe
|
|
169
|
+
* subset of it is ever attributed back (see the catalogue's serving-boundary
|
|
170
|
+
* rules).
|
|
171
|
+
*/
|
|
172
|
+
exports.deploymentIdSchema = zod_1.z.string().min(1).max(128);
|
|
173
|
+
/**
|
|
174
|
+
* A region identifier, e.g. `us-west-2`, `eu-central-1`. Free-form rather than
|
|
175
|
+
* a closed enum because the set is provider-defined and grows without any
|
|
176
|
+
* contract change; residency policies match on exact strings.
|
|
177
|
+
*/
|
|
178
|
+
exports.inferenceRegionSchema = zod_1.z
|
|
179
|
+
.string()
|
|
180
|
+
.max(64)
|
|
181
|
+
.regex(new RegExp(`^${SLUG_PATTERN}$`), 'region must be a lowercase URL-safe slug');
|
|
182
|
+
/**
|
|
183
|
+
* The publisher namespace reserved for models Alia actually owns or derives.
|
|
184
|
+
*
|
|
185
|
+
* `alia/*` is never a re-badged third-party route and never a prompt preset —
|
|
186
|
+
* enforcing that is the job of `modelSchema`'s provenance refinement, which
|
|
187
|
+
* this constant exists to be checked against.
|
|
188
|
+
*/
|
|
189
|
+
exports.RESERVED_ALIA_PUBLISHER = 'alia';
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Money and usage units for the inference contracts.
|
|
4
|
+
*
|
|
5
|
+
* The non-negotiable invariant this file exists to make structural: **customer
|
|
6
|
+
* charges never use floating-point values as the financial source of truth.**
|
|
7
|
+
* A JS `number` cannot represent `0.1 + 0.2` exactly, and an inference ledger
|
|
8
|
+
* adds millions of small amounts, so a float total is wrong by construction
|
|
9
|
+
* rather than by accident.
|
|
10
|
+
*
|
|
11
|
+
* Every amount and every price is one representation: {@link exactDecimalSchema},
|
|
12
|
+
* an exact decimal STRING at the scale ADR 0009 declares. Amounts are not
|
|
13
|
+
* integer minor units, and that is the ADR's decision rather than an oversight:
|
|
14
|
+
* one token costs several orders of magnitude less than one cent, so rounding
|
|
15
|
+
* per request would make a customer's bill depend on how their client chunked
|
|
16
|
+
* its work. Rounding happens ONCE, at the invoice boundary, and is itself a
|
|
17
|
+
* ledger entry.
|
|
18
|
+
*
|
|
19
|
+
* A string is also what the driver hands back — `postgres.js` decodes `NUMERIC`
|
|
20
|
+
* as a string — so keeping it a string on the wire means accidental JS
|
|
21
|
+
* arithmetic fails loudly instead of silently losing precision. Money
|
|
22
|
+
* arithmetic happens in SQL or in a decimal type, never in a JS `number`.
|
|
23
|
+
*
|
|
24
|
+
* Units are carried separately from money in every shape: a receipt says both
|
|
25
|
+
* "204 output tokens" and "0.003060000000 USD", and neither is derived from the
|
|
26
|
+
* other at read time. That separation is what lets a price version change
|
|
27
|
+
* without rewriting settled history.
|
|
28
|
+
*
|
|
29
|
+
* Decided in: docs/adr/0009-usage-reservation-and-settlement.md.
|
|
30
|
+
*/
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.unitPriceSchema = exports.usageSourceSchema = exports.USAGE_SOURCES = exports.usageQuantitySchema = exports.usageUnitSchema = exports.USAGE_UNITS = exports.moneySchema = exports.exactDecimalSchema = exports.INFERENCE_MONEY_SCALE = exports.currencyCodeSchema = void 0;
|
|
33
|
+
const zod_1 = require("zod");
|
|
34
|
+
/* -------------------------------------------------------------------------- */
|
|
35
|
+
/* Money */
|
|
36
|
+
/* -------------------------------------------------------------------------- */
|
|
37
|
+
/** ISO 4217 alpha-3 currency code, e.g. `USD`. */
|
|
38
|
+
exports.currencyCodeSchema = zod_1.z
|
|
39
|
+
.string()
|
|
40
|
+
.regex(/^[A-Z]{3}$/, 'currency must be an ISO 4217 alpha-3 code');
|
|
41
|
+
/**
|
|
42
|
+
* The declared fractional scale of every amount and price in this contract, and
|
|
43
|
+
* of the `NUMERIC` columns the ledger stores them in.
|
|
44
|
+
*
|
|
45
|
+
* Twelve digits is sub-minor-unit precision by a wide margin: at $3 per million
|
|
46
|
+
* input tokens, one token costs `0.000003000000`, which this scale represents
|
|
47
|
+
* exactly. Amounts are compared and summed NUMERICALLY, never as text — `3.0`
|
|
48
|
+
* and `3.000000000000` are one amount written two ways.
|
|
49
|
+
*/
|
|
50
|
+
exports.INFERENCE_MONEY_SCALE = 12;
|
|
51
|
+
/**
|
|
52
|
+
* An exact non-negative decimal, carried as a STRING so no parse step can turn
|
|
53
|
+
* it into a float on the way past. Up to 18 integer digits and
|
|
54
|
+
* {@link INFERENCE_MONEY_SCALE} fractional digits.
|
|
55
|
+
*
|
|
56
|
+
* Non-negative: direction is carried by the SHAPE — a receipt debits, a refund
|
|
57
|
+
* credits — so a stray sign can never silently invert an entry.
|
|
58
|
+
*
|
|
59
|
+
* No exponent form: `1e-6` and `0.000001` are the same number, but only one of
|
|
60
|
+
* them survives a naive string comparison, a cache key or a log grep intact.
|
|
61
|
+
*
|
|
62
|
+
* Branded, so a bare `string` is not assignable and an amount cannot arrive
|
|
63
|
+
* from string concatenation that was never checked. Producers construct one
|
|
64
|
+
* with `exactDecimalSchema.parse(value)`.
|
|
65
|
+
*/
|
|
66
|
+
exports.exactDecimalSchema = zod_1.z
|
|
67
|
+
.string()
|
|
68
|
+
.regex(/^(?:0|[1-9][0-9]{0,17})(?:\.[0-9]{1,12})?$/, 'must be an exact non-negative decimal string without an exponent')
|
|
69
|
+
.brand();
|
|
70
|
+
/**
|
|
71
|
+
* An amount of money: the exact decimal plus the currency it is in.
|
|
72
|
+
*
|
|
73
|
+
* `.strict()` so a payload carrying a convenience float beside the exact value
|
|
74
|
+
* (`{ amount: '18.06', amountFloat: 18.06 }`) is REJECTED rather than stripped.
|
|
75
|
+
* A stripped float is the more dangerous outcome: it disappears silently here
|
|
76
|
+
* and survives in the producer, where it is the value somebody eventually
|
|
77
|
+
* displays.
|
|
78
|
+
*/
|
|
79
|
+
exports.moneySchema = zod_1.z
|
|
80
|
+
.object({
|
|
81
|
+
amount: exports.exactDecimalSchema,
|
|
82
|
+
currency: exports.currencyCodeSchema,
|
|
83
|
+
})
|
|
84
|
+
.strict();
|
|
85
|
+
/* -------------------------------------------------------------------------- */
|
|
86
|
+
/* Usage units */
|
|
87
|
+
/* -------------------------------------------------------------------------- */
|
|
88
|
+
/**
|
|
89
|
+
* The closed set of units inference is metered in.
|
|
90
|
+
*
|
|
91
|
+
* Time is carried in integer MILLISECONDS rather than seconds so that no unit
|
|
92
|
+
* quantity is ever fractional: a 12.5-second transcription is `12500`, exactly,
|
|
93
|
+
* and the "units are integers" rule holds for every modality instead of holding
|
|
94
|
+
* for tokens and being quietly broken by audio.
|
|
95
|
+
*/
|
|
96
|
+
exports.USAGE_UNITS = [
|
|
97
|
+
'input_tokens',
|
|
98
|
+
'cached_input_tokens',
|
|
99
|
+
'output_tokens',
|
|
100
|
+
'reasoning_tokens',
|
|
101
|
+
'requests',
|
|
102
|
+
'images',
|
|
103
|
+
'audio_input_milliseconds',
|
|
104
|
+
'audio_output_milliseconds',
|
|
105
|
+
'video_milliseconds',
|
|
106
|
+
'characters',
|
|
107
|
+
'embeddings',
|
|
108
|
+
];
|
|
109
|
+
exports.usageUnitSchema = zod_1.z.enum(exports.USAGE_UNITS);
|
|
110
|
+
/**
|
|
111
|
+
* A metered quantity of ONE unit. Never money — a quantity carries no price and
|
|
112
|
+
* no currency, so a consumer cannot mistake a token count for an amount owed.
|
|
113
|
+
*/
|
|
114
|
+
exports.usageQuantitySchema = zod_1.z
|
|
115
|
+
.object({
|
|
116
|
+
unit: exports.usageUnitSchema,
|
|
117
|
+
quantity: zod_1.z.number().int().nonnegative().safe(),
|
|
118
|
+
})
|
|
119
|
+
.strict();
|
|
120
|
+
/**
|
|
121
|
+
* Where a metered quantity came from.
|
|
122
|
+
*
|
|
123
|
+
* Kept explicit because the three are not interchangeable when a charge is
|
|
124
|
+
* disputed: `provider_reported` is the upstream's own count, `oxy_measured` is
|
|
125
|
+
* counted by the platform (streamed bytes, wall-clock milliseconds), and
|
|
126
|
+
* `estimated` is a reconstruction used when a provider returned no usage at
|
|
127
|
+
* all. An estimate that is indistinguishable from a reported number is an
|
|
128
|
+
* estimate nobody can later reconcile or refund against.
|
|
129
|
+
*/
|
|
130
|
+
exports.USAGE_SOURCES = ['provider_reported', 'oxy_measured', 'estimated'];
|
|
131
|
+
exports.usageSourceSchema = zod_1.z.enum(exports.USAGE_SOURCES);
|
|
132
|
+
/**
|
|
133
|
+
* A price for one unit, as `amount` per `per` units — `per` because a price
|
|
134
|
+
* quoted per single token would need more fractional digits than it is worth
|
|
135
|
+
* ("$3.00 per 1000000 input_tokens" is how every provider quotes it, and how
|
|
136
|
+
* every customer reads it).
|
|
137
|
+
*/
|
|
138
|
+
exports.unitPriceSchema = zod_1.z
|
|
139
|
+
.object({
|
|
140
|
+
unit: exports.usageUnitSchema,
|
|
141
|
+
amount: exports.exactDecimalSchema,
|
|
142
|
+
per: zod_1.z.number().int().positive().safe(),
|
|
143
|
+
currency: exports.currencyCodeSchema,
|
|
144
|
+
})
|
|
145
|
+
.strict();
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Price versions — the immutable snapshots customer pricing is quoted and
|
|
4
|
+
* settled against.
|
|
5
|
+
*
|
|
6
|
+
* A price is never edited in place. A change publishes a NEW version that
|
|
7
|
+
* supersedes the old one, and every settled receipt keeps the id of the version
|
|
8
|
+
* it was priced with. That is what makes an invoice reproducible a year later:
|
|
9
|
+
* the receipt does not say "3.00 per million tokens", it says "priced under
|
|
10
|
+
* `pv_2026_08`", and that version still exists, unchanged, with its own
|
|
11
|
+
* effective window.
|
|
12
|
+
*
|
|
13
|
+
* Prices are exact decimal strings (see `money.ts`), never floats, and they are
|
|
14
|
+
* quoted per unit. The amount a customer owes is computed from them and is
|
|
15
|
+
* carried in the same exact form, so no step of the calculation passes through
|
|
16
|
+
* a representation that cannot hold the value.
|
|
17
|
+
*
|
|
18
|
+
* Decided in: docs/adr/0009-usage-reservation-and-settlement.md.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.priceSnapshotSchema = exports.priceVersionSchema = exports.priceVersionStatusSchema = void 0;
|
|
22
|
+
const zod_1 = require("zod");
|
|
23
|
+
const identifiers_1 = require("./identifiers");
|
|
24
|
+
const money_1 = require("./money");
|
|
25
|
+
/**
|
|
26
|
+
* Lifecycle of a price version.
|
|
27
|
+
*
|
|
28
|
+
* `draft` is quotable in Console previews but may never price a receipt;
|
|
29
|
+
* `active` is what live requests are priced with; `superseded` priced receipts
|
|
30
|
+
* in the past and still resolves for them forever.
|
|
31
|
+
*/
|
|
32
|
+
exports.priceVersionStatusSchema = zod_1.z.enum(['draft', 'active', 'superseded']);
|
|
33
|
+
/**
|
|
34
|
+
* A published set of customer prices for one model reference on one provider.
|
|
35
|
+
*
|
|
36
|
+
* Scoped to a `(modelReference, provider)` pair rather than to a model alone
|
|
37
|
+
* because the same model costs different amounts on different providers, and a
|
|
38
|
+
* receipt has to be reproducible against the route that actually served it.
|
|
39
|
+
*/
|
|
40
|
+
exports.priceVersionSchema = zod_1.z
|
|
41
|
+
.object({
|
|
42
|
+
/** See `version.ts`: served on its own by the catalogue, so it is versioned. */
|
|
43
|
+
schemaVersion: zod_1.z.literal(1),
|
|
44
|
+
priceVersionId: zod_1.z.string().min(1).max(128),
|
|
45
|
+
status: exports.priceVersionStatusSchema,
|
|
46
|
+
modelReference: identifiers_1.modelReferenceSchema,
|
|
47
|
+
provider: identifiers_1.inferenceProviderSlugSchema,
|
|
48
|
+
currency: money_1.currencyCodeSchema,
|
|
49
|
+
unitPrices: zod_1.z.array(money_1.unitPriceSchema).min(1),
|
|
50
|
+
effectiveFrom: identifiers_1.inferenceTimestampSchema,
|
|
51
|
+
/** Absent while this version is the current one. */
|
|
52
|
+
effectiveUntil: identifiers_1.inferenceTimestampSchema.optional(),
|
|
53
|
+
/** The version this one replaced, absent for the first version of a route. */
|
|
54
|
+
supersedesPriceVersionId: zod_1.z.string().min(1).max(128).optional(),
|
|
55
|
+
createdAt: identifiers_1.inferenceTimestampSchema,
|
|
56
|
+
})
|
|
57
|
+
.superRefine((priceVersion, ctx) => {
|
|
58
|
+
const units = priceVersion.unitPrices.map((price) => price.unit);
|
|
59
|
+
if (new Set(units).size !== units.length) {
|
|
60
|
+
ctx.addIssue({
|
|
61
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
62
|
+
path: ['unitPrices'],
|
|
63
|
+
message: 'a unit may be priced only once per price version',
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
for (const [index, price] of priceVersion.unitPrices.entries()) {
|
|
67
|
+
if (price.currency !== priceVersion.currency) {
|
|
68
|
+
ctx.addIssue({
|
|
69
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
70
|
+
path: ['unitPrices', index, 'currency'],
|
|
71
|
+
message: 'every unit price must be quoted in the price version currency',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Compared as instants, not as strings: `…T00:00:00Z` and `…T00:00:00.000Z`
|
|
76
|
+
// are the same moment and sort differently as text.
|
|
77
|
+
if (priceVersion.effectiveUntil !== undefined &&
|
|
78
|
+
Date.parse(priceVersion.effectiveUntil) <= Date.parse(priceVersion.effectiveFrom)) {
|
|
79
|
+
ctx.addIssue({
|
|
80
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
81
|
+
path: ['effectiveUntil'],
|
|
82
|
+
message: 'a price version must stop applying after it started applying',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
// A superseded version priced requests during a window that has closed. Left
|
|
86
|
+
// open, it is indistinguishable from the current one when a receipt is
|
|
87
|
+
// re-priced years later — which is the one job this record exists to do.
|
|
88
|
+
if (priceVersion.status === 'superseded' && priceVersion.effectiveUntil === undefined) {
|
|
89
|
+
ctx.addIssue({
|
|
90
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
91
|
+
path: ['effectiveUntil'],
|
|
92
|
+
message: 'a superseded price version must record when it stopped applying',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
/**
|
|
97
|
+
* The price snapshot a settled receipt keeps.
|
|
98
|
+
*
|
|
99
|
+
* The unit prices are COPIED onto the receipt, not just referenced, so a receipt
|
|
100
|
+
* remains readable even if the price version record is later archived, and so
|
|
101
|
+
* that a mistake in the copy is visible as a disagreement with the version it
|
|
102
|
+
* names rather than silently invisible.
|
|
103
|
+
*/
|
|
104
|
+
exports.priceSnapshotSchema = zod_1.z
|
|
105
|
+
.object({
|
|
106
|
+
priceVersionId: zod_1.z.string().min(1).max(128),
|
|
107
|
+
currency: money_1.currencyCodeSchema,
|
|
108
|
+
unitPrices: zod_1.z.array(money_1.unitPriceSchema).min(1),
|
|
109
|
+
})
|
|
110
|
+
.strict();
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* BYOK provider connections — the metadata Oxy holds about a customer's own
|
|
4
|
+
* upstream provider credential.
|
|
5
|
+
*
|
|
6
|
+
* The credential itself is NOT here and cannot be put here. This shape carries
|
|
7
|
+
* a locator (`secretRef`) into Vault/KMS/managed secret storage, a prefix short
|
|
8
|
+
* enough to be useless, a fingerprint, and validation state. Two mechanisms
|
|
9
|
+
* make that structural rather than a convention somebody must remember:
|
|
10
|
+
*
|
|
11
|
+
* - The object is `.strict()`. A producer that attaches `apiKey`, `secret`,
|
|
12
|
+
* `token`, `privateKey` or `headers` fails the parse. Nothing is silently
|
|
13
|
+
* stripped, because a stripped field is one that still exists upstream of
|
|
14
|
+
* the parse, in a log line or an error report.
|
|
15
|
+
* - `keyPrefix` is capped at 12 characters — shorter than any provider's
|
|
16
|
+
* usable credential — so the one field designed to show part of a key cannot
|
|
17
|
+
* be widened into showing all of it without changing the contract.
|
|
18
|
+
*
|
|
19
|
+
* BYOK does not move the billing relationship: the upstream provider bills the
|
|
20
|
+
* customer's own account directly, and Oxy charges only its platform fee. The
|
|
21
|
+
* record says so explicitly so a receipt against a BYOK route can be read
|
|
22
|
+
* correctly without consulting anything else.
|
|
23
|
+
*
|
|
24
|
+
* Decided in: issue #972 workstream 10.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.providerConnectionSchema = exports.providerConnectionStatusSchema = exports.providerConnectionValidationSchema = exports.providerSecretReferenceSchema = exports.providerConnectionScopeSchema = void 0;
|
|
28
|
+
const zod_1 = require("zod");
|
|
29
|
+
const identifiers_1 = require("./identifiers");
|
|
30
|
+
/**
|
|
31
|
+
* How widely a connection applies.
|
|
32
|
+
*
|
|
33
|
+
* In the unified account graph a project IS an account, so `account` and
|
|
34
|
+
* `project` differ by INHERITANCE, not by id space: an `account` connection is
|
|
35
|
+
* inherited by every descendant project and application, a `project` one
|
|
36
|
+
* applies to that project account alone, and an `application` one to a single
|
|
37
|
+
* application. Recording which the customer chose is what makes a later
|
|
38
|
+
* "why did this app use that key" answerable.
|
|
39
|
+
*/
|
|
40
|
+
exports.providerConnectionScopeSchema = zod_1.z.discriminatedUnion('kind', [
|
|
41
|
+
zod_1.z.object({ kind: zod_1.z.literal('account'), accountId: identifiers_1.oxyAccountIdSchema }).strict(),
|
|
42
|
+
zod_1.z.object({ kind: zod_1.z.literal('project'), accountId: identifiers_1.oxyAccountIdSchema }).strict(),
|
|
43
|
+
zod_1.z
|
|
44
|
+
.object({
|
|
45
|
+
kind: zod_1.z.literal('application'),
|
|
46
|
+
accountId: identifiers_1.oxyAccountIdSchema,
|
|
47
|
+
applicationId: identifiers_1.oxyApplicationIdSchema,
|
|
48
|
+
})
|
|
49
|
+
.strict(),
|
|
50
|
+
]);
|
|
51
|
+
/**
|
|
52
|
+
* A locator for the credential in managed secret storage — never the credential.
|
|
53
|
+
*
|
|
54
|
+
* The scheme prefix is constrained to the stores Oxy actually uses, so a
|
|
55
|
+
* producer cannot pass a raw key through this field and have it look like a
|
|
56
|
+
* reference; whitespace is excluded for the same reason.
|
|
57
|
+
*/
|
|
58
|
+
exports.providerSecretReferenceSchema = zod_1.z
|
|
59
|
+
.string()
|
|
60
|
+
.max(512)
|
|
61
|
+
.regex(/^(?:vault|kms|ssm|secretsmanager):[A-Za-z0-9/_.:@-]{1,480}$/, 'a secret reference is a <store>:<locator> pointer, never credential material');
|
|
62
|
+
/** Why a credential check failed, as a closed set the Console can render. */
|
|
63
|
+
exports.providerConnectionValidationSchema = zod_1.z
|
|
64
|
+
.object({
|
|
65
|
+
state: zod_1.z.enum(['unvalidated', 'valid', 'invalid', 'expired']),
|
|
66
|
+
lastValidatedAt: identifiers_1.inferenceTimestampSchema.optional(),
|
|
67
|
+
/** Required when `invalid`: a failure nobody can act on is not a result. */
|
|
68
|
+
failureCode: zod_1.z
|
|
69
|
+
.enum(['unauthorized', 'forbidden', 'not_found', 'rate_limited', 'network', 'unknown'])
|
|
70
|
+
.optional(),
|
|
71
|
+
})
|
|
72
|
+
.strict();
|
|
73
|
+
/** Lifecycle of a connection. `revoked` is terminal; `disabled` is reversible. */
|
|
74
|
+
exports.providerConnectionStatusSchema = zod_1.z.enum([
|
|
75
|
+
'pending_validation',
|
|
76
|
+
'active',
|
|
77
|
+
'disabled',
|
|
78
|
+
'revoked',
|
|
79
|
+
]);
|
|
80
|
+
/**
|
|
81
|
+
* A customer's provider connection, without secrets.
|
|
82
|
+
*
|
|
83
|
+
* This is the whole of what Oxy stores, and the whole of what the data plane
|
|
84
|
+
* is given.
|
|
85
|
+
* Resolving `secretRef` to credential material happens in the secret store, at
|
|
86
|
+
* use time, in the data plane — never in a database row, an API response, a
|
|
87
|
+
* Console screen or a log line.
|
|
88
|
+
*/
|
|
89
|
+
exports.providerConnectionSchema = zod_1.z
|
|
90
|
+
.object({
|
|
91
|
+
/** See `version.ts`: exchanged with the data plane and rendered by Console. */
|
|
92
|
+
schemaVersion: zod_1.z.literal(1),
|
|
93
|
+
connectionId: zod_1.z.string().min(1).max(128),
|
|
94
|
+
provider: identifiers_1.inferenceProviderSlugSchema,
|
|
95
|
+
/** The Oxy account that owns the connection and answers for its use. */
|
|
96
|
+
ownerAccountId: identifiers_1.oxyAccountIdSchema,
|
|
97
|
+
scope: exports.providerConnectionScopeSchema,
|
|
98
|
+
environment: identifiers_1.inferenceEnvironmentSchema,
|
|
99
|
+
status: exports.providerConnectionStatusSchema,
|
|
100
|
+
secretRef: exports.providerSecretReferenceSchema,
|
|
101
|
+
/**
|
|
102
|
+
* The leading characters of the credential, for recognition only. Capped at
|
|
103
|
+
* 12 — long enough to tell two keys apart, far too short to be one.
|
|
104
|
+
*/
|
|
105
|
+
keyPrefix: zod_1.z.string().min(1).max(12),
|
|
106
|
+
/** SHA-256 of the credential, so rotation is verifiable without the key. */
|
|
107
|
+
fingerprint: zod_1.z
|
|
108
|
+
.string()
|
|
109
|
+
.regex(/^[a-f0-9]{64}$/, 'fingerprint must be 64 lowercase hex characters'),
|
|
110
|
+
validation: exports.providerConnectionValidationSchema,
|
|
111
|
+
/**
|
|
112
|
+
* Always `true` for a BYOK connection: the provider bills the customer's own
|
|
113
|
+
* upstream account, and Oxy charges only its platform fee. Stated as data so
|
|
114
|
+
* a receipt against this route is readable without a second lookup.
|
|
115
|
+
*/
|
|
116
|
+
upstreamBillsCustomerDirectly: zod_1.z.literal(true),
|
|
117
|
+
/** Set when the provider's terms require a per-customer acknowledgement. */
|
|
118
|
+
termsAcknowledgedAt: identifiers_1.inferenceTimestampSchema.optional(),
|
|
119
|
+
createdAt: identifiers_1.inferenceTimestampSchema,
|
|
120
|
+
rotatedAt: identifiers_1.inferenceTimestampSchema.optional(),
|
|
121
|
+
})
|
|
122
|
+
.strict()
|
|
123
|
+
.superRefine((connection, ctx) => {
|
|
124
|
+
if (connection.validation.state === 'invalid' &&
|
|
125
|
+
connection.validation.failureCode === undefined) {
|
|
126
|
+
ctx.addIssue({
|
|
127
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
128
|
+
path: ['validation', 'failureCode'],
|
|
129
|
+
message: 'an invalid credential must record why the check failed',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
// A credential the provider has rejected cannot be the one live requests are
|
|
133
|
+
// routed through: leaving it active turns every request on this route into a
|
|
134
|
+
// customer-visible upstream failure.
|
|
135
|
+
if (connection.status === 'active' && connection.validation.state === 'invalid') {
|
|
136
|
+
ctx.addIssue({
|
|
137
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
138
|
+
path: ['status'],
|
|
139
|
+
message: 'a connection whose credential failed validation cannot be active',
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|