@oxy.so/contracts 4.11.0 → 4.13.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.
Files changed (40) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/accountGraph.js +15 -0
  3. package/dist/cjs/avatarCharacter.js +120 -0
  4. package/dist/cjs/index.js +24 -14
  5. package/dist/cjs/inference/identifiers.js +14 -1
  6. package/dist/cjs/inference/routingPolicy.js +17 -0
  7. package/dist/cjs/inference/usage.js +8 -0
  8. package/dist/cjs/inference/version.js +1 -1
  9. package/dist/cjs/recommendations.js +3 -0
  10. package/dist/cjs/userResponse.js +7 -0
  11. package/dist/esm/.tsbuildinfo +1 -1
  12. package/dist/esm/accountGraph.js +15 -0
  13. package/dist/esm/avatarCharacter.js +115 -0
  14. package/dist/esm/index.js +4 -1
  15. package/dist/esm/inference/identifiers.js +13 -0
  16. package/dist/esm/inference/routingPolicy.js +18 -1
  17. package/dist/esm/inference/usage.js +9 -1
  18. package/dist/esm/inference/version.js +1 -1
  19. package/dist/esm/recommendations.js +3 -0
  20. package/dist/esm/userResponse.js +7 -0
  21. package/dist/types/.tsbuildinfo +1 -1
  22. package/dist/types/accountGraph.d.ts +15 -4
  23. package/dist/types/accountSubject.d.ts +4 -4
  24. package/dist/types/agency.d.ts +44 -44
  25. package/dist/types/avatarCharacter.d.ts +57 -0
  26. package/dist/types/deviceDirectory.d.ts +66 -66
  27. package/dist/types/email/messages.d.ts +22 -22
  28. package/dist/types/externalIdentity.d.ts +11 -4
  29. package/dist/types/index.d.ts +4 -2
  30. package/dist/types/inference/accountBilling.d.ts +2 -2
  31. package/dist/types/inference/decisions.d.ts +20 -0
  32. package/dist/types/inference/identifiers.d.ts +14 -0
  33. package/dist/types/inference/realtime.d.ts +18 -0
  34. package/dist/types/inference/request.d.ts +54 -0
  35. package/dist/types/inference/routingPolicy.d.ts +10 -0
  36. package/dist/types/inference/usage.d.ts +12 -0
  37. package/dist/types/inference/version.d.ts +1 -1
  38. package/dist/types/recommendations.d.ts +12 -0
  39. package/dist/types/userResponse.d.ts +100 -2
  40. package/package.json +1 -1
@@ -7,6 +7,7 @@
7
7
  * {@link ACCOUNT_CATEGORY_IDS} for the four rules that govern it.
8
8
  */
9
9
  import { z } from 'zod';
10
+ import { avatarCharacterSchema } from './avatarCharacter.js';
10
11
  import { usernameSchema, usernameSchemaForAccountKind } from './username.js';
11
12
  /**
12
13
  * The union is spelled out above and the array proves coverage BOTH ways
@@ -466,8 +467,22 @@ export const createAccountRequestSchema = z
466
467
  * remedy must not either.
467
468
  */
468
469
  isPrivateAccount: z.boolean().optional(),
470
+ /**
471
+ * The 3D avatar character of a `bot` — and ONLY of a bot (the refinement
472
+ * below). Optional on the wire: a bot created without one is given a look
473
+ * seeded from its handle (`avatarCharacterFor`), so every bot account has a
474
+ * character from birth and the owner customizes it afterwards.
475
+ */
476
+ character: avatarCharacterSchema.optional(),
469
477
  })
470
478
  .superRefine((request, ctx) => {
479
+ if (request.character !== undefined && request.kind !== 'bot') {
480
+ ctx.addIssue({
481
+ code: z.ZodIssueCode.custom,
482
+ path: ['character'],
483
+ message: 'Only a bot account carries an avatar character',
484
+ });
485
+ }
471
486
  // The ONE place `kind` and `username` arrive in the same object, so it is the
472
487
  // only place a wire schema CAN apply the per-kind half of the policy: a bot's
473
488
  // handle must end in `bot`. Not a second rule — it asks
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The 3D avatar character a `bot` account carries.
3
+ *
4
+ * ONE definition for the wire shape, its validator and the generator that picks
5
+ * a look for a new bot, shared by the Oxy API (which stores and serves it) and
6
+ * every client. It mirrors Bloom's flat `AvatarCharacter`
7
+ * (`@oxy.so/bloom/agent-avatar`).
8
+ *
9
+ * ## The vocabulary is Bloom's and is NOT enforced here
10
+ *
11
+ * Bloom is a React Native package this one cannot depend on, so the ids a
12
+ * character is made of are restated below as a curated SUBSET, only to PICK a
13
+ * look for a new bot ({@link avatarCharacterFor}). A STORED character is never
14
+ * checked against them: validating against a stale copy of somebody else's
15
+ * catalogue is how a working look starts being refused. What the schema checks
16
+ * is the SHAPE of what a client writes: only the keys a character has, each an
17
+ * id-like string, a `#rrggbb` paint, a spacing in range. The client resolves
18
+ * the ids and falls back on a default look for one it cannot draw.
19
+ */
20
+ import { z } from 'zod';
21
+ const ID = /^[a-z][a-z0-9_]{0,39}$/;
22
+ const HEX = /^#[0-9a-fA-F]{6}$/;
23
+ /** Bounds of {@link AvatarCharacter.eyeSpacing}. */
24
+ export const AVATAR_EYE_SPACING = { min: 0.5, max: 1.5 };
25
+ const idSchema = z.string().regex(ID);
26
+ /**
27
+ * A character a client sends. `.strict()` so a misspelled field is a refusal
28
+ * now rather than a look that never takes effect. A character with nothing in
29
+ * it draws nothing: it needs a preset, or a shape and a paint.
30
+ */
31
+ export const avatarCharacterSchema = z
32
+ .object({
33
+ preset: idSchema.optional(),
34
+ shape: idSchema.optional(),
35
+ eyes: idSchema.optional(),
36
+ eyewear: idSchema.optional(),
37
+ accessory: idSchema.optional(),
38
+ color: idSchema.optional(),
39
+ bodyColor: z
40
+ .string()
41
+ .regex(HEX)
42
+ .transform((hex) => hex.toLowerCase())
43
+ .optional(),
44
+ eyeSpacing: z
45
+ .number()
46
+ .finite()
47
+ .min(AVATAR_EYE_SPACING.min)
48
+ .max(AVATAR_EYE_SPACING.max)
49
+ .optional(),
50
+ })
51
+ .strict()
52
+ .refine((c) => c.preset !== undefined ||
53
+ (c.shape !== undefined && (c.color !== undefined || c.bodyColor !== undefined)), { message: 'A character needs a preset, or a shape and a color' });
54
+ /** The validated character, or `null` for anything that is not one. */
55
+ export function parseAvatarCharacter(value) {
56
+ const parsed = avatarCharacterSchema.safeParse(value);
57
+ return parsed.success ? parsed.data : null;
58
+ }
59
+ /** The parts a new bot's look is picked from: the plain, friendly shapes, in the editor's order. */
60
+ export const AVATAR_PICKABLE_SHAPES = [
61
+ 'rounded_triangle',
62
+ 'capsule',
63
+ 'rounded_head_two_ears',
64
+ 'six_lobed_flower',
65
+ 'heart',
66
+ 'four_lobed_butterfly',
67
+ 'circle_two_ears',
68
+ 'twelve_scalloped_rosette',
69
+ 'rounded_cube',
70
+ 'rounded_diamond',
71
+ 'slender',
72
+ 'pocket',
73
+ 'petal',
74
+ 'star',
75
+ 'cloud',
76
+ 'shield',
77
+ 'pebble',
78
+ 'squircle',
79
+ ];
80
+ /** Bloom's named paints. */
81
+ export const AVATAR_PICKABLE_COLORS = [
82
+ 'pink',
83
+ 'orchid',
84
+ 'violet',
85
+ 'blue',
86
+ 'cyan',
87
+ 'teal',
88
+ 'lime',
89
+ 'yellow',
90
+ 'coral',
91
+ ];
92
+ /** A small, stable hash: the same seed always picks the same look. */
93
+ function hash(seed) {
94
+ let h = 2166136261;
95
+ for (let i = 0; i < seed.length; i++) {
96
+ h ^= seed.charCodeAt(i);
97
+ h = Math.imul(h, 16777619);
98
+ }
99
+ return h >>> 0;
100
+ }
101
+ /**
102
+ * A look for a new bot, picked from its seed (its handle): a shape and a paint,
103
+ * plain oval eyes, nothing on its head. Stable — the same seed is the same look
104
+ * — and the owner changes it afterwards.
105
+ */
106
+ export function avatarCharacterFor(seed) {
107
+ const h = hash(seed);
108
+ return {
109
+ shape: AVATAR_PICKABLE_SHAPES[h % AVATAR_PICKABLE_SHAPES.length],
110
+ eyes: 'oval',
111
+ eyewear: 'none',
112
+ accessory: 'none',
113
+ color: AVATAR_PICKABLE_COLORS[Math.floor(h / AVATAR_PICKABLE_SHAPES.length) % AVATAR_PICKABLE_COLORS.length],
114
+ };
115
+ }
package/dist/esm/index.js CHANGED
@@ -10,6 +10,7 @@
10
10
  * expo, no `require()` in the ESM build.
11
11
  */
12
12
  export { ACCOUNT_KINDS, accountKindSchema, CHILD_ACCOUNT_KINDS, childAccountKindSchema, isAccountKind, isDelegatedActAsEligibleKind, isOperatorSwitchTargetKind, ACCOUNT_CATEGORY_IDS, ACCOUNT_CATEGORY_KINDS, accountCategoriesSchema, accountCategoryIdSchema, isSelectableAccountCategoryId, kindAcceptsAccountCategories, MAX_ACCOUNT_CATEGORIES, newlyAddedRetiredCategories, RETIRED_ACCOUNT_CATEGORY_IDS, SELECTABLE_ACCOUNT_CATEGORY_IDS, createAccountRequestSchema, } from './accountGraph.js';
13
+ export { AVATAR_EYE_SPACING, AVATAR_PICKABLE_COLORS, AVATAR_PICKABLE_SHAPES, avatarCharacterFor, avatarCharacterSchema, parseAvatarCharacter, } from './avatarCharacter.js';
13
14
  export { ACCOUNT_ACTOR_NATURES, ACCOUNT_SUBJECT_CONTRACT_VERSION, FINANCIAL_EFFECT_DIRECTIONS, KIND_INDEPENDENT_ACCOUNT_DIMENSIONS, accountActorChainFromSession, accountActorChainSchema, accountActorNatureSchema, accountKindActorNature, accountKindActsAsItself, attributeFinancialEffect, financialEffectAttributionSchema, } from './accountSubject.js';
14
15
  export { usernameSchema, usernameSchemaForAccountKind, isValidUsername, stripDisallowedUsernameCharacters, applyBotUsernameSuffix, USERNAME_MIN_LENGTH, USERNAME_MAX_LENGTH, USERNAME_INVALID_MESSAGE, BOT_USERNAME_INVALID_MESSAGE, RESERVED_USERNAME_MESSAGE, NUMERIC_USERNAME_MESSAGE, } from './username.js';
15
16
  export {
@@ -119,7 +120,9 @@ oxyAccountIdSchema, delegatedUserIdSchema, oxyApplicationIdSchema, oxyCredential
119
120
  // Wire primitives
120
121
  inferenceTimestampSchema, inferenceDateSchema, inferenceHttpsUrlSchema, sha256DigestSchema,
121
122
  // Catalogue references
122
- publisherSlugSchema, modelSlugSchema, modelIdSchema, modelRevisionLabelSchema, modelReferenceSchema, routingProfileIdSchema, routingProfileSlugSchema, inferenceProviderSlugSchema, deploymentIdSchema, inferenceRegionSchema, RESERVED_ALIA_PUBLISHER, } from './inference/identifiers.js';
123
+ publisherSlugSchema, modelSlugSchema, modelIdSchema, modelRevisionLabelSchema, modelReferenceSchema, routingProfileIdSchema, routingProfileSlugSchema, inferenceProviderSlugSchema, deploymentIdSchema, inferenceRegionSchema,
124
+ // The economic tier Oxy bounds a route by and Kaana reports (ADR 0038)
125
+ inferenceFundingTierSchema, RESERVED_ALIA_PUBLISHER, } from './inference/identifiers.js';
123
126
  export {
124
127
  // Exact money and metered units — never floats, units never money.
125
128
  currencyCodeSchema, INFERENCE_MONEY_SCALE, exactDecimalSchema, moneySchema, USAGE_UNITS, usageUnitSchema, USAGE_SOURCES, usageSourceSchema, usageQuantitySchema, unitPriceSchema, } from './inference/money.js';
@@ -221,3 +221,16 @@ export const inferenceRegionSchema = z
221
221
  * this constant exists to be checked against.
222
222
  */
223
223
  export const RESERVED_ALIA_PUBLISHER = 'alia';
224
+ /**
225
+ * The economic tier of the provider capacity that funds a request, in the
226
+ * order Kaana spends it: `free` (costs nothing), `credit` (a promotional or
227
+ * trial grant, or a prepaid balance), `paid` (ordinary metered spend).
228
+ *
229
+ * Kaana owns provider keys, their economics and which key serves (Oxy ADR
230
+ * 0038, Kaana ADR 0001); Oxy owns what a customer may spend. The tier is the
231
+ * one economic fact the two exchange: Oxy BOUNDS a route by it
232
+ * (`maxFundingTier`), and Kaana REPORTS the tier that served
233
+ * (`normalizedUsageReportSchema.fundingTier`). No key id, label or price
234
+ * travels with it.
235
+ */
236
+ export const inferenceFundingTierSchema = z.enum(['free', 'credit', 'paid']);
@@ -32,7 +32,7 @@
32
32
  * docs/adr/0017-authorized-routes-in-the-envelope.md, issue #972 workstream 6.
33
33
  */
34
34
  import { z } from "zod";
35
- import { deploymentIdSchema, inferenceProviderSlugSchema, inferenceRegionSchema, inferenceTimestampSchema, modelReferenceSchema, oxyAccountIdSchema, oxyApplicationIdSchema, routingProfileIdSchema, } from "./identifiers.js";
35
+ import { deploymentIdSchema, inferenceProviderSlugSchema, inferenceFundingTierSchema, inferenceRegionSchema, inferenceTimestampSchema, modelReferenceSchema, oxyAccountIdSchema, oxyApplicationIdSchema, routingProfileIdSchema, } from "./identifiers.js";
36
36
  import { kaanaCredentialHandleSchema } from "./providerConnection.js";
37
37
  import { exactDecimalSchema, currencyCodeSchema, unitPriceSchema, } from "./money.js";
38
38
  /**
@@ -291,6 +291,16 @@ const authorizedRouteFields = {
291
291
  modelReference: modelReferenceSchema,
292
292
  provider: inferenceProviderSlugSchema,
293
293
  regions: z.array(inferenceRegionSchema),
294
+ /**
295
+ * The dearest funding tier the data plane may spend on this route. Absent
296
+ * means no ceiling. Present, the data plane walks only provider keys whose
297
+ * STATED tier is at or below it — a key whose economics nobody stated is
298
+ * never admitted — and refuses the route rather than fall through to a
299
+ * dearer key. It is how Oxy authorizes a route only while credit funds it
300
+ * (Alia Pro and Ultra spend credits only, ADR 0038). Meaningless with a
301
+ * customer credential, which the provider bills to the customer.
302
+ */
303
+ maxFundingTier: inferenceFundingTierSchema.optional(),
294
304
  /**
295
305
  * Exact customer credential binding. Absence means a platform credential;
296
306
  * presence names the immutable Kaana generation this route may use.
@@ -345,6 +355,13 @@ export const authorizedRouteSchema = z
345
355
  .strict(),
346
356
  ])
347
357
  .superRefine((route, ctx) => {
358
+ if (route.maxFundingTier !== undefined && route.customerProviderCredential !== undefined) {
359
+ ctx.addIssue({
360
+ code: z.ZodIssueCode.custom,
361
+ path: ['maxFundingTier'],
362
+ message: 'a funding ceiling bounds platform keys; a customer credential is billed to the customer',
363
+ });
364
+ }
348
365
  // Same rule as `modelDeploymentSchema`: a route serves specific weights. An
349
366
  // unpinned entry would leave the data plane choosing a revision, which is
350
367
  // the one substitution the customer never authorized by naming a model.
@@ -30,7 +30,7 @@
30
30
  */
31
31
  import { z } from 'zod';
32
32
  import { inferenceAttributionSchema } from './attribution.js';
33
- import { deploymentIdSchema, generationIdSchema, idempotencyKeySchema, inferenceProviderSlugSchema, inferenceTimestampSchema, modelReferenceSchema, requestIdSchema, } from './identifiers.js';
33
+ import { deploymentIdSchema, generationIdSchema, idempotencyKeySchema, inferenceFundingTierSchema, inferenceProviderSlugSchema, inferenceTimestampSchema, modelReferenceSchema, requestIdSchema, } from './identifiers.js';
34
34
  import { currencyCodeSchema, exactDecimalSchema, usageQuantitySchema, usageSourceSchema, } from './money.js';
35
35
  import { priceSnapshotSchema } from './priceVersion.js';
36
36
  /* -------------------------------------------------------------------------- */
@@ -170,6 +170,14 @@ export const normalizedUsageReportSchema = z
170
170
  deploymentId: deploymentIdSchema,
171
171
  /** How many allowed route switches occurred while serving this request. */
172
172
  routeSwitches: z.number().int().nonnegative().max(100),
173
+ /**
174
+ * The funding tier of the platform key that served the final attempt, as
175
+ * the data plane states it (ADR 0038). Absent when the key's economics
176
+ * were never stated, when a customer credential served, or when nothing
177
+ * was served. Oxy reads it to account for what the request cost the
178
+ * platform; it is never a key identity and never reaches a customer.
179
+ */
180
+ fundingTier: inferenceFundingTierSchema.optional(),
173
181
  startedAt: inferenceTimestampSchema,
174
182
  completedAt: inferenceTimestampSchema,
175
183
  timeToFirstTokenMs: z.number().int().nonnegative().safe().optional(),
@@ -99,4 +99,4 @@
99
99
  * change to, say, the catalogue reject every in-flight inference request; the
100
100
  * per-shape `schemaVersion` is what a message is validated against.
101
101
  */
102
- export const INFERENCE_CONTRACT_VERSION = '3.5.0';
102
+ export const INFERENCE_CONTRACT_VERSION = '3.8.0';
@@ -11,6 +11,7 @@
11
11
  * / expo, ESM-safe).
12
12
  */
13
13
  import { z } from 'zod';
14
+ import { avatarCharacterSchema } from './avatarCharacter.js';
14
15
  import { userNameSchema } from './userResponse.js';
15
16
  /** User-type filters a caller may exclude from the recommendation surface. */
16
17
  export const recommendationExcludeTypeSchema = z.enum(['federated', 'agent', 'automated']);
@@ -77,6 +78,8 @@ export const recommendationItemSchema = z
77
78
  username: z.string().optional(),
78
79
  name: userNameSchema,
79
80
  avatar: z.string().nullable().optional(),
81
+ /** A bot's 3D avatar character; absent for every other kind. */
82
+ character: avatarCharacterSchema.optional(),
80
83
  /** The account's accent colour, as on every profile; absent when unset. */
81
84
  color: z.string().optional(),
82
85
  description: z.string().nullable().optional(),
@@ -46,6 +46,7 @@ export const USER_PROFILE_COLOR_PRESETS = [
46
46
  import { z } from 'zod';
47
47
  import { verifiedDomainSchema } from './identity.js';
48
48
  import { accountCategoriesSchema, accountKindSchema } from './accountGraph.js';
49
+ import { avatarCharacterSchema } from './avatarCharacter.js';
49
50
  export const userNameSchema = z
50
51
  .object({
51
52
  first: z.string().optional(),
@@ -222,6 +223,12 @@ export const userResponseSchema = z
222
223
  * `user.accountCategories ?? []`.
223
224
  */
224
225
  accountCategories: accountCategoriesSchema.optional(),
226
+ /**
227
+ * The 3D avatar character of a `kind: 'bot'` account — what Bloom's
228
+ * `Avatar` draws instead of a picture. Present ONLY on a bot's payload,
229
+ * never on a person's or an organization's, so those DTOs do not grow.
230
+ */
231
+ character: avatarCharacterSchema.optional(),
225
232
  /**
226
233
  * The authenticated viewer's relationship to this profile. Present ONLY
227
234
  * on single-profile fetches (`GET /profiles/username/:username`,