@oxyhq/contracts 0.26.0 → 0.28.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 +186 -1
- package/dist/cjs/inference/accountBilling.js +334 -0
- package/dist/cjs/inference/attribution.js +106 -0
- package/dist/cjs/inference/catalogue.js +482 -0
- package/dist/cjs/inference/entitlement.js +217 -0
- package/dist/cjs/inference/errors.js +210 -0
- package/dist/cjs/inference/identifiers.js +197 -0
- package/dist/cjs/inference/money.js +188 -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 +297 -0
- package/dist/cjs/inference/version.js +85 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/accountGraph.js +4 -3
- package/dist/esm/index.js +54 -0
- package/dist/esm/inference/accountBilling.js +331 -0
- package/dist/esm/inference/attribution.js +103 -0
- package/dist/esm/inference/catalogue.js +479 -0
- package/dist/esm/inference/entitlement.js +214 -0
- package/dist/esm/inference/errors.js +207 -0
- package/dist/esm/inference/identifiers.js +194 -0
- package/dist/esm/inference/money.js +185 -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 +294 -0
- package/dist/esm/inference/version.js +82 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/accountGraph.d.ts +6 -5
- package/dist/types/index.d.ts +27 -0
- package/dist/types/inference/accountBilling.d.ts +738 -0
- package/dist/types/inference/attribution.d.ts +176 -0
- package/dist/types/inference/catalogue.d.ts +1612 -0
- package/dist/types/inference/entitlement.d.ts +519 -0
- package/dist/types/inference/errors.d.ts +206 -0
- package/dist/types/inference/identifiers.d.ts +157 -0
- package/dist/types/inference/money.d.ts +185 -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 +1139 -0
- package/dist/types/inference/version.d.ts +82 -0
- package/package.json +1 -1
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Product entitlements — the interface a first-party product (Alia) queries to
|
|
4
|
+
* learn what an account is entitled to, WITHOUT learning anything it could
|
|
5
|
+
* mistake for a balance.
|
|
6
|
+
*
|
|
7
|
+
* ## The separation this file is
|
|
8
|
+
*
|
|
9
|
+
* #972 states the failure mode outright: confusing a product subscription with
|
|
10
|
+
* pay-as-you-go inference usage. So the two live in disjoint shapes here, and
|
|
11
|
+
* the separation is structural rather than documented:
|
|
12
|
+
*
|
|
13
|
+
* - {@link productEntitlementSchema} carries PLAN and ALLOWANCES. Allowances
|
|
14
|
+
* are whole integer counts of a product entitlement (API credits, included
|
|
15
|
+
* requests) — `z.number().int()`, never `exactDecimalSchema`, so an allowance
|
|
16
|
+
* is not even the same TYPE as money and cannot be added to one.
|
|
17
|
+
* - `accountBalanceSchema` (in `accountBilling.ts`) carries MONEY, as exact
|
|
18
|
+
* decimal strings.
|
|
19
|
+
*
|
|
20
|
+
* There is no field anywhere that is both, and no schema that sums them. A
|
|
21
|
+
* consumer wanting "what can this account do right now" reads both sections and
|
|
22
|
+
* presents both; a consumer that only wanted one is not handed the other in a
|
|
23
|
+
* form it can accidentally arithmetic.
|
|
24
|
+
*
|
|
25
|
+
* ## Allowances do not change what a request COSTS
|
|
26
|
+
*
|
|
27
|
+
* An Alia plan may include an allowance of inference. Oxy still records the
|
|
28
|
+
* exact underlying cost of every request against the account's ledger — the
|
|
29
|
+
* allowance is a PRODUCT-side entitlement that decides what Alia charges its
|
|
30
|
+
* user, not a discount applied to the receipt. That is why
|
|
31
|
+
* {@link productEntitlementSchema} names no price and no currency: a plan that
|
|
32
|
+
* could restate the cost of a request would be a second pricing authority, and
|
|
33
|
+
* a receipt has exactly one.
|
|
34
|
+
*
|
|
35
|
+
* ## Cost centres
|
|
36
|
+
*
|
|
37
|
+
* A first-party cost centre IS an Oxy project account — `users.kind` already has
|
|
38
|
+
* `project`, and an application's `ownerAccountId` already points at one. So a
|
|
39
|
+
* cost centre adds a LABEL and a slug to an account rather than a parallel
|
|
40
|
+
* hierarchy, which is the epic's "do not add a second organization model" rule
|
|
41
|
+
* applied to internal accounting.
|
|
42
|
+
*
|
|
43
|
+
* Decided in: docs/adr/0014-account-billing-and-entitlements.md.
|
|
44
|
+
*/
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.productEntitlementSchema = exports.costCenterSpendSchema = exports.costCenterSchema = exports.costCenterStatusSchema = exports.COST_CENTER_STATUSES = exports.payAsYouGoEntitlementSchema = exports.productPlanSchema = exports.planAllowanceSchema = exports.LIVE_PRODUCT_PLAN_STATUSES = exports.productPlanStatusSchema = exports.PRODUCT_PLAN_STATUSES = void 0;
|
|
47
|
+
const zod_1 = require("zod");
|
|
48
|
+
const identifiers_1 = require("./identifiers");
|
|
49
|
+
const accountBilling_1 = require("./accountBilling");
|
|
50
|
+
const money_1 = require("./money");
|
|
51
|
+
/* -------------------------------------------------------------------------- */
|
|
52
|
+
/* Plans and allowances */
|
|
53
|
+
/* -------------------------------------------------------------------------- */
|
|
54
|
+
/**
|
|
55
|
+
* The statuses a subscription may be mirrored in.
|
|
56
|
+
*
|
|
57
|
+
* All of the processor's, not just the ones this platform sells: a mirror that
|
|
58
|
+
* cannot represent what it mirrors freezes at its previous value, and a
|
|
59
|
+
* subscription the processor moved to `paused` would keep granting a plan
|
|
60
|
+
* nobody is paying for.
|
|
61
|
+
*/
|
|
62
|
+
exports.PRODUCT_PLAN_STATUSES = [
|
|
63
|
+
'active',
|
|
64
|
+
'canceled',
|
|
65
|
+
'incomplete',
|
|
66
|
+
'incomplete_expired',
|
|
67
|
+
'past_due',
|
|
68
|
+
'paused',
|
|
69
|
+
'trialing',
|
|
70
|
+
'unpaid',
|
|
71
|
+
];
|
|
72
|
+
exports.productPlanStatusSchema = zod_1.z.enum(exports.PRODUCT_PLAN_STATUSES);
|
|
73
|
+
/**
|
|
74
|
+
* The statuses that mean the plan is LIVE.
|
|
75
|
+
*
|
|
76
|
+
* Exported because "is this entitlement in force" must have one answer across
|
|
77
|
+
* Oxy and every product consuming it — a consumer deriving its own list is how
|
|
78
|
+
* `past_due` comes to be honoured in one place and refused in another.
|
|
79
|
+
*/
|
|
80
|
+
exports.LIVE_PRODUCT_PLAN_STATUSES = ['active', 'trialing'];
|
|
81
|
+
/**
|
|
82
|
+
* One allowance included in a plan.
|
|
83
|
+
*
|
|
84
|
+
* `remaining` is optional and absent means "not metered against this allowance
|
|
85
|
+
* here" — NOT zero. A consumer that read an absent allowance as exhausted would
|
|
86
|
+
* refuse a user who has spent nothing.
|
|
87
|
+
*/
|
|
88
|
+
exports.planAllowanceSchema = zod_1.z
|
|
89
|
+
.object({
|
|
90
|
+
/** A stable machine name, e.g. `api_credits_per_month`. */
|
|
91
|
+
key: zod_1.z.string().regex(/^[a-z][a-z0-9_]{0,62}$/),
|
|
92
|
+
/** Whole units included per period. Never money. */
|
|
93
|
+
included: zod_1.z.number().int().nonnegative().safe(),
|
|
94
|
+
remaining: zod_1.z.number().int().nonnegative().safe().optional(),
|
|
95
|
+
})
|
|
96
|
+
.strict();
|
|
97
|
+
/**
|
|
98
|
+
* The plan an account is on, if any.
|
|
99
|
+
*
|
|
100
|
+
* `price` is deliberately absent. What a customer pays for their plan is the
|
|
101
|
+
* processor's record and this platform's `billing_transactions`; restating it
|
|
102
|
+
* here would put a second price authority in the one interface whose whole job
|
|
103
|
+
* is to keep product pricing and inference cost apart.
|
|
104
|
+
*/
|
|
105
|
+
exports.productPlanSchema = zod_1.z
|
|
106
|
+
.object({
|
|
107
|
+
id: zod_1.z.string().min(1).max(64),
|
|
108
|
+
name: zod_1.z.string().min(1).max(120),
|
|
109
|
+
status: exports.productPlanStatusSchema,
|
|
110
|
+
live: zod_1.z.boolean(),
|
|
111
|
+
currentPeriodStart: zod_1.z.string().datetime(),
|
|
112
|
+
currentPeriodEnd: zod_1.z.string().datetime(),
|
|
113
|
+
cancelAtPeriodEnd: zod_1.z.boolean(),
|
|
114
|
+
allowances: zod_1.z.array(exports.planAllowanceSchema),
|
|
115
|
+
})
|
|
116
|
+
.strict();
|
|
117
|
+
/* -------------------------------------------------------------------------- */
|
|
118
|
+
/* Pay-as-you-go position */
|
|
119
|
+
/* -------------------------------------------------------------------------- */
|
|
120
|
+
/**
|
|
121
|
+
* The account's inference-spend position, summarised for a product consumer.
|
|
122
|
+
*
|
|
123
|
+
* A REDUCTION of `accountBillingStateSchema`, not a copy: a product asking "may
|
|
124
|
+
* this account run another request" needs to know whether spending is possible
|
|
125
|
+
* and roughly how much room is left, and does not need the bucket breakdown.
|
|
126
|
+
* `promotionalBalance` and `purchasedBalance` are still separate — the rule that
|
|
127
|
+
* a grant and a purchase are never one number does not relax because the
|
|
128
|
+
* consumer is first-party.
|
|
129
|
+
*/
|
|
130
|
+
exports.payAsYouGoEntitlementSchema = zod_1.z
|
|
131
|
+
.object({
|
|
132
|
+
/** The account that actually pays — the nearest ancestor with a profile. */
|
|
133
|
+
billingAccountId: identifiers_1.oxyAccountIdSchema,
|
|
134
|
+
currency: money_1.currencyCodeSchema,
|
|
135
|
+
billingMode: accountBilling_1.billingModeSchema,
|
|
136
|
+
purchasedBalance: money_1.exactDecimalSchema,
|
|
137
|
+
promotionalBalance: money_1.exactDecimalSchema,
|
|
138
|
+
availableToSpend: money_1.exactDecimalSchema,
|
|
139
|
+
/** False when the profile is suspended, closed, or out of room. */
|
|
140
|
+
canSpend: zod_1.z.boolean(),
|
|
141
|
+
})
|
|
142
|
+
.strict();
|
|
143
|
+
/* -------------------------------------------------------------------------- */
|
|
144
|
+
/* Cost centres */
|
|
145
|
+
/* -------------------------------------------------------------------------- */
|
|
146
|
+
exports.COST_CENTER_STATUSES = ['active', 'retired'];
|
|
147
|
+
exports.costCenterStatusSchema = zod_1.z.enum(exports.COST_CENTER_STATUSES);
|
|
148
|
+
/**
|
|
149
|
+
* An internal cost centre — an Oxy account that first-party spend is attributed
|
|
150
|
+
* to, with a stable slug so a report can name it without an id.
|
|
151
|
+
*
|
|
152
|
+
* The account IS the cost centre; this shape only labels it. There is no
|
|
153
|
+
* `parentId` here for the same reason: the account graph already has one, and a
|
|
154
|
+
* second parent link would be a second hierarchy that can disagree with it.
|
|
155
|
+
*/
|
|
156
|
+
exports.costCenterSchema = zod_1.z
|
|
157
|
+
.object({
|
|
158
|
+
/** See `version.ts`: this shape is served to Console and to Alia. */
|
|
159
|
+
schemaVersion: zod_1.z.literal(1),
|
|
160
|
+
accountId: identifiers_1.oxyAccountIdSchema,
|
|
161
|
+
slug: zod_1.z.string().regex(/^[a-z0-9][a-z0-9-]{0,62}$/),
|
|
162
|
+
label: zod_1.z.string().min(1).max(120),
|
|
163
|
+
status: exports.costCenterStatusSchema,
|
|
164
|
+
createdAt: zod_1.z.string().datetime(),
|
|
165
|
+
updatedAt: zod_1.z.string().datetime(),
|
|
166
|
+
})
|
|
167
|
+
.strict();
|
|
168
|
+
/**
|
|
169
|
+
* What one cost centre spent over a window.
|
|
170
|
+
*
|
|
171
|
+
* `billedAmount` comes from settled receipts — the FINANCIAL ledger — never from
|
|
172
|
+
* telemetry sums, per #972 workstream 8. `requestCount` is a count of receipts,
|
|
173
|
+
* so the two are always about the same set of rows.
|
|
174
|
+
*/
|
|
175
|
+
exports.costCenterSpendSchema = zod_1.z
|
|
176
|
+
.object({
|
|
177
|
+
/** See `version.ts`: this shape is served to Console and to Alia. */
|
|
178
|
+
schemaVersion: zod_1.z.literal(1),
|
|
179
|
+
costCenter: exports.costCenterSchema,
|
|
180
|
+
currency: money_1.currencyCodeSchema,
|
|
181
|
+
periodStart: zod_1.z.string().datetime(),
|
|
182
|
+
periodEnd: zod_1.z.string().datetime(),
|
|
183
|
+
billedAmount: money_1.exactDecimalSchema,
|
|
184
|
+
requestCount: zod_1.z.number().int().nonnegative().safe(),
|
|
185
|
+
})
|
|
186
|
+
.strict();
|
|
187
|
+
/* -------------------------------------------------------------------------- */
|
|
188
|
+
/* The interface Alia queries */
|
|
189
|
+
/* -------------------------------------------------------------------------- */
|
|
190
|
+
/**
|
|
191
|
+
* Everything a product needs to decide what an account may do, in one read.
|
|
192
|
+
*
|
|
193
|
+
* The three sections never merge:
|
|
194
|
+
*
|
|
195
|
+
* - `plan` + `allowances` — the product subscription.
|
|
196
|
+
* - `payAsYouGo` — inference money. `null` when the account has no billing
|
|
197
|
+
* profile anywhere up its ancestry, which is a REAL and distinct state from a
|
|
198
|
+
* zero balance: nobody has decided who pays for this account yet.
|
|
199
|
+
* - `costCenter` — where first-party spend is booked, `null` for a customer.
|
|
200
|
+
*/
|
|
201
|
+
exports.productEntitlementSchema = zod_1.z
|
|
202
|
+
.object({
|
|
203
|
+
/** See `version.ts`: this shape is served to Console and to Alia. */
|
|
204
|
+
schemaVersion: zod_1.z.literal(1),
|
|
205
|
+
accountId: identifiers_1.oxyAccountIdSchema,
|
|
206
|
+
plan: exports.productPlanSchema.nullable(),
|
|
207
|
+
/**
|
|
208
|
+
* Allowances in force right now, whether they came from a plan or from the
|
|
209
|
+
* platform's own free tier. Whole counts; never money.
|
|
210
|
+
*/
|
|
211
|
+
allowances: zod_1.z.array(exports.planAllowanceSchema),
|
|
212
|
+
payAsYouGo: exports.payAsYouGoEntitlementSchema.nullable(),
|
|
213
|
+
costCenter: exports.costCenterSchema.nullable(),
|
|
214
|
+
/** When this view was computed. Entitlements are eventually consistent. */
|
|
215
|
+
resolvedAt: zod_1.z.string().datetime(),
|
|
216
|
+
})
|
|
217
|
+
.strict();
|
|
@@ -0,0 +1,210 @@
|
|
|
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
|
+
* The platform group is NOT uniformly retryable, and that is the point of
|
|
40
|
+
* `provider_credential_invalid` sitting in it: an upstream that refuses the
|
|
41
|
+
* PLATFORM's own credential fails every identical retry until an operator
|
|
42
|
+
* rotates a key, so classifying it as `provider_error` would send every client
|
|
43
|
+
* into a retry loop against a request that cannot succeed.
|
|
44
|
+
*/
|
|
45
|
+
exports.INFERENCE_ERROR_CODES = [
|
|
46
|
+
'invalid_request',
|
|
47
|
+
'authentication_failed',
|
|
48
|
+
'permission_denied',
|
|
49
|
+
'insufficient_scope',
|
|
50
|
+
'model_not_found',
|
|
51
|
+
'unsupported_modality',
|
|
52
|
+
'context_length_exceeded',
|
|
53
|
+
'request_too_large',
|
|
54
|
+
'output_limit_exceeded',
|
|
55
|
+
'idempotency_conflict',
|
|
56
|
+
'insufficient_balance',
|
|
57
|
+
'spending_limit_exceeded',
|
|
58
|
+
'quota_exceeded',
|
|
59
|
+
'byok_credential_invalid',
|
|
60
|
+
'policy_violation',
|
|
61
|
+
'commercial_permission_denied',
|
|
62
|
+
'no_route_available',
|
|
63
|
+
'upstream_content_filtered',
|
|
64
|
+
'cancelled',
|
|
65
|
+
'rate_limited',
|
|
66
|
+
'deployment_unavailable',
|
|
67
|
+
'provider_error',
|
|
68
|
+
'provider_timeout',
|
|
69
|
+
'provider_overloaded',
|
|
70
|
+
'provider_credential_invalid',
|
|
71
|
+
'service_unavailable',
|
|
72
|
+
'internal_error',
|
|
73
|
+
];
|
|
74
|
+
exports.inferenceErrorCodeSchema = zod_1.z.enum(exports.INFERENCE_ERROR_CODES);
|
|
75
|
+
/**
|
|
76
|
+
* Codes for which an identical retried request cannot succeed.
|
|
77
|
+
*
|
|
78
|
+
* `rate_limited` and `quota_exceeded` sit on opposite sides of this line
|
|
79
|
+
* deliberately: a rate limit clears on its own within the window the response
|
|
80
|
+
* names, while a quota is an account-level ceiling that only a human raises.
|
|
81
|
+
* `cancelled` is here because the caller already withdrew the request; a client
|
|
82
|
+
* that retries it is contradicting its own cancellation.
|
|
83
|
+
*
|
|
84
|
+
* `byok_credential_invalid` and `provider_credential_invalid` are the same
|
|
85
|
+
* failure seen from the two sides of the BYOK boundary — the customer's own
|
|
86
|
+
* upstream credential and the platform's — and they are two codes rather than
|
|
87
|
+
* one because only the first names an action the customer can take. Both are
|
|
88
|
+
* non-retryable for the same reason: a credential an upstream has refused keeps
|
|
89
|
+
* being refused until somebody replaces it.
|
|
90
|
+
*/
|
|
91
|
+
exports.NON_RETRYABLE_INFERENCE_ERROR_CODES = [
|
|
92
|
+
'invalid_request',
|
|
93
|
+
'authentication_failed',
|
|
94
|
+
'permission_denied',
|
|
95
|
+
'insufficient_scope',
|
|
96
|
+
'model_not_found',
|
|
97
|
+
'unsupported_modality',
|
|
98
|
+
'context_length_exceeded',
|
|
99
|
+
'request_too_large',
|
|
100
|
+
'output_limit_exceeded',
|
|
101
|
+
'idempotency_conflict',
|
|
102
|
+
'insufficient_balance',
|
|
103
|
+
'spending_limit_exceeded',
|
|
104
|
+
'quota_exceeded',
|
|
105
|
+
'byok_credential_invalid',
|
|
106
|
+
'policy_violation',
|
|
107
|
+
'commercial_permission_denied',
|
|
108
|
+
'no_route_available',
|
|
109
|
+
'upstream_content_filtered',
|
|
110
|
+
'cancelled',
|
|
111
|
+
'provider_credential_invalid',
|
|
112
|
+
];
|
|
113
|
+
const NON_RETRYABLE_CODE_SET = new Set(exports.NON_RETRYABLE_INFERENCE_ERROR_CODES);
|
|
114
|
+
/**
|
|
115
|
+
* Text that looks like it carries a credential.
|
|
116
|
+
*
|
|
117
|
+
* A deliberately narrow set of literal markers — the shapes upstream providers
|
|
118
|
+
* actually echo — rather than an entropy heuristic, which would reject
|
|
119
|
+
* legitimate error text (a request id, a base64 image fragment) and teach
|
|
120
|
+
* producers to strip messages until they pass.
|
|
121
|
+
*/
|
|
122
|
+
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;
|
|
123
|
+
/**
|
|
124
|
+
* Free text that is safe to hand a customer: bounded, and refused outright if a
|
|
125
|
+
* credential marker appears in it. Applied to BOTH the Oxy message and the
|
|
126
|
+
* upstream one — a leak is no less a leak for having been written by a provider.
|
|
127
|
+
*/
|
|
128
|
+
exports.safeErrorTextSchema = zod_1.z
|
|
129
|
+
.string()
|
|
130
|
+
.min(1)
|
|
131
|
+
.max(2000)
|
|
132
|
+
.refine((value) => !CREDENTIAL_LIKE_TEXT.test(value), 'error text must not contain credential-shaped material');
|
|
133
|
+
/**
|
|
134
|
+
* A coarse classification of an upstream failure (ADR 0010's `upstreamCategory`).
|
|
135
|
+
*
|
|
136
|
+
* Distinct from {@link providerErrorPassthroughSchema}, which carries the
|
|
137
|
+
* upstream's OWN code and text: this is Oxy's reading of what kind of failure it
|
|
138
|
+
* was, in a vocabulary that is the same across every provider, so a client can
|
|
139
|
+
* branch on it without knowing who served the request.
|
|
140
|
+
*/
|
|
141
|
+
exports.upstreamErrorCategorySchema = zod_1.z.enum([
|
|
142
|
+
'rate_limit',
|
|
143
|
+
'quota',
|
|
144
|
+
'timeout',
|
|
145
|
+
'overloaded',
|
|
146
|
+
'server_error',
|
|
147
|
+
'content_filter',
|
|
148
|
+
'invalid_request',
|
|
149
|
+
'authentication',
|
|
150
|
+
'unknown',
|
|
151
|
+
]);
|
|
152
|
+
/**
|
|
153
|
+
* What the upstream provider said, reduced to the four fields a customer can
|
|
154
|
+
* act on.
|
|
155
|
+
*
|
|
156
|
+
* `.strict()` is the security control here, not a tidiness preference: it means
|
|
157
|
+
* a producer cannot widen this by attaching `requestHeaders`, `curl`, `body` or
|
|
158
|
+
* `raw` and have it silently pass. Adding a field is a contract change with a
|
|
159
|
+
* version bump and a review, which is the point.
|
|
160
|
+
*/
|
|
161
|
+
exports.providerErrorPassthroughSchema = zod_1.z
|
|
162
|
+
.object({
|
|
163
|
+
provider: identifiers_1.inferenceProviderSlugSchema,
|
|
164
|
+
/** The upstream HTTP status, when the upstream spoke HTTP. */
|
|
165
|
+
status: zod_1.z.number().int().min(100).max(599).optional(),
|
|
166
|
+
/** The upstream's own error code, verbatim and uninterpreted. */
|
|
167
|
+
code: zod_1.z.string().max(128).optional(),
|
|
168
|
+
/** The upstream's message, subject to the same credential refusal. */
|
|
169
|
+
message: exports.safeErrorTextSchema.optional(),
|
|
170
|
+
})
|
|
171
|
+
.strict();
|
|
172
|
+
/**
|
|
173
|
+
* The error body every inference surface returns and every stream error event
|
|
174
|
+
* carries.
|
|
175
|
+
*
|
|
176
|
+
* `requestId` is always present — an error a customer cannot correlate with a
|
|
177
|
+
* log line is an error they have to reproduce to report.
|
|
178
|
+
*/
|
|
179
|
+
exports.inferenceErrorSchema = zod_1.z
|
|
180
|
+
.object({
|
|
181
|
+
/** See `version.ts`: this shape appears alone on the wire, so it is versioned. */
|
|
182
|
+
schemaVersion: zod_1.z.literal(1),
|
|
183
|
+
code: exports.inferenceErrorCodeSchema,
|
|
184
|
+
message: exports.safeErrorTextSchema,
|
|
185
|
+
retryable: zod_1.z.boolean(),
|
|
186
|
+
requestId: identifiers_1.requestIdSchema,
|
|
187
|
+
/** How long to wait before retrying. Only meaningful when `retryable`. */
|
|
188
|
+
retryAfterMs: zod_1.z.number().int().nonnegative().safe().optional(),
|
|
189
|
+
/** The request field at fault, for `invalid_request`. */
|
|
190
|
+
param: zod_1.z.string().max(128).optional(),
|
|
191
|
+
/** Present only when an upstream provider was reached and failed. */
|
|
192
|
+
upstreamCategory: exports.upstreamErrorCategorySchema.optional(),
|
|
193
|
+
providerError: exports.providerErrorPassthroughSchema.optional(),
|
|
194
|
+
})
|
|
195
|
+
.superRefine((error, ctx) => {
|
|
196
|
+
if (error.retryable && NON_RETRYABLE_CODE_SET.has(error.code)) {
|
|
197
|
+
ctx.addIssue({
|
|
198
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
199
|
+
path: ['retryable'],
|
|
200
|
+
message: `${error.code} can never succeed on an identical retry, so it cannot be retryable`,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if (error.retryAfterMs !== undefined && !error.retryable) {
|
|
204
|
+
ctx.addIssue({
|
|
205
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
206
|
+
path: ['retryAfterMs'],
|
|
207
|
+
message: 'retryAfterMs tells a client when to retry, so it requires retryable: true',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
});
|
|
@@ -0,0 +1,197 @@
|
|
|
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 allocated by the Oxy EDGE, at admission and BEFORE
|
|
61
|
+
* authentication, so that a request rejected for a bad credential is as
|
|
62
|
+
* traceable as one that was served (ADR 0007, and step 1 of ADR 0010's edge
|
|
63
|
+
* order). It is required on the inbound envelope, which is what makes the data
|
|
64
|
+
* plane a consumer of this id rather than its source: the data plane echoes it
|
|
65
|
+
* on every stream event, on the usage report and on anything it can be asked
|
|
66
|
+
* about later.
|
|
67
|
+
*
|
|
68
|
+
* Correlates the Oxy edge, the data plane, the financial ledger and the
|
|
69
|
+
* customer-visible receipt, so it appears on every stream event and every
|
|
70
|
+
* ledger record.
|
|
71
|
+
*/
|
|
72
|
+
exports.requestIdSchema = zod_1.z.string().min(1).max(128);
|
|
73
|
+
/**
|
|
74
|
+
* A generation id generated by the data plane, present when a request produced
|
|
75
|
+
* a generation that can be looked up later (`GET /v1/generations/:id`).
|
|
76
|
+
*/
|
|
77
|
+
exports.generationIdSchema = zod_1.z.string().min(1).max(128);
|
|
78
|
+
/**
|
|
79
|
+
* A caller-supplied idempotency key. Every reserve/settle/refund call is keyed
|
|
80
|
+
* on one so a retry, a redelivered event or a duplicated webhook can never
|
|
81
|
+
* charge twice.
|
|
82
|
+
*/
|
|
83
|
+
exports.idempotencyKeySchema = zod_1.z.string().min(1).max(255);
|
|
84
|
+
/** Credential environments. A credential is issued into exactly one of them. */
|
|
85
|
+
exports.inferenceEnvironmentSchema = zod_1.z.enum(['development', 'staging', 'production']);
|
|
86
|
+
/* -------------------------------------------------------------------------- */
|
|
87
|
+
/* Wire primitives */
|
|
88
|
+
/* -------------------------------------------------------------------------- */
|
|
89
|
+
/**
|
|
90
|
+
* An instant, as an ISO 8601 string in UTC (`2026-08-15T09:41:00.000Z`).
|
|
91
|
+
*
|
|
92
|
+
* Validated rather than left free-form — unlike the session contracts, which
|
|
93
|
+
* carry legacy expiry strings no consumer interprets, every instant here is
|
|
94
|
+
* read: a reservation expires, a price version starts applying, a receipt is
|
|
95
|
+
* settled. One canonical spelling (UTC, `Z`) so two records that describe the
|
|
96
|
+
* same moment compare and sort as equal, which a mix of offsets would not.
|
|
97
|
+
*/
|
|
98
|
+
exports.inferenceTimestampSchema = zod_1.z.string().datetime();
|
|
99
|
+
/**
|
|
100
|
+
* A calendar DATE with no instant attached (`2026-05-01`) — a knowledge cutoff
|
|
101
|
+
* or a release date, which are published as days and become wrong when a
|
|
102
|
+
* timezone is invented for them.
|
|
103
|
+
*/
|
|
104
|
+
exports.inferenceDateSchema = zod_1.z
|
|
105
|
+
.string()
|
|
106
|
+
.regex(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/, 'must be an ISO 8601 calendar date (YYYY-MM-DD)');
|
|
107
|
+
/** An absolute https URL, for model cards, licenses and provider documentation. */
|
|
108
|
+
exports.inferenceHttpsUrlSchema = zod_1.z
|
|
109
|
+
.string()
|
|
110
|
+
.max(2048)
|
|
111
|
+
.regex(/^https:\/\/[^\s]+$/, 'must be an absolute https URL');
|
|
112
|
+
/* -------------------------------------------------------------------------- */
|
|
113
|
+
/* Catalogue references */
|
|
114
|
+
/* -------------------------------------------------------------------------- */
|
|
115
|
+
/** One path segment of a canonical model id: a lowercase, URL-safe slug. */
|
|
116
|
+
const SLUG_PATTERN = '[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?';
|
|
117
|
+
/** A revision label. Case-preserving, because upstream revisions often are. */
|
|
118
|
+
const REVISION_PATTERN = '[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?';
|
|
119
|
+
/** A publisher slug, e.g. `openai`, `anthropic`, `meta`, `alia`. */
|
|
120
|
+
exports.publisherSlugSchema = zod_1.z
|
|
121
|
+
.string()
|
|
122
|
+
.max(64)
|
|
123
|
+
.regex(new RegExp(`^${SLUG_PATTERN}$`), 'publisher must be a lowercase URL-safe slug');
|
|
124
|
+
/** A model slug within its publisher's namespace, e.g. `gpt-5`, `llama-3.1-70b`. */
|
|
125
|
+
exports.modelSlugSchema = zod_1.z
|
|
126
|
+
.string()
|
|
127
|
+
.max(64)
|
|
128
|
+
.regex(new RegExp(`^${SLUG_PATTERN}$`), 'model must be a lowercase URL-safe slug');
|
|
129
|
+
/**
|
|
130
|
+
* A canonical model id, `<publisher>/<model>`. This names a MODEL — a
|
|
131
|
+
* long-lived product identity whose behaviour changes as revisions ship. It
|
|
132
|
+
* does not name a revision, a deployment or a provider.
|
|
133
|
+
*/
|
|
134
|
+
exports.modelIdSchema = zod_1.z
|
|
135
|
+
.string()
|
|
136
|
+
.max(129)
|
|
137
|
+
.regex(new RegExp(`^${SLUG_PATTERN}/${SLUG_PATTERN}$`), 'model id must be <publisher>/<model>');
|
|
138
|
+
/** An immutable revision label, unique within its model, e.g. `2026-05-01`. */
|
|
139
|
+
exports.modelRevisionLabelSchema = zod_1.z
|
|
140
|
+
.string()
|
|
141
|
+
.max(64)
|
|
142
|
+
.regex(new RegExp(`^${REVISION_PATTERN}$`), 'revision must be a URL-safe label');
|
|
143
|
+
/**
|
|
144
|
+
* A model reference as a customer writes it: `<publisher>/<model>` (the model's
|
|
145
|
+
* current revision, chosen by Oxy) or `<publisher>/<model>@<revision>` (an
|
|
146
|
+
* immutable revision the customer pinned).
|
|
147
|
+
*
|
|
148
|
+
* Both forms name a CONCRETE MODEL. Neither can name a routing profile — see
|
|
149
|
+
* {@link routingProfileSlugSchema} — which is what makes "a request for a
|
|
150
|
+
* concrete model is never silently replaced with a different model" a
|
|
151
|
+
* distinction the type system can carry rather than a convention.
|
|
152
|
+
*/
|
|
153
|
+
exports.modelReferenceSchema = zod_1.z
|
|
154
|
+
.string()
|
|
155
|
+
.max(194)
|
|
156
|
+
.regex(new RegExp(`^${SLUG_PATTERN}/${SLUG_PATTERN}(?:@${REVISION_PATTERN})?$`), 'model reference must be <publisher>/<model> or <publisher>/<model>@<revision>');
|
|
157
|
+
/**
|
|
158
|
+
* A routing-profile slug, e.g. `auto`, `fast`, `quality`.
|
|
159
|
+
*
|
|
160
|
+
* Deliberately refuses a `/`, so a profile can never be written in the shape of
|
|
161
|
+
* a model id and no caller can be confused about whether they asked for a
|
|
162
|
+
* concrete model or for Oxy to choose one. Modes like `auto`/`fast`/`quality`
|
|
163
|
+
* are profiles or product presets; they are never model objects.
|
|
164
|
+
*/
|
|
165
|
+
exports.routingProfileSlugSchema = zod_1.z
|
|
166
|
+
.string()
|
|
167
|
+
.max(64)
|
|
168
|
+
.regex(new RegExp(`^${SLUG_PATTERN}$`), 'routing profile must be a lowercase URL-safe slug');
|
|
169
|
+
/** An inference provider slug, e.g. `openai`, `bedrock`, `oxy-hosted`. */
|
|
170
|
+
exports.inferenceProviderSlugSchema = zod_1.z
|
|
171
|
+
.string()
|
|
172
|
+
.max(64)
|
|
173
|
+
.regex(new RegExp(`^${SLUG_PATTERN}$`), 'provider must be a lowercase URL-safe slug');
|
|
174
|
+
/**
|
|
175
|
+
* A deployment/endpoint id. Opaque to customers: which concrete endpoint served
|
|
176
|
+
* a request is the data plane's operational detail, and only the customer-safe
|
|
177
|
+
* subset of it is ever attributed back (see the catalogue's serving-boundary
|
|
178
|
+
* rules).
|
|
179
|
+
*/
|
|
180
|
+
exports.deploymentIdSchema = zod_1.z.string().min(1).max(128);
|
|
181
|
+
/**
|
|
182
|
+
* A region identifier, e.g. `us-west-2`, `eu-central-1`. Free-form rather than
|
|
183
|
+
* a closed enum because the set is provider-defined and grows without any
|
|
184
|
+
* contract change; residency policies match on exact strings.
|
|
185
|
+
*/
|
|
186
|
+
exports.inferenceRegionSchema = zod_1.z
|
|
187
|
+
.string()
|
|
188
|
+
.max(64)
|
|
189
|
+
.regex(new RegExp(`^${SLUG_PATTERN}$`), 'region must be a lowercase URL-safe slug');
|
|
190
|
+
/**
|
|
191
|
+
* The publisher namespace reserved for models Alia actually owns or derives.
|
|
192
|
+
*
|
|
193
|
+
* `alia/*` is never a re-badged third-party route and never a prompt preset —
|
|
194
|
+
* enforcing that is the job of `modelSchema`'s provenance refinement, which
|
|
195
|
+
* this constant exists to be checked against.
|
|
196
|
+
*/
|
|
197
|
+
exports.RESERVED_ALIA_PUBLISHER = 'alia';
|