@oxy.so/contracts 1.3.0 → 1.5.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/esm/index.js CHANGED
@@ -138,7 +138,7 @@ export {
138
138
  priceVersionStatusSchema, priceVersionSchema, priceSnapshotSchema, } from './inference/priceVersion.js';
139
139
  export {
140
140
  // The six distinct catalogue objects + the customer-safe projection.
141
- inferenceModalitySchema, modelCapabilitiesSchema, modelLicenseSchema, modelProvenanceSchema, inferenceDataPolicySchema, availabilityScopeSchema, commercialPermissionSchema, modelDeprecationSchema, modelEvaluationResultSchema, modelSafetyMetadataSchema, modelPublisherSchema, catalogueModelSchema, modelRevisionSchema, inferenceProviderSchema, modelDeploymentSchema, routingProfileCandidateSchema, routingProfileSchema, cataloguePublisherSummarySchema, catalogueServingProviderSummarySchema, modelCatalogueEntrySchema, } from './inference/catalogue.js';
141
+ inferenceModalitySchema, reasoningEffortSchema, modelCapabilitiesSchema, modelLicenseSchema, modelProvenanceSchema, inferenceDataPolicySchema, availabilityScopeSchema, commercialPermissionSchema, modelDeprecationSchema, modelEvaluationResultSchema, modelSafetyMetadataSchema, modelPublisherSchema, catalogueModelSchema, modelRevisionSchema, inferenceProviderSchema, modelDeploymentSchema, routingProfileCandidateSchema, routingProfileSchema, cataloguePublisherSummarySchema, catalogueServingProviderSummarySchema, modelCatalogueEntrySchema, } from './inference/catalogue.js';
142
142
  export {
143
143
  // Routing policy: every control, plus the refinement that rejects a policy
144
144
  // no route could ever satisfy.
@@ -155,7 +155,7 @@ export {
155
155
  modelDistributionMethodSchema, modelSystemicRiskTierSchema, trainingComputeFlopsSchema, SYSTEMIC_RISK_COMPUTE_THRESHOLD_FLOPS, modelDownstreamDocumentationSchema, modelGpaiDocumentationSchema, modelLineDeclarationSchema, modelReleaseIngestionRequestSchema, modelReleaseIngestionResultSchema, modelDocumentationSchema, } from './inference/modelDocumentation.js';
156
156
  export {
157
157
  // The normalized Oxy→data-plane request envelope.
158
- inferenceContentSourceSchema, inferenceContentPartSchema, inferenceToolCallSchema, inferenceMessageRoleSchema, inferenceMessageSchema, inferenceInputSchema, samplingParametersSchema, toolDefinitionSchema, toolChoiceSchema, responseFormatSchema, clientRequestMetadataSchema, inferenceRequestSchema, inferenceSpeechParametersSchema, } from './inference/request.js';
158
+ inferenceContentSourceSchema, inferenceContentPartSchema, inferenceToolCallSchema, inferenceMessageRoleSchema, inferenceMessageSchema, inferenceInputSchema, samplingParametersSchema, inferenceReasoningSchema, toolDefinitionSchema, toolChoiceSchema, responseFormatSchema, clientRequestMetadataSchema, inferenceRequestSchema, inferenceSpeechParametersSchema, } from './inference/request.js';
159
159
  export {
160
160
  // Normalized SSE events.
161
161
  inferenceStreamStartEventSchema, inferenceStreamDeltaEventSchema, inferenceAudioMediaTypeSchema, MAX_INFERENCE_AUDIO_BYTES, inferenceStreamAudioEventSchema, inferenceStreamToolCallEventSchema, inferenceStreamUsageEventSchema, inferenceRouteSwitchDetailSchema, inferenceRouteSwitchReasonSchema, inferenceStreamRouteSwitchEventSchema, inferenceStreamErrorEventSchema, inferenceFinishReasonSchema, inferenceStreamDoneEventSchema, inferenceStreamEventSchema, } from './inference/streamEvents.js';
@@ -178,3 +178,5 @@ export { AUTONOMY_LEVELS, CAPABILITY_PACKAGES, autonomyLevelSchema, capabilityPa
178
178
  export { emailContextAddressSchema, emailContextMailboxSchema, emailContextMessageSchema, emailAgentContextSchema, } from './emailAgentContext.js';
179
179
  export { inboxComposeRequestSchema, inboxDailyBriefRequestSchema, inboxNaturalSearchRequestSchema, inboxMessageInferenceParamsSchema, inboxInferenceTextResponseSchema, inboxNaturalSearchResponseSchema, inboxSmartRepliesResponseSchema, inboxThreadSummaryResponseSchema, inboxInferenceStreamEventSchema, } from './inference/inbox.js';
180
180
  export * from './externalIdentity.js';
181
+ export * from './linkedAccounts.js';
182
+ export * from './notifications.js';
@@ -40,6 +40,15 @@ export const inferenceModalitySchema = z.enum([
40
40
  'video',
41
41
  'embedding',
42
42
  ]);
43
+ /**
44
+ * How hard a reasoning model is asked to think before it answers.
45
+ *
46
+ * A closed, provider-neutral vocabulary: Kaana translates each value into the
47
+ * serving provider's own control. A model advertises the subset it accepts in
48
+ * `modelCapabilitiesSchema.reasoningEfforts`, and Oxy refuses a request for an
49
+ * effort the resolved model does not advertise rather than dropping it.
50
+ */
51
+ export const reasoningEffortSchema = z.enum(['low', 'medium', 'high']);
43
52
  /**
44
53
  * What a model can do, in the terms a caller has to decide against before
45
54
  * sending a request: can it call tools, does it accept images, will it honour a
@@ -54,6 +63,13 @@ export const modelCapabilitiesSchema = z
54
63
  structuredOutput: z.boolean(),
55
64
  jsonMode: z.boolean(),
56
65
  reasoning: z.boolean(),
66
+ /**
67
+ * The reasoning efforts a request may name for this model, in the order
68
+ * low → high. Empty means the model takes no effort control at all (it may
69
+ * still reason: `reasoning` answers that). Added in contract set 3.1.0;
70
+ * absent on the wire from an older producer, it parses as `[]`.
71
+ */
72
+ reasoningEfforts: z.array(reasoningEffortSchema).default([]),
57
73
  streaming: z.boolean(),
58
74
  promptCaching: z.boolean(),
59
75
  maxContextTokens: z.number().int().positive().safe(),
@@ -469,6 +485,12 @@ export const modelCatalogueEntrySchema = z.object({
469
485
  provenance: modelProvenanceSchema,
470
486
  knowledgeCutoff: inferenceDateSchema.optional(),
471
487
  releasedOn: inferenceDateSchema.optional(),
488
+ /**
489
+ * When the upstream provider first published this model, as that provider
490
+ * reports it. Present only when a provider reported it; never an Oxy or Kaana
491
+ * observation time. Lets a picker order models newest first.
492
+ */
493
+ releasedAt: inferenceTimestampSchema.optional(),
472
494
  regions: z.array(inferenceRegionSchema).default([]),
473
495
  servingProviders: z.array(catalogueServingProviderSummarySchema).default([]),
474
496
  dataPolicy: inferenceDataPolicySchema,
@@ -25,7 +25,7 @@
25
25
  */
26
26
  import { z } from "zod";
27
27
  import { inferenceAttributionSchema } from "./attribution.js";
28
- import { inferenceModalitySchema } from "./catalogue.js";
28
+ import { inferenceModalitySchema, reasoningEffortSchema } from "./catalogue.js";
29
29
  import { idempotencyKeySchema, inferenceTimestampSchema } from "./identifiers.js";
30
30
  import { authorizedRouteSchema, routingPolicyReferenceSchema, routingTargetSchema, } from "./routingPolicy.js";
31
31
  /* -------------------------------------------------------------------------- */
@@ -260,6 +260,16 @@ export const responseFormatSchema = z.discriminatedUnion("type", [
260
260
  })
261
261
  .strict(),
262
262
  ]);
263
+ /**
264
+ * The caller's reasoning control. Strict, like every leaf: a provider-specific
265
+ * knob (`budget_tokens`, `summary`) is refused rather than silently dropped.
266
+ *
267
+ * Oxy forwards it only after checking the resolved model advertises the effort
268
+ * in its catalogue `reasoningEfforts`; Kaana translates it per provider.
269
+ */
270
+ export const inferenceReasoningSchema = z
271
+ .object({ effort: reasoningEffortSchema })
272
+ .strict();
263
273
  /* -------------------------------------------------------------------------- */
264
274
  /* Client metadata */
265
275
  /* -------------------------------------------------------------------------- */
@@ -336,6 +346,8 @@ export const inferenceRequestSchema = z
336
346
  stream: z.boolean(),
337
347
  maxOutputTokens: z.number().int().positive().safe().optional(),
338
348
  sampling: samplingParametersSchema,
349
+ /** Absent means the route's own default reasoning behaviour. */
350
+ reasoning: inferenceReasoningSchema.optional(),
339
351
  speech: inferenceSpeechParametersSchema.optional(),
340
352
  tools: z.array(toolDefinitionSchema).default([]),
341
353
  toolChoice: toolChoiceSchema.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.0.0';
102
+ export const INFERENCE_CONTRACT_VERSION = '3.1.0';
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Wire contract for Oxy linked accounts — external accounts (any Mastodon-API
3
+ * server, Bluesky) a LOCAL Oxy user has proven they own by completing an OAuth
4
+ * authorization there.
5
+ *
6
+ * Every response schema is `.strict()` and carries no field a third-party token
7
+ * could occupy: Oxy uses the OAuth flow only to learn which account authorized
8
+ * it, then discards the token. See `docs/identity/linked-accounts.md`.
9
+ *
10
+ * Platform-agnostic — zod only, no react/react-native/expo.
11
+ */
12
+ import { z } from 'zod';
13
+ export const LINKED_ACCOUNT_NETWORKS = ['activitypub', 'atproto'];
14
+ export const linkedAccountNetworkSchema = z.enum(LINKED_ACCOUNT_NETWORKS);
15
+ /**
16
+ * `POST /linked-accounts/:network/start`.
17
+ *
18
+ * - `instance` (activitypub): `mastodon.social`, `https://mastodon.social` or
19
+ * `@user@mastodon.social`.
20
+ * - `handle` (atproto): a handle (`alice.bsky.social`) or a DID.
21
+ * - `returnTo` + `clientId`: where the browser lands afterwards, with
22
+ * `?link_code=` or `?link_error=`. `returnTo` must exactly match a redirect
23
+ * URI registered on the TRUSTED (first-party) application `clientId` names.
24
+ */
25
+ export const startLinkedAccountRequestSchema = z
26
+ .object({
27
+ instance: z.string().trim().min(1).max(320).optional(),
28
+ handle: z.string().trim().min(1).max(320).optional(),
29
+ clientId: z.string().trim().min(1).max(256),
30
+ returnTo: z.string().trim().min(1).max(2048),
31
+ })
32
+ .strict();
33
+ export const startLinkedAccountResponseSchema = z
34
+ .object({
35
+ authorizeUrl: z.string().url(),
36
+ expiresAt: z.string().datetime(),
37
+ })
38
+ .strict();
39
+ /**
40
+ * `POST /linked-accounts/complete`, with the session of the user who STARTED
41
+ * the flow: `code` is the one-time `link_code` the callback appended to
42
+ * `returnTo`. It expires five minutes after the callback. Presented by any
43
+ * other user it is refused (403) and burned.
44
+ */
45
+ export const completeLinkedAccountRequestSchema = z
46
+ .object({ code: z.string().trim().min(1).max(256) })
47
+ .strict();
48
+ /** One live linked account, as its owner sees it. */
49
+ export const linkedAccountSchema = z
50
+ .object({
51
+ id: z.string().min(1),
52
+ network: linkedAccountNetworkSchema,
53
+ /** `username@domain` for ActivityPub; the DID for atproto. */
54
+ accountKey: z.string().min(1),
55
+ /** ActivityPub actor id, or the DID for atproto. */
56
+ actorUri: z.string().min(1),
57
+ handle: z.string().min(1),
58
+ host: z.string().min(1),
59
+ proofMethod: z.literal('oauth'),
60
+ verifiedAt: z.string().datetime(),
61
+ createdAt: z.string().datetime(),
62
+ })
63
+ .strict();
64
+ export const completeLinkedAccountResponseSchema = z
65
+ .object({ linkedAccount: linkedAccountSchema })
66
+ .strict();
67
+ export const linkedAccountListResponseSchema = z
68
+ .object({ linkedAccounts: z.array(linkedAccountSchema) })
69
+ .strict();
70
+ /**
71
+ * `GET /linked-accounts/by-user/:userId` (service token with the privileged
72
+ * `linked-accounts:read`). Adds `federatedUserId`: the FEDERATED shadow user Oxy
73
+ * already holds for that external account, if any — the anchor for adopting
74
+ * content Oxy federated in before the account was linked.
75
+ */
76
+ export const serviceLinkedAccountSchema = linkedAccountSchema
77
+ .extend({ federatedUserId: z.string().min(1).nullable() })
78
+ .strict();
79
+ export const serviceLinkedAccountListResponseSchema = z
80
+ .object({ userId: z.string().min(1), linkedAccounts: z.array(serviceLinkedAccountSchema) })
81
+ .strict();
82
+ /**
83
+ * Error codes the callback appends as `?link_error=<code>` to `returnTo`. An
84
+ * account already linked to someone else is reported by `/complete` (409).
85
+ */
86
+ export const LINKED_ACCOUNT_CALLBACK_ERRORS = [
87
+ 'access_denied',
88
+ 'verification_failed',
89
+ 'provider_unavailable',
90
+ ];
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The closed set of Oxy in-app notification types — owned here so the API's
3
+ * CHECK constraint (`notifications.type`), its request validation and the SDK
4
+ * all read one tuple.
5
+ *
6
+ * `system` is a message from an Oxy service about the recipient's OWN account
7
+ * (Oxy Move's "your migration finished"): the actor is the recipient, and the
8
+ * entity is their profile or an `app` id. It is the only type an actor does not
9
+ * cause.
10
+ *
11
+ * Platform-agnostic — zod only, no react/react-native/expo.
12
+ */
13
+ import { z } from 'zod';
14
+ export const OXY_NOTIFICATION_TYPES = [
15
+ 'like',
16
+ 'reply',
17
+ 'mention',
18
+ 'follow',
19
+ 'repost',
20
+ 'quote',
21
+ 'welcome',
22
+ 'system',
23
+ ];
24
+ /**
25
+ * What a notification's `entityId` names.
26
+ *
27
+ * `app` means `entityId` is an OPAQUE id in the notifying application's own
28
+ * namespace (Oxy Move's migration job id). Oxy never resolves it; it exists so
29
+ * a `system` notification about something that is not a post or a profile does
30
+ * not have to pretend to be one. Valid only with `type: 'system'`.
31
+ */
32
+ export const OXY_NOTIFICATION_ENTITY_TYPES = ['post', 'reply', 'profile', 'app'];
33
+ /** Length caps on the text a `system` notification carries (enforced by CHECKs too). */
34
+ export const OXY_SYSTEM_NOTIFICATION_TITLE_MAX = 120;
35
+ export const OXY_SYSTEM_NOTIFICATION_MESSAGE_MAX = 500;
36
+ export const OXY_NOTIFICATION_URL_MAX = 2048;
37
+ /**
38
+ * `POST /notifications` (service token with `notifications:write`).
39
+ *
40
+ * Only a `system` notification carries text: `title` and `message` are REQUIRED
41
+ * for it and stored, and `url` is an optional deep link (https, or a custom
42
+ * scheme registered as a redirect URI on the calling application). For every
43
+ * other type the client renders from `type` + entity, and `title` / `message` /
44
+ * `data` are accepted but discarded, as they always were.
45
+ *
46
+ * `system` notifications share the duplicate guard (recipient, actor, type,
47
+ * entityId): use a caller-unique `entityId` (a job id) for each distinct event.
48
+ */
49
+ export const createOxyNotificationRequestSchema = z
50
+ .object({
51
+ recipientId: z.string().min(1),
52
+ actorId: z.string().min(1),
53
+ type: z.enum(OXY_NOTIFICATION_TYPES),
54
+ entityId: z.string().min(1),
55
+ entityType: z.enum(OXY_NOTIFICATION_ENTITY_TYPES),
56
+ title: z.string().trim().min(1).max(OXY_SYSTEM_NOTIFICATION_TITLE_MAX).optional(),
57
+ message: z.string().trim().min(1).max(OXY_SYSTEM_NOTIFICATION_MESSAGE_MAX).optional(),
58
+ url: z.string().trim().min(1).max(OXY_NOTIFICATION_URL_MAX).optional(),
59
+ data: z.record(z.string(), z.unknown()).optional(),
60
+ })
61
+ .superRefine((value, context) => {
62
+ if (value.type === 'system') {
63
+ if (value.actorId !== value.recipientId)
64
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ['actorId'], message: 'a system notification is from the recipient\'s own account' });
65
+ if (!value.title)
66
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ['title'], message: 'title is required for a system notification' });
67
+ if (!value.message)
68
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ['message'], message: 'message is required for a system notification' });
69
+ }
70
+ else {
71
+ if (value.url !== undefined) {
72
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ['url'], message: 'url is only stored for a system notification' });
73
+ }
74
+ if (value.entityType === 'app') {
75
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ['entityType'], message: "entityType 'app' is only valid for a system notification" });
76
+ }
77
+ }
78
+ });