@oxyhq/core 20.1.0 → 21.0.1
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/HttpService.js +47 -8
- package/dist/cjs/boot/sessionColdBoot.js +107 -8
- package/dist/cjs/i18n/locales/en-US.json +26 -4
- package/dist/cjs/i18n/locales/es-ES.json +26 -4
- package/dist/cjs/i18n/locales/locales/en-US.json +26 -4
- package/dist/cjs/i18n/locales/locales/es-ES.json +26 -4
- package/dist/cjs/index.js +57 -16
- package/dist/cjs/inference/OxyInferenceClient.js +330 -0
- package/dist/cjs/mixins/OxyServices.accounts.js +5 -72
- package/dist/cjs/mixins/OxyServices.auth.js +27 -3
- package/dist/cjs/mixins/OxyServices.inference.js +59 -0
- package/dist/cjs/mixins/OxyServices.utility.js +18 -6
- package/dist/cjs/mixins/index.js +6 -0
- package/dist/cjs/server/auth.js +76 -0
- package/dist/cjs/server/index.js +5 -1
- package/dist/cjs/session/SessionClient.js +361 -1
- package/dist/cjs/session/accountDialogController.js +121 -147
- package/dist/cjs/session/accountSwitchTargets.js +75 -0
- package/dist/cjs/session/deviceDirectory.js +143 -0
- package/dist/cjs/session/deviceSwitcherRows.js +76 -0
- package/dist/cjs/session/projectSessionState.js +8 -1
- package/dist/cjs/session/sharedDeviceCredential.js +247 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +47 -8
- package/dist/esm/boot/sessionColdBoot.js +107 -8
- package/dist/esm/i18n/locales/en-US.json +26 -4
- package/dist/esm/i18n/locales/es-ES.json +26 -4
- package/dist/esm/i18n/locales/locales/en-US.json +26 -4
- package/dist/esm/i18n/locales/locales/es-ES.json +26 -4
- package/dist/esm/index.js +36 -10
- package/dist/esm/inference/OxyInferenceClient.js +325 -0
- package/dist/esm/mixins/OxyServices.accounts.js +5 -72
- package/dist/esm/mixins/OxyServices.auth.js +27 -3
- package/dist/esm/mixins/OxyServices.inference.js +56 -0
- package/dist/esm/mixins/OxyServices.utility.js +18 -6
- package/dist/esm/mixins/index.js +6 -0
- package/dist/esm/server/auth.js +72 -0
- package/dist/esm/server/index.js +1 -1
- package/dist/esm/session/SessionClient.js +362 -2
- package/dist/esm/session/accountDialogController.js +121 -147
- package/dist/esm/session/accountSwitchTargets.js +71 -0
- package/dist/esm/session/deviceDirectory.js +135 -0
- package/dist/esm/session/deviceSwitcherRows.js +72 -0
- package/dist/esm/session/projectSessionState.js +8 -2
- package/dist/esm/session/sharedDeviceCredential.js +239 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +39 -1
- package/dist/types/boot/sessionColdBoot.d.ts +24 -4
- package/dist/types/index.d.ts +11 -4
- package/dist/types/inference/OxyInferenceClient.d.ts +324 -0
- package/dist/types/mixins/OxyServices.accounts.d.ts +73 -95
- package/dist/types/mixins/OxyServices.auth.d.ts +75 -3
- package/dist/types/mixins/OxyServices.inference.d.ts +95 -0
- package/dist/types/mixins/OxyServices.utility.d.ts +44 -13
- package/dist/types/mixins/index.d.ts +2 -1
- package/dist/types/models/session.d.ts +11 -0
- package/dist/types/server/auth.d.ts +80 -0
- package/dist/types/server/index.d.ts +2 -2
- package/dist/types/session/SessionClient.d.ts +202 -1
- package/dist/types/session/accountDialogController.d.ts +76 -64
- package/dist/types/session/accountSwitchTargets.d.ts +64 -0
- package/dist/types/session/deviceDirectory.d.ts +182 -0
- package/dist/types/session/deviceSwitcherRows.d.ts +92 -0
- package/dist/types/session/projectSessionState.d.ts +29 -0
- package/dist/types/session/sharedDeviceCredential.d.ts +202 -0
- package/package.json +3 -3
- package/src/HttpService.ts +50 -10
- package/src/__tests__/httpServiceUnwrapEnvelope.test.ts +115 -0
- package/src/boot/__tests__/sessionColdBoot.sharedDevice.test.ts +325 -0
- package/src/boot/sessionColdBoot.ts +133 -9
- package/src/i18n/locales/en-US.json +26 -4
- package/src/i18n/locales/es-ES.json +26 -4
- package/src/index.ts +94 -25
- package/src/inference/OxyInferenceClient.ts +590 -0
- package/src/inference/__tests__/OxyInferenceClient.test.ts +383 -0
- package/src/mixins/OxyServices.accounts.ts +75 -176
- package/src/mixins/OxyServices.auth.ts +67 -5
- package/src/mixins/OxyServices.inference.ts +57 -0
- package/src/mixins/OxyServices.utility.ts +58 -14
- package/src/mixins/__tests__/accounts.test.ts +57 -102
- package/src/mixins/__tests__/inferenceFactory.test.ts +58 -0
- package/src/mixins/__tests__/preSessionSkipAuth.test.ts +54 -1
- package/src/mixins/__tests__/serviceAuth.test.ts +2 -0
- package/src/mixins/index.ts +8 -0
- package/src/models/session.ts +11 -0
- package/src/server/__tests__/serviceTokenAttribution.test.ts +396 -0
- package/src/server/auth.ts +118 -0
- package/src/server/index.ts +6 -0
- package/src/session/SessionClient.ts +386 -1
- package/src/session/__tests__/SessionClient.directory.test.ts +688 -0
- package/src/session/__tests__/accountDialogController.test.ts +411 -278
- package/src/session/__tests__/accountDialogShape.test.ts +118 -0
- package/src/session/__tests__/accountSwitchTargets.test.ts +132 -0
- package/src/session/__tests__/deviceDirectory.test.ts +422 -0
- package/src/session/__tests__/deviceSwitcherRows.test.ts +223 -0
- package/src/session/__tests__/projectSessionState.test.ts +17 -0
- package/src/session/__tests__/sharedDeviceCredential.test.ts +300 -0
- package/src/session/accountDialogController.ts +141 -179
- package/src/session/accountSwitchTargets.ts +87 -0
- package/src/session/deviceDirectory.ts +269 -0
- package/src/session/deviceSwitcherRows.ts +145 -0
- package/src/session/projectSessionState.ts +9 -3
- package/src/session/sharedDeviceCredential.ts +349 -0
- package/dist/cjs/session/accountProjection.js +0 -213
- package/dist/esm/session/accountProjection.js +0 -207
- package/dist/types/session/accountProjection.d.ts +0 -198
- package/src/session/__tests__/accountProjection.test.ts +0 -447
- package/src/session/accountProjection.ts +0 -354
|
@@ -194,6 +194,18 @@ export declare class HttpService {
|
|
|
194
194
|
* ambiguous with a serialized request body.
|
|
195
195
|
*/
|
|
196
196
|
private static readonly CACHE_IDENTITY_DELIM;
|
|
197
|
+
/**
|
|
198
|
+
* The keys whose presence beside `data` makes a body a PAGE rather than a
|
|
199
|
+
* payload — see {@link unwrapResponse} for why this list is narrow.
|
|
200
|
+
*
|
|
201
|
+
* - `pagination` — the offset-paginated house envelope (`sendPaginated`).
|
|
202
|
+
* - `nextCursor` — the keyset-paginated one (the account audit trails).
|
|
203
|
+
*
|
|
204
|
+
* Membership is decided by key PRESENCE, never by value: the last page sends
|
|
205
|
+
* `nextCursor: null`, and an envelope that collapsed into a bare payload
|
|
206
|
+
* exactly when the stream ended would be a worse bug than the one this fixes.
|
|
207
|
+
*/
|
|
208
|
+
private static readonly PAGE_ENVELOPE_KEYS;
|
|
197
209
|
/**
|
|
198
210
|
* Derive a stable, non-sensitive identity discriminator for cache scoping.
|
|
199
211
|
*
|
|
@@ -270,7 +282,33 @@ export declare class HttpService {
|
|
|
270
282
|
*/
|
|
271
283
|
runSingleFlightDeviceSecretMint(mint: () => Promise<DeviceSecretMintOutcome>): Promise<DeviceSecretMintOutcome>;
|
|
272
284
|
/**
|
|
273
|
-
* Unwrap standardized API response
|
|
285
|
+
* Unwrap the standardized API response envelope — EXCEPT when the envelope is
|
|
286
|
+
* a page, in which case it travels whole.
|
|
287
|
+
*
|
|
288
|
+
* `{ data: <payload> }` is the house success envelope (`sendSuccess`), and
|
|
289
|
+
* reducing it to `<payload>` is what every call site in the SDK expects. But
|
|
290
|
+
* the reduction DISCARDS every sibling key, silently, and a page's siblings
|
|
291
|
+
* are the only thing that says where the next page starts. That is how
|
|
292
|
+
* `GET /accounts/:id/audit` lost its `nextCursor`: the caller received a bare
|
|
293
|
+
* array, `getNextPageParam` read `undefined`, and pagination was dead past the
|
|
294
|
+
* first page with nothing to show that it was.
|
|
295
|
+
*
|
|
296
|
+
* ## Why the rule is narrow, and not "any sibling key survives"
|
|
297
|
+
*
|
|
298
|
+
* "An object carrying `data` plus anything else is not an envelope" is the
|
|
299
|
+
* tempting general rule, and it is wrong here: this API already answers
|
|
300
|
+
* `{ data, count }` on ~15 routes, plus `{ data, source }`, `{ data, reason }`
|
|
301
|
+
* and `{ data, secretDestroyed }`, and a dozen measured Console call sites
|
|
302
|
+
* type those as the bare payload (`Array<ProviderConnection>`,
|
|
303
|
+
* `AccountBillingState | null`, …). Preserving those envelopes would hand every
|
|
304
|
+
* one of them an object where it expects its payload — at runtime only, since
|
|
305
|
+
* the response type is a call-site assertion. So the rule names PAGINATION
|
|
306
|
+
* specifically: `data` beside {@link PAGE_ENVELOPE_KEYS} is a page.
|
|
307
|
+
*
|
|
308
|
+
* A route whose sibling key genuinely matters to its caller belongs in that
|
|
309
|
+
* list, or should not be a sibling of `data` at all — the cursor-paginated
|
|
310
|
+
* surfaces already in the SDK (`{ follows, nextCursor }`,
|
|
311
|
+
* `{ records, nextCursor }`) sidestep this by never using `data`.
|
|
274
312
|
*/
|
|
275
313
|
private unwrapResponse;
|
|
276
314
|
/**
|
|
@@ -15,10 +15,17 @@
|
|
|
15
15
|
* origin persisted a `deviceId` + `deviceSecret`, mint a short access token
|
|
16
16
|
* with a single bearer-less POST to `/session/device/token` (no cookie, no
|
|
17
17
|
* navigation) and rotate the secret in-use.
|
|
18
|
-
* 3. `shared-
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* 3. `shared-device-adopt` (native, ACCOUNT mode) — this app has no credential
|
|
19
|
+
* of its own but a sibling official app already put one in the shared native
|
|
20
|
+
* slot: adopt it and mint. This is how a newly installed official app joins
|
|
21
|
+
* the device's existing session WITHOUT another QR and without ever touching
|
|
22
|
+
* the Commons private key.
|
|
23
|
+
* 4. `shared-key-signin` (native, ACCOUNT mode) — the legacy lane: re-mint by
|
|
24
|
+
* signing with the shared-keychain IDENTITY key. Retained as a recovery /
|
|
25
|
+
* compatibility path for devices whose apps have not yet published a shared
|
|
26
|
+
* device credential — OR `identity-key-signin` (IDENTITY mode) — re-mint
|
|
27
|
+
* from THIS device's primary identity key.
|
|
28
|
+
* 5. Signed out.
|
|
22
29
|
*
|
|
23
30
|
* Two session modes (see {@link RunSessionColdBootOptions.sessionMode}):
|
|
24
31
|
* - `account` (default) — the device's ACTIVE account owns the session. Every
|
|
@@ -33,6 +40,7 @@
|
|
|
33
40
|
*/
|
|
34
41
|
import { type ColdBootOutcome } from '../utils/coldBoot';
|
|
35
42
|
import { type IdentityBinding } from '../session/identitySession';
|
|
43
|
+
import { type SharedDeviceCredentialStore } from '../session/sharedDeviceCredential';
|
|
36
44
|
import type { OxyServices } from '../OxyServices';
|
|
37
45
|
import type { AuthStateStore } from '../session/authStateStore';
|
|
38
46
|
/**
|
|
@@ -108,6 +116,18 @@ export interface RunSessionColdBootOptions {
|
|
|
108
116
|
* `sessionMode: 'identity'`. Ignored in `'account'` mode.
|
|
109
117
|
*/
|
|
110
118
|
identity?: IdentityBinding;
|
|
119
|
+
/**
|
|
120
|
+
* The cross-app native slot holding this device's shared DeviceSession
|
|
121
|
+
* credential, enabling the `shared-device-adopt` lane. Supplied by
|
|
122
|
+
* `@oxyhq/services` on native; absent on web, where each origin is its own
|
|
123
|
+
* device by design.
|
|
124
|
+
*
|
|
125
|
+
* IGNORED in `sessionMode: 'identity'`. The shared slot belongs to whichever
|
|
126
|
+
* principal signed in on this device; an identity-bound client must resolve
|
|
127
|
+
* its session from the local key alone, and adopting a device credential is
|
|
128
|
+
* exactly the drift that mode exists to prevent.
|
|
129
|
+
*/
|
|
130
|
+
sharedDeviceCredential?: SharedDeviceCredentialStore;
|
|
111
131
|
}
|
|
112
132
|
/**
|
|
113
133
|
* Run the device-first cold boot. Resolves to the `runColdBoot` outcome and, as
|
package/dist/types/index.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export { ServiceCredentialMismatchError, } from './mixins/OxyServices.auth';
|
|
|
25
25
|
export { getCommonsApprovalBlockingReason, parseCommonsApprovalExpiresAt, } from './utils/commonsApproval';
|
|
26
26
|
export { selectCommonsDelivery, pushTargetsFromDelivery, commonsDeliveryPlatform } from './utils/commonsDelivery';
|
|
27
27
|
export type { CommonsDeliveryFacts, CommonsDeliveryPlatform, CommonsDeliveryRoute, } from './utils/commonsDelivery';
|
|
28
|
-
export type { ServiceTokenResponse, OAuthUserInfoResponse } from './mixins/OxyServices.auth';
|
|
28
|
+
export type { ServiceTokenResponse, OAuthUserInfoResponse, OAuthTokenExchangeResult, } from './mixins/OxyServices.auth';
|
|
29
29
|
export type { CommonsSignInHandle, CommonsSignInStatus, CommonsSignInPurpose, CommonsOAuthContext, CommonsApprovalInfo, CommonsApprovalSubjectAccount, CommonsSignInActionResult, CommonsOAuthFinalizeResult, CommonsDeliveryResult, } from './mixins/OxyServices.auth';
|
|
30
30
|
export type { PushTokenPlatform, RegisterPushTokenInput, } from './mixins/OxyServices.notifications';
|
|
31
31
|
export type { ServiceApp, ServiceActingAsVerification } from './mixins/OxyServices.utility';
|
|
@@ -40,7 +40,7 @@ export { normalizeProfileLinks } from './utils/profileLinks';
|
|
|
40
40
|
export type { ProfileLink, ProfileLinkMetadata } from './utils/profileLinks';
|
|
41
41
|
export type { PublicApplication, ConnectedApp, } from './mixins/OxyServices.connectedApps';
|
|
42
42
|
export type { StoreCategory, StoreRating, StoreListingSummary, StoreListingDetail, StoreScreenshot, StoreScreenshotPlatform, StoreReview, StoreOwnReview, WriteStoreReviewInput, StoreListingStatus, PublisherListing, WriteListingInput, AddScreenshotInput, UpdateScreenshotInput, StorePage, StorePageOptions, StoreReviewsOptions, } from './mixins/OxyServices.store';
|
|
43
|
-
export type { AccountKind, AccountCategoryId, AccountRelationship, AccountRole, AccountMemberStatus, AccountMemberSource, AccountMember, AccountNode,
|
|
43
|
+
export type { AccountKind, AccountCategoryId, AccountRelationship, AccountRole, AccountMemberStatus, AccountMemberSource, AccountMember, AccountNode, ListAccountsOptions, CreateAccountInput, UpdateAccountInput, ProvisionChannelInput, ProvisionChannelMemberInput, ProvisionChannelResult, InviteAccountMemberInput, UpdateAccountMemberInput, TransferAccountOwnershipInput, AccountSuccessResult, SwitchAccountResult, Application, ApplicationType, ApplicationStatus, ApplicationCredential, ApplicationCredentialType, ApplicationCredentialStatus, ApplicationEnvironment, CreateApplicationInput, UpdateApplicationInput, CreateApplicationCredentialInput, RotateApplicationCredentialInput, ApplicationCredentialWithSecret, RotateApplicationCredentialResult, ApplicationUsagePeriod, ApplicationUsageSummary, ApplicationUsageByDay, ApplicationUsageByEndpoint, ApplicationUsageStats, } from './mixins/OxyServices.accounts';
|
|
44
44
|
export { ACCOUNT_CATEGORY_IDS, MAX_ACCOUNT_CATEGORIES, SELECTABLE_ACCOUNT_CATEGORY_IDS, isSelectableAccountCategoryId, kindAcceptsAccountCategories, } from './mixins/OxyServices.accounts';
|
|
45
45
|
export { buildUserDid } from './mixins/OxyServices.identity';
|
|
46
46
|
export type { IdentityRecordType, UnlinkableAuthMethodType, LinkAuthMethodResult, PublishRecordResult, VerifyRecordResult, VerifyDomainResult, RemoveDomainResult, RotateKeyProof, RotateKeyOptions, RotateKeyResult, } from './mixins/OxyServices.identity';
|
|
@@ -116,12 +116,17 @@ export type { SocketIOFactory, MinimalSocket } from './session/socketLoader';
|
|
|
116
116
|
export { createSessionClientHost } from './session/sessionClientHost';
|
|
117
117
|
export { createSessionClient } from './session/createSessionClient';
|
|
118
118
|
export { deviceStateToClientSessions, activeSessionIdOf, activeUserOf, accountIdsOf, } from './session/projectSessionState';
|
|
119
|
-
export {
|
|
120
|
-
export type {
|
|
119
|
+
export { canActivateContext, directoryDisplayName, directoryHandle, projectDevicePrincipals, resolveActiveContext, resolveDeviceContext, } from './session/deviceDirectory';
|
|
120
|
+
export type { DeviceContext, DeviceContextActor, DeviceContextSubject, DevicePrincipalGroup, } from './session/deviceDirectory';
|
|
121
|
+
export { buildSwitcherRows, showsPrincipalHeaders } from './session/deviceSwitcherRows';
|
|
122
|
+
export type { ResolveAvatarUrl, SwitcherContextRow, SwitcherPrincipalRow, } from './session/deviceSwitcherRows';
|
|
123
|
+
export { isSwitchTargetAccount, canSwitchIntoAccount, } from './session/accountSwitchTargets';
|
|
121
124
|
export { AccountDialogController, createAccountDialogController, } from './session/accountDialogController';
|
|
122
125
|
export type { AccountDialogControllerOptions, AccountDialogSnapshot, AccountDialogView, CommonsAvailability, PopupWindowHandle, SignInFlowPhase, SignInFlowState, SignInProgress, } from './session/accountDialogController';
|
|
123
126
|
export { createWebAuthStateStore, createNativeAuthStateStore, createMemoryAuthStateStore, AUTH_STATE_STORAGE_KEY, } from './session/authStateStore';
|
|
124
127
|
export type { PersistedAuthState, AuthStateStore, NativeKeyValueStorage, } from './session/authStateStore';
|
|
128
|
+
export { createSharedMirroringAuthStateStore, decideSharedDeviceJoin, decideSharedDevicePublish, normalizeSharedDeviceSessionRead, publishProvenDeviceCredential, readLocalDeviceCredential, } from './session/sharedDeviceCredential';
|
|
129
|
+
export type { SharedDeviceCredential, SharedDeviceCredentialRead, SharedDeviceCredentialStore, SharedDeviceJoinDecision, SharedDeviceJoinSkipReason, SharedDevicePublishDecision, SharedDevicePublishOutcome, SharedDevicePublishSkipReason, } from './session/sharedDeviceCredential';
|
|
125
130
|
export { createWebIdentityPinStore, createNativeIdentityPinStore, createMemoryIdentityPinStore, identityPinMatches, IDENTITY_PIN_STORAGE_KEY, } from './session/identityPin';
|
|
126
131
|
export type { IdentityPin, IdentityPinStore } from './session/identityPin';
|
|
127
132
|
export { resolveIdentityPin, establishIdentitySession, } from './session/identitySession';
|
|
@@ -129,6 +134,8 @@ export type { IdentityBinding, IdentityRequestOptions, EstablishedIdentitySessio
|
|
|
129
134
|
export { AccountNotOnDeviceError } from './mixins/OxyServices.deviceBoot';
|
|
130
135
|
export { refreshPersistedSession, refreshDeviceSecretArm, createAuthRefreshHandler, installAuthRefreshHandler, startTokenRefreshScheduler, TOKEN_REFRESH_LEAD_MS, } from './session/refresh';
|
|
131
136
|
export type { RefreshDeps, TokenRefreshSchedulerHandle, DeviceSecretMintOutcome } from './session/refresh';
|
|
137
|
+
export { OxyInferenceClient, OxyInferenceError, OXY_INFERENCE_BASE_URL, } from './inference/OxyInferenceClient';
|
|
138
|
+
export type { OxyInferenceClientOptions, OxyInferenceCredential, OxyInferenceFetch, OxyInferenceRequestOptions, OxyInferenceResponse, OxyGenerationReceipt, OxyResponsesRequest, } from './inference/OxyInferenceClient';
|
|
132
139
|
export { runSessionColdBoot } from './boot/sessionColdBoot';
|
|
133
140
|
export type { RunSessionColdBootOptions, SessionMode, SignedOutReason, DeviceBootSession, } from './boot/sessionColdBoot';
|
|
134
141
|
export { packageInfo } from './constants/version';
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Oxy inference client — one surface, two credential lanes (issue #972,
|
|
3
|
+
* workstream 15).
|
|
4
|
+
*
|
|
5
|
+
* ```typescript
|
|
6
|
+
* // An OpenAI-style machine key: one bearer string, no session, no exchange.
|
|
7
|
+
* const oxy = new OxyInferenceClient({ credential: process.env.OXY_API_KEY });
|
|
8
|
+
*
|
|
9
|
+
* // Oxy auth: whatever bearer the session or the service-token mint holds.
|
|
10
|
+
* const oxy = oxyServices.inference();
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* Both lanes reach the SAME endpoints and are told apart only by how the bearer
|
|
14
|
+
* is produced: a machine key is a constant string, and an Oxy bearer rotates, so
|
|
15
|
+
* it is a function this client calls on every request rather than a value it
|
|
16
|
+
* captures once. There is no third lane, and no method behaves differently
|
|
17
|
+
* depending on which one you used.
|
|
18
|
+
*
|
|
19
|
+
* ## What you will observe today
|
|
20
|
+
*
|
|
21
|
+
* **Every invoke refuses.** `respond()` reaches the public edge, which
|
|
22
|
+
* authenticates the credential, resolves attribution, authorizes scopes, pins a
|
|
23
|
+
* routing policy and reserves spend — and then has no data plane to forward to,
|
|
24
|
+
* so it releases the hold and answers `service_unavailable`. That surfaces here
|
|
25
|
+
* as an {@link OxyInferenceError} with `code: 'service_unavailable'`,
|
|
26
|
+
* `retryable: false` and a `requestId`. It is the correct answer, not a
|
|
27
|
+
* misconfiguration of yours, and no balance is spent.
|
|
28
|
+
*
|
|
29
|
+
* **The catalogue is empty**, so {@link OxyInferenceClient.listModels} answers
|
|
30
|
+
* `[]` and {@link OxyInferenceClient.getModel} throws for every id. `[]` is a
|
|
31
|
+
* normal answer to render, not an error to retry.
|
|
32
|
+
*
|
|
33
|
+
* `docs/inference/README.md` is the status board; `docs/inference/sdk.md` is
|
|
34
|
+
* this client's page.
|
|
35
|
+
*
|
|
36
|
+
* ## Why this is a client and not more methods on `OxyServices`
|
|
37
|
+
*
|
|
38
|
+
* Two reasons, both structural. A machine-key holder has no Oxy session at all,
|
|
39
|
+
* so a surface reached only through the session client would be unreachable for
|
|
40
|
+
* exactly the developer this workstream exists to serve. And the `/v1` error
|
|
41
|
+
* body is the contract's `InferenceError` at the top level rather than the
|
|
42
|
+
* platform's `{ error, message }` envelope — it carries `requestId`, `retryable`
|
|
43
|
+
* and `retryAfterMs`, all of which `OxyServices.handleError` would flatten into a
|
|
44
|
+
* message string. `oxyServices.inference()` binds the session bearer into this
|
|
45
|
+
* client so a session-holding app writes no plumbing of its own.
|
|
46
|
+
*
|
|
47
|
+
* ## Streaming is absent on purpose
|
|
48
|
+
*
|
|
49
|
+
* There is no `stream()` method and no `stream` field on a request. The stream
|
|
50
|
+
* event union exists in `@oxyhq/contracts` and no endpoint emits one — the edge
|
|
51
|
+
* refuses `stream: true` with `invalid_request`. A method that always failed
|
|
52
|
+
* would be a worse artefact than an absent one. See
|
|
53
|
+
* `docs/inference/streaming.md`.
|
|
54
|
+
*
|
|
55
|
+
* ## Field names, and the one place they could drift
|
|
56
|
+
*
|
|
57
|
+
* Every VALUE type here comes from `@oxyhq/contracts` — messages, tools, tool
|
|
58
|
+
* choice, response format, usage quantities, unit prices, error codes. The
|
|
59
|
+
* request FIELD NAMES cannot: they belong to `responsesRequestSchema`, which
|
|
60
|
+
* lives in the API because it is a public dialect rather than an Oxy↔data-plane
|
|
61
|
+
* contract. `packages/api/src/schemas/__tests__/sdkRequestCompatibility.test.ts`
|
|
62
|
+
* is the gate — it parses a value of this module's request type against that
|
|
63
|
+
* schema, so a rename on either side fails a build rather than a customer's
|
|
64
|
+
* request.
|
|
65
|
+
*/
|
|
66
|
+
import type { CurrencyCode, ExactDecimal, InferenceEnvironment, InferenceErrorCode, InferenceFinishReason, InferenceMessage, InferenceRequestOutcome, ModelCatalogueEntry, ResponseFormat, RoutingPolicyReference, RoutingProfile, ToolChoice, ToolDefinition, UnitPrice, UsageQuantity, UsageSource } from '@oxyhq/contracts';
|
|
67
|
+
/** The base URL of the Oxy API, when a caller names none. */
|
|
68
|
+
export declare const OXY_INFERENCE_BASE_URL = "https://api.oxy.so";
|
|
69
|
+
/**
|
|
70
|
+
* How this client gets its bearer.
|
|
71
|
+
*
|
|
72
|
+
* A `string` is a static machine credential (`oxy_sk_…`) — presented verbatim,
|
|
73
|
+
* exactly as a stock OpenAI SDK would present it. A function is the Oxy auth
|
|
74
|
+
* lane and is called on EVERY request, because a session bearer and a service
|
|
75
|
+
* token both rotate and a captured one goes stale inside the hour.
|
|
76
|
+
*/
|
|
77
|
+
export type OxyInferenceCredential = string | (() => string | null | Promise<string | null>);
|
|
78
|
+
/**
|
|
79
|
+
* The `fetch` this client calls.
|
|
80
|
+
*
|
|
81
|
+
* The global signature rather than a narrowed one, so any drop-in
|
|
82
|
+
* implementation — a test double, an instrumented wrapper, a Node agent — is
|
|
83
|
+
* assignable without a cast at either end.
|
|
84
|
+
*/
|
|
85
|
+
export type OxyInferenceFetch = typeof fetch;
|
|
86
|
+
export interface OxyInferenceClientOptions {
|
|
87
|
+
readonly credential: OxyInferenceCredential;
|
|
88
|
+
/** Defaults to {@link OXY_INFERENCE_BASE_URL}. A trailing slash is trimmed. */
|
|
89
|
+
readonly baseURL?: string;
|
|
90
|
+
/** Defaults to the global `fetch`. */
|
|
91
|
+
readonly fetch?: OxyInferenceFetch;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A request to `POST /v1/responses`.
|
|
95
|
+
*
|
|
96
|
+
* `model` and `routingProfile` are mutually exclusive and BOTH are optional: an
|
|
97
|
+
* application whose routing policy carries a `defaultTarget` may name neither,
|
|
98
|
+
* which is what "per-application default model or routing profile" means. Naming
|
|
99
|
+
* both is refused by the edge with `invalid_request`.
|
|
100
|
+
*/
|
|
101
|
+
export interface OxyResponsesRequest {
|
|
102
|
+
/** `<publisher>/<model>` or `<publisher>/<model>@<revision>`. */
|
|
103
|
+
readonly model?: string;
|
|
104
|
+
/** A routing profile slug. Never contains a slash, so it is never a model id. */
|
|
105
|
+
readonly routingProfile?: string;
|
|
106
|
+
/** A prompt, or the message list it is shorthand for. */
|
|
107
|
+
readonly input: string | readonly InferenceMessage[];
|
|
108
|
+
readonly maxOutputTokens?: number;
|
|
109
|
+
readonly temperature?: number;
|
|
110
|
+
readonly topP?: number;
|
|
111
|
+
readonly topK?: number;
|
|
112
|
+
readonly frequencyPenalty?: number;
|
|
113
|
+
readonly presencePenalty?: number;
|
|
114
|
+
readonly seed?: number;
|
|
115
|
+
readonly stopSequences?: readonly string[];
|
|
116
|
+
readonly tools?: readonly ToolDefinition[];
|
|
117
|
+
readonly toolChoice?: ToolChoice;
|
|
118
|
+
readonly responseFormat?: ResponseFormat;
|
|
119
|
+
/** Cost-attribution tags, echoed back on the receipt. At most 16. */
|
|
120
|
+
readonly labels?: Readonly<Record<string, string>>;
|
|
121
|
+
/** Your own correlation id, echoed on the response. */
|
|
122
|
+
readonly clientRequestId?: string;
|
|
123
|
+
}
|
|
124
|
+
export interface OxyInferenceRequestOptions {
|
|
125
|
+
/**
|
|
126
|
+
* Abort the request. The edge treats a client disconnect as a cancellation:
|
|
127
|
+
* it settles what was produced and refunds the rest, so a cancelled request
|
|
128
|
+
* is a normal terminal state rather than an error to clean up after.
|
|
129
|
+
*/
|
|
130
|
+
readonly signal?: AbortSignal;
|
|
131
|
+
/**
|
|
132
|
+
* `Idempotency-Key`. A key already bound to a reservation is REFUSED with
|
|
133
|
+
* `idempotency_conflict` rather than replayed — responses are not retained,
|
|
134
|
+
* so there is nothing to replay, and refusing is what makes "a retry never
|
|
135
|
+
* produces a second charge" structural. At most 128 characters.
|
|
136
|
+
*/
|
|
137
|
+
readonly idempotencyKey?: string;
|
|
138
|
+
/**
|
|
139
|
+
* `X-Oxy-User-Id` — the end user this request is made on behalf of.
|
|
140
|
+
* ATTRIBUTION ONLY: it never changes which account is charged.
|
|
141
|
+
*/
|
|
142
|
+
readonly delegatedUserId?: string;
|
|
143
|
+
}
|
|
144
|
+
/** The body of a successful `POST /v1/responses`. */
|
|
145
|
+
export interface OxyInferenceResponse {
|
|
146
|
+
readonly schemaVersion: 1;
|
|
147
|
+
/** Also on `X-Oxy-Request-Id`, on success and on every refusal. */
|
|
148
|
+
readonly requestId: string;
|
|
149
|
+
readonly generationId?: string;
|
|
150
|
+
/** Always revision-pinned, even when you named only the model line. */
|
|
151
|
+
readonly model: string;
|
|
152
|
+
readonly servingProvider: string;
|
|
153
|
+
readonly finishReason: InferenceFinishReason;
|
|
154
|
+
readonly output: readonly InferenceMessage[];
|
|
155
|
+
/** Metered quantities. Never money — the charge is on the receipt. */
|
|
156
|
+
readonly usage: readonly UsageQuantity[];
|
|
157
|
+
/** The exact policy version this request was admitted under. */
|
|
158
|
+
readonly routingPolicy: RoutingPolicyReference;
|
|
159
|
+
/**
|
|
160
|
+
* How long Oxy took over this request, in whole milliseconds — also on
|
|
161
|
+
* `X-Oxy-Latency-Ms`.
|
|
162
|
+
*
|
|
163
|
+
* Measured from the moment the edge received the request through
|
|
164
|
+
* authentication, admission, routing, the reservation, the call to the
|
|
165
|
+
* inference data plane and the settlement of the hold. Most of it is the
|
|
166
|
+
* upstream generating tokens; it does not separate the two.
|
|
167
|
+
*
|
|
168
|
+
* It is NOT the round trip you can measure yourself, which additionally
|
|
169
|
+
* covers DNS, TLS, both network legs and your own parse. Report them side by
|
|
170
|
+
* side rather than picking one — this figure has no network in it and yours
|
|
171
|
+
* cannot be attributed to the model.
|
|
172
|
+
*
|
|
173
|
+
* Optional because it is additive: an Oxy deployment older than the field
|
|
174
|
+
* omits it, and a streamed request never carries it (the head is written
|
|
175
|
+
* before the first frame arrives, so the number does not exist yet).
|
|
176
|
+
*/
|
|
177
|
+
readonly latencyMs?: number;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* A settled receipt, as `GET /v1/generations/:id` returns it.
|
|
181
|
+
*
|
|
182
|
+
* Carries the price SNAPSHOT rather than a reference to a price version, so the
|
|
183
|
+
* arithmetic stays checkable after that version has been superseded.
|
|
184
|
+
*/
|
|
185
|
+
export interface OxyGenerationReceipt {
|
|
186
|
+
readonly schemaVersion: 1;
|
|
187
|
+
readonly receiptId: string;
|
|
188
|
+
readonly requestId: string;
|
|
189
|
+
readonly generationId?: string;
|
|
190
|
+
readonly applicationId: string;
|
|
191
|
+
readonly credentialId: string;
|
|
192
|
+
/** Attribution only. Never the billing identity. */
|
|
193
|
+
readonly delegatedUserId?: string;
|
|
194
|
+
readonly environment: InferenceEnvironment;
|
|
195
|
+
readonly outcome: InferenceRequestOutcome;
|
|
196
|
+
readonly usageSource: UsageSource;
|
|
197
|
+
/** EVERY unit, including the zeros — see `usageSource` for what a zero means. */
|
|
198
|
+
readonly units: readonly UsageQuantity[];
|
|
199
|
+
readonly resolvedModelReference: string;
|
|
200
|
+
readonly servingProvider: string;
|
|
201
|
+
readonly priceSnapshot: {
|
|
202
|
+
readonly priceVersionId: string;
|
|
203
|
+
readonly currency: CurrencyCode;
|
|
204
|
+
readonly unitPrices: readonly UnitPrice[];
|
|
205
|
+
};
|
|
206
|
+
readonly billedAmount: ExactDecimal;
|
|
207
|
+
readonly currency: CurrencyCode;
|
|
208
|
+
/** A BYOK route: `billedAmount` is Oxy's fee, not the cost of the tokens. */
|
|
209
|
+
readonly platformFeeOnly: boolean;
|
|
210
|
+
readonly settledAt: string;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Anything the inference API refused.
|
|
214
|
+
*
|
|
215
|
+
* `retryable` is asserted by the server and looked up from a total map over the
|
|
216
|
+
* closed code set — never inferred here from the status. A client that decides
|
|
217
|
+
* retryability from an HTTP status is exactly what the contract's retryability
|
|
218
|
+
* rule exists to prevent, so this class carries the server's answer and does not
|
|
219
|
+
* compute one.
|
|
220
|
+
*/
|
|
221
|
+
export declare class OxyInferenceError extends Error {
|
|
222
|
+
readonly code: InferenceErrorCode;
|
|
223
|
+
readonly retryable: boolean;
|
|
224
|
+
readonly requestId: string;
|
|
225
|
+
readonly status: number;
|
|
226
|
+
/** How long to wait. Only ever present when `retryable`. */
|
|
227
|
+
readonly retryAfterMs?: number;
|
|
228
|
+
/** The request field at fault, for `invalid_request`. */
|
|
229
|
+
readonly param?: string;
|
|
230
|
+
constructor(input: {
|
|
231
|
+
code: InferenceErrorCode;
|
|
232
|
+
message: string;
|
|
233
|
+
retryable: boolean;
|
|
234
|
+
requestId: string;
|
|
235
|
+
status: number;
|
|
236
|
+
retryAfterMs?: number;
|
|
237
|
+
param?: string;
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* The Oxy inference API.
|
|
242
|
+
*
|
|
243
|
+
* Stateless: it holds a base URL, a way to get a bearer and a `fetch`. Nothing
|
|
244
|
+
* is cached, because the two things worth caching here are a catalogue that is
|
|
245
|
+
* audience-scoped and a receipt that is immutable but rarely re-read.
|
|
246
|
+
*
|
|
247
|
+
* Successful responses are TYPED, not re-parsed. The server validates every one
|
|
248
|
+
* against its own schema before serving it, and a second client-side parse of a
|
|
249
|
+
* non-strict shape would silently DROP fields a newer API added — turning
|
|
250
|
+
* forward compatibility into data loss. Refusals are read defensively, because
|
|
251
|
+
* two routers answer under `/v1` and an unreadable failure must still reach the
|
|
252
|
+
* caller as one.
|
|
253
|
+
*/
|
|
254
|
+
export declare class OxyInferenceClient {
|
|
255
|
+
#private;
|
|
256
|
+
constructor(options: OxyInferenceClientOptions);
|
|
257
|
+
/**
|
|
258
|
+
* The models this caller may use — `GET /v1/models`.
|
|
259
|
+
*
|
|
260
|
+
* Audience-scoped server-side. A machine credential and an anonymous caller
|
|
261
|
+
* both see the PUBLIC catalogue; only an internal/system application's
|
|
262
|
+
* service token sees internal-only routes.
|
|
263
|
+
*
|
|
264
|
+
* **`[]` is a normal answer**, and is the only answer today: the catalogue
|
|
265
|
+
* is populated by operators, and a route is not publicly exposed until
|
|
266
|
+
* somebody has reviewed the right to resell it.
|
|
267
|
+
*/
|
|
268
|
+
listModels(options?: {
|
|
269
|
+
signal?: AbortSignal;
|
|
270
|
+
}): Promise<ModelCatalogueEntry[]>;
|
|
271
|
+
/**
|
|
272
|
+
* One catalogue entry by its canonical id — `GET /v1/models/:publisher/:model`.
|
|
273
|
+
*
|
|
274
|
+
* The id is TWO path segments, because a canonical model id contains a slash
|
|
275
|
+
* and a single encoded segment would never match the route.
|
|
276
|
+
*
|
|
277
|
+
* A model you may not see answers 404 identically to one that does not
|
|
278
|
+
* exist, deliberately: the catalogue is never an existence oracle for what
|
|
279
|
+
* Oxy runs internally.
|
|
280
|
+
*
|
|
281
|
+
* @param modelId - `<publisher>/<model>`. A revision pin
|
|
282
|
+
* (`<publisher>/<model>@<revision>`) names a model REFERENCE rather than a
|
|
283
|
+
* model and is rejected here rather than sent, because the catalogue is
|
|
284
|
+
* keyed on models and a pinned reference would 404 indistinguishably from
|
|
285
|
+
* "no such model".
|
|
286
|
+
*/
|
|
287
|
+
getModel(modelId: string, options?: {
|
|
288
|
+
signal?: AbortSignal;
|
|
289
|
+
}): Promise<ModelCatalogueEntry>;
|
|
290
|
+
/**
|
|
291
|
+
* The routing profiles this caller may select — `GET /v1/models/routing-profiles`.
|
|
292
|
+
*
|
|
293
|
+
* A profile is a named strategy for CHOOSING among routes, not a model: no
|
|
294
|
+
* publisher, no revision, no licence, no weights. Like the model list, `[]`
|
|
295
|
+
* is a normal answer.
|
|
296
|
+
*/
|
|
297
|
+
listRoutingProfiles(options?: {
|
|
298
|
+
signal?: AbortSignal;
|
|
299
|
+
}): Promise<RoutingProfile[]>;
|
|
300
|
+
/**
|
|
301
|
+
* Send one non-streaming inference request — `POST /v1/responses`.
|
|
302
|
+
*
|
|
303
|
+
* **This refuses in every deployment today** with `service_unavailable`,
|
|
304
|
+
* because there is no data plane behind the edge. The spend held for the
|
|
305
|
+
* request is released before the refusal returns, so nothing is charged.
|
|
306
|
+
*
|
|
307
|
+
* @throws {OxyInferenceError} for every refusal, carrying the server's own
|
|
308
|
+
* `code`, `retryable` and `requestId`.
|
|
309
|
+
*/
|
|
310
|
+
respond(request: OxyResponsesRequest, options?: OxyInferenceRequestOptions): Promise<OxyInferenceResponse>;
|
|
311
|
+
/**
|
|
312
|
+
* Read back the settled receipt for one request —
|
|
313
|
+
* `GET /v1/generations/:id`.
|
|
314
|
+
*
|
|
315
|
+
* `id` is the `requestId` you already hold (it is on every response and
|
|
316
|
+
* every error) or the `generationId`. Requires the `inference:usage:read`
|
|
317
|
+
* scope; a caller without it, or one whose application did not make the
|
|
318
|
+
* request, is told the receipt does not exist rather than that it belongs to
|
|
319
|
+
* somebody else.
|
|
320
|
+
*/
|
|
321
|
+
getGeneration(id: string, options?: {
|
|
322
|
+
signal?: AbortSignal;
|
|
323
|
+
}): Promise<OxyGenerationReceipt>;
|
|
324
|
+
}
|