@oxy.so/core 4.0.0 → 4.1.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.
|
@@ -65,6 +65,7 @@ var _OxyInferenceClient_instances, _OxyInferenceClient_baseURL, _OxyInferenceCli
|
|
|
65
65
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
66
66
|
exports.OxyInferenceClient = exports.OxyInferenceProtocolError = exports.OxyInferenceError = exports.OXY_INFERENCE_BASE_URL = void 0;
|
|
67
67
|
const contracts_1 = require("@oxy.so/contracts");
|
|
68
|
+
const contracts_2 = require("@oxy.so/contracts");
|
|
68
69
|
/** The base URL of the Oxy API, when a caller names none. */
|
|
69
70
|
exports.OXY_INFERENCE_BASE_URL = 'https://api.oxy.so';
|
|
70
71
|
/**
|
|
@@ -175,7 +176,7 @@ class OxyInferenceClient {
|
|
|
175
176
|
* "no such model".
|
|
176
177
|
*/
|
|
177
178
|
async getModel(modelId, options = {}) {
|
|
178
|
-
const parsed =
|
|
179
|
+
const parsed = contracts_2.modelIdSchema.safeParse(modelId);
|
|
179
180
|
if (!parsed.success) {
|
|
180
181
|
throw new Error(`Not a canonical model id: ${modelId}. Expected <publisher>/<model>, e.g. acme/some-model.`);
|
|
181
182
|
}
|
|
@@ -212,6 +213,23 @@ class OxyInferenceClient {
|
|
|
212
213
|
: { delegatedUserId: options.delegatedUserId }),
|
|
213
214
|
});
|
|
214
215
|
}
|
|
216
|
+
/** Typed nonstreaming decisions. Idempotency conflicts are never replayed or retried. */
|
|
217
|
+
async decide(request, options = {}) {
|
|
218
|
+
const input = contracts_1.decisionRequestSchema.parse(request);
|
|
219
|
+
const payload = await __classPrivateFieldGet(this, _OxyInferenceClient_instances, "m", _OxyInferenceClient_request).call(this, 'POST', '/v1/decisions', {
|
|
220
|
+
body: input,
|
|
221
|
+
bindRequestId: true,
|
|
222
|
+
signal: options.signal,
|
|
223
|
+
idempotencyKey: options.idempotencyKey,
|
|
224
|
+
delegatedUserId: options.delegatedUserId,
|
|
225
|
+
});
|
|
226
|
+
const parsed = contracts_1.decisionSuccessSchema.safeParse(payload);
|
|
227
|
+
if (!parsed.success || parsed.data.model !== input.model ||
|
|
228
|
+
!(0, contracts_1.decisionAnswersMatch)(input, parsed.data.data)) {
|
|
229
|
+
throw new OxyInferenceProtocolError('Invalid or mismatched decisions response.');
|
|
230
|
+
}
|
|
231
|
+
return parsed.data;
|
|
232
|
+
}
|
|
215
233
|
/** Generate audio bytes through Oxy's authenticated inference edge. */
|
|
216
234
|
async speech(request, options = {}) {
|
|
217
235
|
const response = await __classPrivateFieldGet(this, _OxyInferenceClient_fetch, "f").call(this, `${__classPrivateFieldGet(this, _OxyInferenceClient_baseURL, "f")}/v1/audio/speech`, {
|
|
@@ -232,7 +250,7 @@ class OxyInferenceClient {
|
|
|
232
250
|
if (!response.ok) {
|
|
233
251
|
throw toInferenceError(await response.json().catch(() => undefined), response.status, requestId);
|
|
234
252
|
}
|
|
235
|
-
const mediaType =
|
|
253
|
+
const mediaType = contracts_2.inferenceAudioMediaTypeSchema.safeParse(mediaTypeOf(response));
|
|
236
254
|
if (!mediaType.success || !response.body) {
|
|
237
255
|
await cancelUnreadResponse(response);
|
|
238
256
|
throw protocolError('The inference API returned no supported audio body.', requestId);
|
|
@@ -249,7 +267,7 @@ class OxyInferenceClient {
|
|
|
249
267
|
break;
|
|
250
268
|
}
|
|
251
269
|
size += next.value.byteLength;
|
|
252
|
-
if (size >
|
|
270
|
+
if (size > contracts_2.MAX_INFERENCE_AUDIO_BYTES)
|
|
253
271
|
throw protocolError('The inference audio exceeds the response limit.', requestId);
|
|
254
272
|
chunks.push(next.value);
|
|
255
273
|
}
|
|
@@ -332,7 +350,7 @@ class OxyInferenceClient {
|
|
|
332
350
|
catch (_error) {
|
|
333
351
|
throw protocolError(`The inference API sent a ${frame.name || 'unnamed'} SSE event that is not JSON.`, requestId);
|
|
334
352
|
}
|
|
335
|
-
const parsed =
|
|
353
|
+
const parsed = contracts_2.inferenceStreamEventSchema.safeParse(payload);
|
|
336
354
|
if (!parsed.success) {
|
|
337
355
|
const path = parsed.error.issues[0]?.path;
|
|
338
356
|
throw protocolError(`The inference API sent an event outside the published stream contract at ${path === undefined || path.length === 0 ? 'an unknown field' : path.join('.')}.`, requestId);
|
|
@@ -428,6 +446,13 @@ async function _OxyInferenceClient_request(method, path, options) {
|
|
|
428
446
|
if (!response.ok) {
|
|
429
447
|
throw toInferenceError(payload, response.status, response.headers.get('X-Oxy-Request-Id'));
|
|
430
448
|
}
|
|
449
|
+
if (options.bindRequestId) {
|
|
450
|
+
const requestId = response.headers.get('X-Oxy-Request-Id');
|
|
451
|
+
if (!requestId || typeof payload !== 'object' || payload === null ||
|
|
452
|
+
!('requestId' in payload) || payload.requestId !== requestId) {
|
|
453
|
+
throw new OxyInferenceProtocolError('Decisions response request ID does not match its header.', requestId ?? undefined);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
431
456
|
return payload;
|
|
432
457
|
};
|
|
433
458
|
/** Bound one public SSE event before parsing it as JSON. */
|
|
@@ -574,7 +599,7 @@ const STATUS_FALLBACK_CODE = {
|
|
|
574
599
|
* together — so an unrecognised one falls back to the status map instead of
|
|
575
600
|
* being asserted into the type.
|
|
576
601
|
*/
|
|
577
|
-
const INFERENCE_ERROR_CODE_SET = new Set(
|
|
602
|
+
const INFERENCE_ERROR_CODE_SET = new Set(contracts_2.INFERENCE_ERROR_CODES);
|
|
578
603
|
/** Read whichever error shape arrived into the one this client throws. */
|
|
579
604
|
function toInferenceError(payload, status, requestIdHeader) {
|
|
580
605
|
const body = (payload ?? {});
|
|
@@ -61,6 +61,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
61
61
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
62
62
|
};
|
|
63
63
|
var _OxyInferenceClient_instances, _OxyInferenceClient_baseURL, _OxyInferenceClient_credential, _OxyInferenceClient_fetch, _OxyInferenceClient_bearer, _OxyInferenceClient_headers, _OxyInferenceClient_request;
|
|
64
|
+
import { decisionRequestSchema, decisionSuccessSchema, decisionAnswersMatch } from '@oxy.so/contracts';
|
|
64
65
|
import { INFERENCE_ERROR_CODES, MAX_INFERENCE_AUDIO_BYTES, inferenceAudioMediaTypeSchema, inferenceStreamEventSchema, modelIdSchema, } from '@oxy.so/contracts';
|
|
65
66
|
/** The base URL of the Oxy API, when a caller names none. */
|
|
66
67
|
export const OXY_INFERENCE_BASE_URL = 'https://api.oxy.so';
|
|
@@ -207,6 +208,23 @@ export class OxyInferenceClient {
|
|
|
207
208
|
: { delegatedUserId: options.delegatedUserId }),
|
|
208
209
|
});
|
|
209
210
|
}
|
|
211
|
+
/** Typed nonstreaming decisions. Idempotency conflicts are never replayed or retried. */
|
|
212
|
+
async decide(request, options = {}) {
|
|
213
|
+
const input = decisionRequestSchema.parse(request);
|
|
214
|
+
const payload = await __classPrivateFieldGet(this, _OxyInferenceClient_instances, "m", _OxyInferenceClient_request).call(this, 'POST', '/v1/decisions', {
|
|
215
|
+
body: input,
|
|
216
|
+
bindRequestId: true,
|
|
217
|
+
signal: options.signal,
|
|
218
|
+
idempotencyKey: options.idempotencyKey,
|
|
219
|
+
delegatedUserId: options.delegatedUserId,
|
|
220
|
+
});
|
|
221
|
+
const parsed = decisionSuccessSchema.safeParse(payload);
|
|
222
|
+
if (!parsed.success || parsed.data.model !== input.model ||
|
|
223
|
+
!decisionAnswersMatch(input, parsed.data.data)) {
|
|
224
|
+
throw new OxyInferenceProtocolError('Invalid or mismatched decisions response.');
|
|
225
|
+
}
|
|
226
|
+
return parsed.data;
|
|
227
|
+
}
|
|
210
228
|
/** Generate audio bytes through Oxy's authenticated inference edge. */
|
|
211
229
|
async speech(request, options = {}) {
|
|
212
230
|
const response = await __classPrivateFieldGet(this, _OxyInferenceClient_fetch, "f").call(this, `${__classPrivateFieldGet(this, _OxyInferenceClient_baseURL, "f")}/v1/audio/speech`, {
|
|
@@ -422,6 +440,13 @@ async function _OxyInferenceClient_request(method, path, options) {
|
|
|
422
440
|
if (!response.ok) {
|
|
423
441
|
throw toInferenceError(payload, response.status, response.headers.get('X-Oxy-Request-Id'));
|
|
424
442
|
}
|
|
443
|
+
if (options.bindRequestId) {
|
|
444
|
+
const requestId = response.headers.get('X-Oxy-Request-Id');
|
|
445
|
+
if (!requestId || typeof payload !== 'object' || payload === null ||
|
|
446
|
+
!('requestId' in payload) || payload.requestId !== requestId) {
|
|
447
|
+
throw new OxyInferenceProtocolError('Decisions response request ID does not match its header.', requestId ?? undefined);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
425
450
|
return payload;
|
|
426
451
|
};
|
|
427
452
|
/** Bound one public SSE event before parsing it as JSON. */
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
* schema, so a rename on either side fails a build rather than a customer's
|
|
50
50
|
* request.
|
|
51
51
|
*/
|
|
52
|
+
import { type DecisionRequest, type DecisionSuccess } from '@oxy.so/contracts';
|
|
52
53
|
import type { CurrencyCode, ExactDecimal, InferenceEnvironment, InferenceErrorCode, InferenceFinishReason, InferenceMessage, InferenceReasoning, InferenceRequestOutcome, InferenceAudioMediaType, InferenceStreamEvent, InferenceSpeechParameters, ModelCatalogueEntry, ResponseFormat, RoutingPolicyReference, RoutingProfile, ToolChoice, ToolDefinition, UnitPrice, UsageQuantity, UsageSource } from '@oxy.so/contracts';
|
|
53
54
|
/** The base URL of the Oxy API, when a caller names none. */
|
|
54
55
|
export declare const OXY_INFERENCE_BASE_URL = "https://api.oxy.so";
|
|
@@ -336,6 +337,8 @@ export declare class OxyInferenceClient {
|
|
|
336
337
|
* `code`, `retryable` and `requestId`.
|
|
337
338
|
*/
|
|
338
339
|
respond(request: OxyResponsesRequest, options?: OxyInferenceRequestOptions): Promise<OxyInferenceResponse>;
|
|
340
|
+
/** Typed nonstreaming decisions. Idempotency conflicts are never replayed or retried. */
|
|
341
|
+
decide(request: DecisionRequest, options?: OxyInferenceRequestOptions): Promise<DecisionSuccess>;
|
|
339
342
|
/** Generate audio bytes through Oxy's authenticated inference edge. */
|
|
340
343
|
speech(request: OxySpeechRequest, options?: OxyInferenceRequestOptions): Promise<OxySpeechResponse>;
|
|
341
344
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxy.so/core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -179,8 +179,8 @@
|
|
|
179
179
|
"@oxy.so/telemetry": "^1.2.0",
|
|
180
180
|
"@noble/ciphers": "^1.3.0",
|
|
181
181
|
"@noble/hashes": "^1.8.0",
|
|
182
|
-
"@oxy.so/contracts": "^4.
|
|
183
|
-
"@oxy.so/protocol": "^1.2.
|
|
182
|
+
"@oxy.so/contracts": "^4.7.0",
|
|
183
|
+
"@oxy.so/protocol": "^1.2.1",
|
|
184
184
|
"@scure/bip39": "^1.6.0",
|
|
185
185
|
"buffer": "^6.0.3",
|
|
186
186
|
"express-rate-limit": "^8.6.0",
|
|
@@ -50,6 +50,8 @@
|
|
|
50
50
|
* request.
|
|
51
51
|
*/
|
|
52
52
|
|
|
53
|
+
import { decisionRequestSchema, decisionSuccessSchema, decisionAnswersMatch, type DecisionRequest, type DecisionSuccess } from '@oxy.so/contracts';
|
|
54
|
+
|
|
53
55
|
import type {
|
|
54
56
|
CurrencyCode,
|
|
55
57
|
ExactDecimal,
|
|
@@ -471,6 +473,27 @@ export class OxyInferenceClient {
|
|
|
471
473
|
});
|
|
472
474
|
}
|
|
473
475
|
|
|
476
|
+
/** Typed nonstreaming decisions. Idempotency conflicts are never replayed or retried. */
|
|
477
|
+
async decide(
|
|
478
|
+
request: DecisionRequest,
|
|
479
|
+
options: OxyInferenceRequestOptions = {},
|
|
480
|
+
): Promise<DecisionSuccess> {
|
|
481
|
+
const input = decisionRequestSchema.parse(request);
|
|
482
|
+
const payload = await this.#request<unknown>('POST', '/v1/decisions', {
|
|
483
|
+
body: input,
|
|
484
|
+
bindRequestId: true,
|
|
485
|
+
signal: options.signal,
|
|
486
|
+
idempotencyKey: options.idempotencyKey,
|
|
487
|
+
delegatedUserId: options.delegatedUserId,
|
|
488
|
+
});
|
|
489
|
+
const parsed = decisionSuccessSchema.safeParse(payload);
|
|
490
|
+
if (!parsed.success || parsed.data.model !== input.model ||
|
|
491
|
+
!decisionAnswersMatch(input, parsed.data.data)) {
|
|
492
|
+
throw new OxyInferenceProtocolError('Invalid or mismatched decisions response.');
|
|
493
|
+
}
|
|
494
|
+
return parsed.data;
|
|
495
|
+
}
|
|
496
|
+
|
|
474
497
|
/** Generate audio bytes through Oxy's authenticated inference edge. */
|
|
475
498
|
async speech(
|
|
476
499
|
request: OxySpeechRequest,
|
|
@@ -728,6 +751,7 @@ export class OxyInferenceClient {
|
|
|
728
751
|
path: string,
|
|
729
752
|
options: {
|
|
730
753
|
body?: unknown;
|
|
754
|
+
bindRequestId?: boolean;
|
|
731
755
|
signal?: AbortSignal;
|
|
732
756
|
idempotencyKey?: string;
|
|
733
757
|
delegatedUserId?: string;
|
|
@@ -758,6 +782,13 @@ export class OxyInferenceClient {
|
|
|
758
782
|
);
|
|
759
783
|
}
|
|
760
784
|
|
|
785
|
+
if (options.bindRequestId) {
|
|
786
|
+
const requestId = response.headers.get('X-Oxy-Request-Id');
|
|
787
|
+
if (!requestId || typeof payload !== 'object' || payload === null ||
|
|
788
|
+
!('requestId' in payload) || payload.requestId !== requestId) {
|
|
789
|
+
throw new OxyInferenceProtocolError('Decisions response request ID does not match its header.', requestId ?? undefined);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
761
792
|
return payload as T;
|
|
762
793
|
}
|
|
763
794
|
}
|