@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
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The Oxy inference client — one surface, two credential lanes (issue #972,
|
|
4
|
+
* workstream 15).
|
|
5
|
+
*
|
|
6
|
+
* ```typescript
|
|
7
|
+
* // An OpenAI-style machine key: one bearer string, no session, no exchange.
|
|
8
|
+
* const oxy = new OxyInferenceClient({ credential: process.env.OXY_API_KEY });
|
|
9
|
+
*
|
|
10
|
+
* // Oxy auth: whatever bearer the session or the service-token mint holds.
|
|
11
|
+
* const oxy = oxyServices.inference();
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Both lanes reach the SAME endpoints and are told apart only by how the bearer
|
|
15
|
+
* is produced: a machine key is a constant string, and an Oxy bearer rotates, so
|
|
16
|
+
* it is a function this client calls on every request rather than a value it
|
|
17
|
+
* captures once. There is no third lane, and no method behaves differently
|
|
18
|
+
* depending on which one you used.
|
|
19
|
+
*
|
|
20
|
+
* ## What you will observe today
|
|
21
|
+
*
|
|
22
|
+
* **Every invoke refuses.** `respond()` reaches the public edge, which
|
|
23
|
+
* authenticates the credential, resolves attribution, authorizes scopes, pins a
|
|
24
|
+
* routing policy and reserves spend — and then has no data plane to forward to,
|
|
25
|
+
* so it releases the hold and answers `service_unavailable`. That surfaces here
|
|
26
|
+
* as an {@link OxyInferenceError} with `code: 'service_unavailable'`,
|
|
27
|
+
* `retryable: false` and a `requestId`. It is the correct answer, not a
|
|
28
|
+
* misconfiguration of yours, and no balance is spent.
|
|
29
|
+
*
|
|
30
|
+
* **The catalogue is empty**, so {@link OxyInferenceClient.listModels} answers
|
|
31
|
+
* `[]` and {@link OxyInferenceClient.getModel} throws for every id. `[]` is a
|
|
32
|
+
* normal answer to render, not an error to retry.
|
|
33
|
+
*
|
|
34
|
+
* `docs/inference/README.md` is the status board; `docs/inference/sdk.md` is
|
|
35
|
+
* this client's page.
|
|
36
|
+
*
|
|
37
|
+
* ## Why this is a client and not more methods on `OxyServices`
|
|
38
|
+
*
|
|
39
|
+
* Two reasons, both structural. A machine-key holder has no Oxy session at all,
|
|
40
|
+
* so a surface reached only through the session client would be unreachable for
|
|
41
|
+
* exactly the developer this workstream exists to serve. And the `/v1` error
|
|
42
|
+
* body is the contract's `InferenceError` at the top level rather than the
|
|
43
|
+
* platform's `{ error, message }` envelope — it carries `requestId`, `retryable`
|
|
44
|
+
* and `retryAfterMs`, all of which `OxyServices.handleError` would flatten into a
|
|
45
|
+
* message string. `oxyServices.inference()` binds the session bearer into this
|
|
46
|
+
* client so a session-holding app writes no plumbing of its own.
|
|
47
|
+
*
|
|
48
|
+
* ## Streaming is absent on purpose
|
|
49
|
+
*
|
|
50
|
+
* There is no `stream()` method and no `stream` field on a request. The stream
|
|
51
|
+
* event union exists in `@oxyhq/contracts` and no endpoint emits one — the edge
|
|
52
|
+
* refuses `stream: true` with `invalid_request`. A method that always failed
|
|
53
|
+
* would be a worse artefact than an absent one. See
|
|
54
|
+
* `docs/inference/streaming.md`.
|
|
55
|
+
*
|
|
56
|
+
* ## Field names, and the one place they could drift
|
|
57
|
+
*
|
|
58
|
+
* Every VALUE type here comes from `@oxyhq/contracts` — messages, tools, tool
|
|
59
|
+
* choice, response format, usage quantities, unit prices, error codes. The
|
|
60
|
+
* request FIELD NAMES cannot: they belong to `responsesRequestSchema`, which
|
|
61
|
+
* lives in the API because it is a public dialect rather than an Oxy↔data-plane
|
|
62
|
+
* contract. `packages/api/src/schemas/__tests__/sdkRequestCompatibility.test.ts`
|
|
63
|
+
* is the gate — it parses a value of this module's request type against that
|
|
64
|
+
* schema, so a rename on either side fails a build rather than a customer's
|
|
65
|
+
* request.
|
|
66
|
+
*/
|
|
67
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
68
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
69
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
70
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
71
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
72
|
+
};
|
|
73
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
74
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
75
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
76
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
77
|
+
};
|
|
78
|
+
var _OxyInferenceClient_instances, _OxyInferenceClient_baseURL, _OxyInferenceClient_credential, _OxyInferenceClient_fetch, _OxyInferenceClient_bearer, _OxyInferenceClient_request;
|
|
79
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
80
|
+
exports.OxyInferenceClient = exports.OxyInferenceError = exports.OXY_INFERENCE_BASE_URL = void 0;
|
|
81
|
+
const contracts_1 = require("@oxyhq/contracts");
|
|
82
|
+
/** The base URL of the Oxy API, when a caller names none. */
|
|
83
|
+
exports.OXY_INFERENCE_BASE_URL = 'https://api.oxy.so';
|
|
84
|
+
/**
|
|
85
|
+
* Anything the inference API refused.
|
|
86
|
+
*
|
|
87
|
+
* `retryable` is asserted by the server and looked up from a total map over the
|
|
88
|
+
* closed code set — never inferred here from the status. A client that decides
|
|
89
|
+
* retryability from an HTTP status is exactly what the contract's retryability
|
|
90
|
+
* rule exists to prevent, so this class carries the server's answer and does not
|
|
91
|
+
* compute one.
|
|
92
|
+
*/
|
|
93
|
+
class OxyInferenceError extends Error {
|
|
94
|
+
constructor(input) {
|
|
95
|
+
super(input.message);
|
|
96
|
+
this.name = 'OxyInferenceError';
|
|
97
|
+
this.code = input.code;
|
|
98
|
+
this.retryable = input.retryable;
|
|
99
|
+
this.requestId = input.requestId;
|
|
100
|
+
this.status = input.status;
|
|
101
|
+
if (input.retryAfterMs !== undefined)
|
|
102
|
+
this.retryAfterMs = input.retryAfterMs;
|
|
103
|
+
if (input.param !== undefined)
|
|
104
|
+
this.param = input.param;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.OxyInferenceError = OxyInferenceError;
|
|
108
|
+
/**
|
|
109
|
+
* The Oxy inference API.
|
|
110
|
+
*
|
|
111
|
+
* Stateless: it holds a base URL, a way to get a bearer and a `fetch`. Nothing
|
|
112
|
+
* is cached, because the two things worth caching here are a catalogue that is
|
|
113
|
+
* audience-scoped and a receipt that is immutable but rarely re-read.
|
|
114
|
+
*
|
|
115
|
+
* Successful responses are TYPED, not re-parsed. The server validates every one
|
|
116
|
+
* against its own schema before serving it, and a second client-side parse of a
|
|
117
|
+
* non-strict shape would silently DROP fields a newer API added — turning
|
|
118
|
+
* forward compatibility into data loss. Refusals are read defensively, because
|
|
119
|
+
* two routers answer under `/v1` and an unreadable failure must still reach the
|
|
120
|
+
* caller as one.
|
|
121
|
+
*/
|
|
122
|
+
class OxyInferenceClient {
|
|
123
|
+
constructor(options) {
|
|
124
|
+
_OxyInferenceClient_instances.add(this);
|
|
125
|
+
_OxyInferenceClient_baseURL.set(this, void 0);
|
|
126
|
+
_OxyInferenceClient_credential.set(this, void 0);
|
|
127
|
+
_OxyInferenceClient_fetch.set(this, void 0);
|
|
128
|
+
const baseURL = options.baseURL ?? exports.OXY_INFERENCE_BASE_URL;
|
|
129
|
+
__classPrivateFieldSet(this, _OxyInferenceClient_baseURL, baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL, "f");
|
|
130
|
+
__classPrivateFieldSet(this, _OxyInferenceClient_credential, options.credential, "f");
|
|
131
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
132
|
+
if (fetchImpl === undefined) {
|
|
133
|
+
throw new Error('OxyInferenceClient needs a fetch implementation: this runtime has no global fetch, so pass one as `fetch`.');
|
|
134
|
+
}
|
|
135
|
+
__classPrivateFieldSet(this, _OxyInferenceClient_fetch, fetchImpl, "f");
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* The models this caller may use — `GET /v1/models`.
|
|
139
|
+
*
|
|
140
|
+
* Audience-scoped server-side. A machine credential and an anonymous caller
|
|
141
|
+
* both see the PUBLIC catalogue; only an internal/system application's
|
|
142
|
+
* service token sees internal-only routes.
|
|
143
|
+
*
|
|
144
|
+
* **`[]` is a normal answer**, and is the only answer today: the catalogue
|
|
145
|
+
* is populated by operators, and a route is not publicly exposed until
|
|
146
|
+
* somebody has reviewed the right to resell it.
|
|
147
|
+
*/
|
|
148
|
+
async listModels(options = {}) {
|
|
149
|
+
const body = await __classPrivateFieldGet(this, _OxyInferenceClient_instances, "m", _OxyInferenceClient_request).call(this, 'GET', '/v1/models', { ...(options.signal === undefined ? {} : { signal: options.signal }) });
|
|
150
|
+
return body.data;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* One catalogue entry by its canonical id — `GET /v1/models/:publisher/:model`.
|
|
154
|
+
*
|
|
155
|
+
* The id is TWO path segments, because a canonical model id contains a slash
|
|
156
|
+
* and a single encoded segment would never match the route.
|
|
157
|
+
*
|
|
158
|
+
* A model you may not see answers 404 identically to one that does not
|
|
159
|
+
* exist, deliberately: the catalogue is never an existence oracle for what
|
|
160
|
+
* Oxy runs internally.
|
|
161
|
+
*
|
|
162
|
+
* @param modelId - `<publisher>/<model>`. A revision pin
|
|
163
|
+
* (`<publisher>/<model>@<revision>`) names a model REFERENCE rather than a
|
|
164
|
+
* model and is rejected here rather than sent, because the catalogue is
|
|
165
|
+
* keyed on models and a pinned reference would 404 indistinguishably from
|
|
166
|
+
* "no such model".
|
|
167
|
+
*/
|
|
168
|
+
async getModel(modelId, options = {}) {
|
|
169
|
+
const parsed = contracts_1.modelIdSchema.safeParse(modelId);
|
|
170
|
+
if (!parsed.success) {
|
|
171
|
+
throw new Error(`Not a canonical model id: ${modelId}. Expected <publisher>/<model>, e.g. acme/some-model.`);
|
|
172
|
+
}
|
|
173
|
+
const [publisher, model] = parsed.data.split('/');
|
|
174
|
+
const body = await __classPrivateFieldGet(this, _OxyInferenceClient_instances, "m", _OxyInferenceClient_request).call(this, 'GET', `/v1/models/${encodeURIComponent(publisher)}/${encodeURIComponent(model)}`, { ...(options.signal === undefined ? {} : { signal: options.signal }) });
|
|
175
|
+
return body.data;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The routing profiles this caller may select — `GET /v1/models/routing-profiles`.
|
|
179
|
+
*
|
|
180
|
+
* A profile is a named strategy for CHOOSING among routes, not a model: no
|
|
181
|
+
* publisher, no revision, no licence, no weights. Like the model list, `[]`
|
|
182
|
+
* is a normal answer.
|
|
183
|
+
*/
|
|
184
|
+
async listRoutingProfiles(options = {}) {
|
|
185
|
+
const body = await __classPrivateFieldGet(this, _OxyInferenceClient_instances, "m", _OxyInferenceClient_request).call(this, 'GET', '/v1/models/routing-profiles', { ...(options.signal === undefined ? {} : { signal: options.signal }) });
|
|
186
|
+
return body.data;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Send one non-streaming inference request — `POST /v1/responses`.
|
|
190
|
+
*
|
|
191
|
+
* **This refuses in every deployment today** with `service_unavailable`,
|
|
192
|
+
* because there is no data plane behind the edge. The spend held for the
|
|
193
|
+
* request is released before the refusal returns, so nothing is charged.
|
|
194
|
+
*
|
|
195
|
+
* @throws {OxyInferenceError} for every refusal, carrying the server's own
|
|
196
|
+
* `code`, `retryable` and `requestId`.
|
|
197
|
+
*/
|
|
198
|
+
async respond(request, options = {}) {
|
|
199
|
+
return __classPrivateFieldGet(this, _OxyInferenceClient_instances, "m", _OxyInferenceClient_request).call(this, 'POST', '/v1/responses', {
|
|
200
|
+
body: request,
|
|
201
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
202
|
+
...(options.idempotencyKey === undefined
|
|
203
|
+
? {}
|
|
204
|
+
: { idempotencyKey: options.idempotencyKey }),
|
|
205
|
+
...(options.delegatedUserId === undefined
|
|
206
|
+
? {}
|
|
207
|
+
: { delegatedUserId: options.delegatedUserId }),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Read back the settled receipt for one request —
|
|
212
|
+
* `GET /v1/generations/:id`.
|
|
213
|
+
*
|
|
214
|
+
* `id` is the `requestId` you already hold (it is on every response and
|
|
215
|
+
* every error) or the `generationId`. Requires the `inference:usage:read`
|
|
216
|
+
* scope; a caller without it, or one whose application did not make the
|
|
217
|
+
* request, is told the receipt does not exist rather than that it belongs to
|
|
218
|
+
* somebody else.
|
|
219
|
+
*/
|
|
220
|
+
async getGeneration(id, options = {}) {
|
|
221
|
+
const body = await __classPrivateFieldGet(this, _OxyInferenceClient_instances, "m", _OxyInferenceClient_request).call(this, 'GET', `/v1/generations/${encodeURIComponent(id)}`, { ...(options.signal === undefined ? {} : { signal: options.signal }) });
|
|
222
|
+
return body.data;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
exports.OxyInferenceClient = OxyInferenceClient;
|
|
226
|
+
_OxyInferenceClient_baseURL = new WeakMap(), _OxyInferenceClient_credential = new WeakMap(), _OxyInferenceClient_fetch = new WeakMap(), _OxyInferenceClient_instances = new WeakSet(), _OxyInferenceClient_bearer =
|
|
227
|
+
/** The bearer for this request, from whichever lane was configured. */
|
|
228
|
+
async function _OxyInferenceClient_bearer() {
|
|
229
|
+
const value = typeof __classPrivateFieldGet(this, _OxyInferenceClient_credential, "f") === 'string'
|
|
230
|
+
? __classPrivateFieldGet(this, _OxyInferenceClient_credential, "f")
|
|
231
|
+
: await __classPrivateFieldGet(this, _OxyInferenceClient_credential, "f").call(this);
|
|
232
|
+
if (value === null || value === undefined || value.length === 0) {
|
|
233
|
+
throw new Error('OxyInferenceClient has no bearer: the configured credential resolved to nothing. On the Oxy auth lane this usually means the session is not restored yet.');
|
|
234
|
+
}
|
|
235
|
+
return value;
|
|
236
|
+
}, _OxyInferenceClient_request =
|
|
237
|
+
/**
|
|
238
|
+
* One request, and the one place a refusal becomes an
|
|
239
|
+
* {@link OxyInferenceError}.
|
|
240
|
+
*
|
|
241
|
+
* Two error shapes arrive here, because two routers serve `/v1`. The edge
|
|
242
|
+
* returns the contract error at the top level; the catalogue returns the
|
|
243
|
+
* platform's `{ error, message }` envelope. Both are read, and a body that
|
|
244
|
+
* is neither still produces an `OxyInferenceError` — with the code the
|
|
245
|
+
* status maps to — rather than a bare `Error`, so a caller's `catch` never
|
|
246
|
+
* has to branch on which router answered.
|
|
247
|
+
*/
|
|
248
|
+
async function _OxyInferenceClient_request(method, path, options) {
|
|
249
|
+
const headers = {
|
|
250
|
+
Authorization: `Bearer ${await __classPrivateFieldGet(this, _OxyInferenceClient_instances, "m", _OxyInferenceClient_bearer).call(this)}`,
|
|
251
|
+
Accept: 'application/json',
|
|
252
|
+
};
|
|
253
|
+
if (options.body !== undefined)
|
|
254
|
+
headers['Content-Type'] = 'application/json';
|
|
255
|
+
if (options.idempotencyKey !== undefined) {
|
|
256
|
+
headers['Idempotency-Key'] = options.idempotencyKey;
|
|
257
|
+
}
|
|
258
|
+
if (options.delegatedUserId !== undefined) {
|
|
259
|
+
headers['X-Oxy-User-Id'] = options.delegatedUserId;
|
|
260
|
+
}
|
|
261
|
+
const response = await __classPrivateFieldGet(this, _OxyInferenceClient_fetch, "f").call(this, `${__classPrivateFieldGet(this, _OxyInferenceClient_baseURL, "f")}${path}`, {
|
|
262
|
+
method,
|
|
263
|
+
headers,
|
|
264
|
+
...(options.body === undefined ? {} : { body: JSON.stringify(options.body) }),
|
|
265
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
266
|
+
});
|
|
267
|
+
const payload = await response.json().catch(() => undefined);
|
|
268
|
+
if (!response.ok) {
|
|
269
|
+
throw toInferenceError(payload, response.status, response.headers.get('X-Oxy-Request-Id'));
|
|
270
|
+
}
|
|
271
|
+
return payload;
|
|
272
|
+
};
|
|
273
|
+
/**
|
|
274
|
+
* Which code a status means when the body did not name one.
|
|
275
|
+
*
|
|
276
|
+
* Deliberately partial: only the statuses whose meaning is unambiguous without a
|
|
277
|
+
* body. Everything else becomes `internal_error`, which is non-retryable — the
|
|
278
|
+
* safe direction, since inventing a retryable code for an unreadable failure is
|
|
279
|
+
* how one outage becomes a retry storm.
|
|
280
|
+
*/
|
|
281
|
+
const STATUS_FALLBACK_CODE = {
|
|
282
|
+
400: 'invalid_request',
|
|
283
|
+
401: 'authentication_failed',
|
|
284
|
+
403: 'permission_denied',
|
|
285
|
+
404: 'model_not_found',
|
|
286
|
+
409: 'idempotency_conflict',
|
|
287
|
+
413: 'request_too_large',
|
|
288
|
+
429: 'rate_limited',
|
|
289
|
+
502: 'provider_error',
|
|
290
|
+
503: 'service_unavailable',
|
|
291
|
+
504: 'provider_timeout',
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* The closed set the contract defines, as a lookup.
|
|
295
|
+
*
|
|
296
|
+
* A `code` outside it is a contract violation rather than a code this client
|
|
297
|
+
* has not caught up with — `INFERENCE_ERROR_CODES` and the version header move
|
|
298
|
+
* together — so an unrecognised one falls back to the status map instead of
|
|
299
|
+
* being asserted into the type.
|
|
300
|
+
*/
|
|
301
|
+
const INFERENCE_ERROR_CODE_SET = new Set(contracts_1.INFERENCE_ERROR_CODES);
|
|
302
|
+
/** Read whichever error shape arrived into the one this client throws. */
|
|
303
|
+
function toInferenceError(payload, status, requestIdHeader) {
|
|
304
|
+
const body = (payload ?? {});
|
|
305
|
+
// The edge's own shape is the contract error at the top level; the
|
|
306
|
+
// catalogue's is the platform envelope, whose `error` is a string.
|
|
307
|
+
const code = typeof body.code === 'string' && INFERENCE_ERROR_CODE_SET.has(body.code)
|
|
308
|
+
? body.code
|
|
309
|
+
: (STATUS_FALLBACK_CODE[status] ?? 'internal_error');
|
|
310
|
+
const message = typeof body.message === 'string' && body.message.length > 0
|
|
311
|
+
? body.message
|
|
312
|
+
: typeof body.error === 'string' && body.error.length > 0
|
|
313
|
+
? body.error
|
|
314
|
+
: `The inference API answered ${status}.`;
|
|
315
|
+
return new OxyInferenceError({
|
|
316
|
+
code,
|
|
317
|
+
message,
|
|
318
|
+
// A body that did not assert retryability is not retryable: the server
|
|
319
|
+
// is the only thing that may say a retry could succeed.
|
|
320
|
+
retryable: body.retryable === true,
|
|
321
|
+
requestId: typeof body.requestId === 'string' && body.requestId.length > 0
|
|
322
|
+
? body.requestId
|
|
323
|
+
: (requestIdHeader ?? ''),
|
|
324
|
+
status,
|
|
325
|
+
...(body.retryable === true && typeof body.retryAfterMs === 'number'
|
|
326
|
+
? { retryAfterMs: body.retryAfterMs }
|
|
327
|
+
: {}),
|
|
328
|
+
...(typeof body.param === 'string' ? { param: body.param } : {}),
|
|
329
|
+
});
|
|
330
|
+
}
|
|
@@ -341,76 +341,6 @@ function OxyServicesAccountsMixin(Base) {
|
|
|
341
341
|
}
|
|
342
342
|
}
|
|
343
343
|
// =========================================================================
|
|
344
|
-
// Bot (account) service credentials — /accounts/:id/credentials
|
|
345
|
-
// =========================================================================
|
|
346
|
-
/**
|
|
347
|
-
* List a bot account's service credentials. The response NEVER includes
|
|
348
|
-
* secrets.
|
|
349
|
-
* @param accountId - The account's Mongo `_id`.
|
|
350
|
-
*/
|
|
351
|
-
async listAccountCredentials(accountId) {
|
|
352
|
-
try {
|
|
353
|
-
const res = await this.makeRequest('GET', `/accounts/${encodeURIComponent(accountId)}/credentials`, undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
|
|
354
|
-
return res.credentials ?? [];
|
|
355
|
-
}
|
|
356
|
-
catch (error) {
|
|
357
|
-
throw this.handleError(error);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Create a service credential for a bot account. The plaintext `secret` is
|
|
362
|
-
* returned exactly ONCE; the server stores only a hash and will never return
|
|
363
|
-
* it again.
|
|
364
|
-
* @param accountId - The account's Mongo `_id`.
|
|
365
|
-
* @param data - Credential configuration (`type` is always `service`).
|
|
366
|
-
*/
|
|
367
|
-
async createAccountCredential(accountId, data) {
|
|
368
|
-
try {
|
|
369
|
-
const result = await this.makeRequest('POST', `/accounts/${encodeURIComponent(accountId)}/credentials`, data, { cache: false });
|
|
370
|
-
this.clearCacheEntry(`GET:/accounts/${encodeURIComponent(accountId)}/credentials`);
|
|
371
|
-
return result;
|
|
372
|
-
}
|
|
373
|
-
catch (error) {
|
|
374
|
-
throw this.handleError(error);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
/**
|
|
378
|
-
* Rotate a bot credential's secret. The new plaintext `secret` is returned
|
|
379
|
-
* exactly ONCE, along with audit fields: `rotatedFrom` (the previous
|
|
380
|
-
* credentialId) and `graceExpiresAt` (ISO string for the grace window during
|
|
381
|
-
* which the old credential is still honoured).
|
|
382
|
-
* @param accountId - The account's Mongo `_id`.
|
|
383
|
-
* @param credentialId - The credential's Mongo `_id`.
|
|
384
|
-
*/
|
|
385
|
-
async rotateAccountCredential(accountId, credentialId) {
|
|
386
|
-
try {
|
|
387
|
-
const result = await this.makeRequest('POST', `/accounts/${encodeURIComponent(accountId)}/credentials/${encodeURIComponent(credentialId)}/rotate`, undefined, { cache: false });
|
|
388
|
-
// Rotation changes credential status/audit fields surfaced by the list.
|
|
389
|
-
this.clearCacheEntry(`GET:/accounts/${encodeURIComponent(accountId)}/credentials`);
|
|
390
|
-
return result;
|
|
391
|
-
}
|
|
392
|
-
catch (error) {
|
|
393
|
-
throw this.handleError(error);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* Revoke a bot credential (`status='revoked'`). Revoked credentials can no
|
|
398
|
-
* longer authenticate.
|
|
399
|
-
* @param accountId - The account's Mongo `_id`.
|
|
400
|
-
* @param credentialId - The credential's Mongo `_id`.
|
|
401
|
-
*/
|
|
402
|
-
async revokeAccountCredential(accountId, credentialId) {
|
|
403
|
-
try {
|
|
404
|
-
const result = await this.makeRequest('DELETE', `/accounts/${encodeURIComponent(accountId)}/credentials/${encodeURIComponent(credentialId)}`, undefined, { cache: false });
|
|
405
|
-
// Revocation flips the credential's status in the cached list.
|
|
406
|
-
this.clearCacheEntry(`GET:/accounts/${encodeURIComponent(accountId)}/credentials`);
|
|
407
|
-
return result;
|
|
408
|
-
}
|
|
409
|
-
catch (error) {
|
|
410
|
-
throw this.handleError(error);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
// =========================================================================
|
|
414
344
|
// Applications owned by an account — /applications
|
|
415
345
|
// =========================================================================
|
|
416
346
|
/**
|
|
@@ -531,10 +461,13 @@ function OxyServicesAccountsMixin(Base) {
|
|
|
531
461
|
* which the old credential is still honoured).
|
|
532
462
|
* @param applicationId - The application's Mongo `_id`.
|
|
533
463
|
* @param credentialId - The credential's Mongo `_id`.
|
|
464
|
+
* @param options - `graceSeconds` keeps a superseded `machine` token working
|
|
465
|
+
* for that long. Omitted, the previous token dies the moment the
|
|
466
|
+
* replacement is minted.
|
|
534
467
|
*/
|
|
535
|
-
async rotateAppCredential(applicationId, credentialId) {
|
|
468
|
+
async rotateAppCredential(applicationId, credentialId, options) {
|
|
536
469
|
try {
|
|
537
|
-
const result = await this.makeRequest('POST', `/applications/${encodeURIComponent(applicationId)}/credentials/${encodeURIComponent(credentialId)}/rotate`,
|
|
470
|
+
const result = await this.makeRequest('POST', `/applications/${encodeURIComponent(applicationId)}/credentials/${encodeURIComponent(credentialId)}/rotate`, options, { cache: false });
|
|
538
471
|
// Rotation changes credential status/audit fields surfaced by the list.
|
|
539
472
|
this.clearCacheEntry(`GET:/applications/${encodeURIComponent(applicationId)}/credentials`);
|
|
540
473
|
return result;
|
|
@@ -1147,6 +1147,23 @@ function OxyServicesAuthMixin(Base) {
|
|
|
1147
1147
|
* response this method used before were an Oxy invention no OAuth library
|
|
1148
1148
|
* could interoperate with; the endpoint no longer accepts them. The method's
|
|
1149
1149
|
* OWN signature is unchanged, so callers are unaffected.
|
|
1150
|
+
*
|
|
1151
|
+
* `deviceId` + `deviceSecret` are OPTIONAL and their absence is a valid
|
|
1152
|
+
* outcome, not an error. A third-party grant is meant to be isolated from the
|
|
1153
|
+
* browser's shared DeviceSession, so the token endpoint must be free to return
|
|
1154
|
+
* no device credential at all — the guard that used to require the pair made
|
|
1155
|
+
* that omission unshippable, since it turned every third-party sign-in through
|
|
1156
|
+
* the SDK into a silent `exchange-failed`.
|
|
1157
|
+
*
|
|
1158
|
+
* The cost is real and deliberate: a DEVICE-LESS session cannot use the
|
|
1159
|
+
* zero-cookie mint lane (`POST /session/device/token`), because that lane's
|
|
1160
|
+
* whole proof is possession of a `deviceSecret`. Its lifetime is therefore the
|
|
1161
|
+
* access token itself — nothing persists a restore credential, the cold boot's
|
|
1162
|
+
* `device-secret-mint` step reports `no-secret` and skips, and the refresh
|
|
1163
|
+
* scheduler has nothing to re-mint from. When the token expires the session
|
|
1164
|
+
* ends LOUDLY: the 401 lane clears the tokens and the provider resolves signed
|
|
1165
|
+
* out, so the app can run the OAuth flow again. It never degrades into a
|
|
1166
|
+
* session that looks alive and cannot refresh.
|
|
1150
1167
|
*/
|
|
1151
1168
|
async exchangeOAuthCode(params) {
|
|
1152
1169
|
try {
|
|
@@ -1168,7 +1185,9 @@ function OxyServicesAuthMixin(Base) {
|
|
|
1168
1185
|
const deviceId = typeof record.deviceId === 'string' ? record.deviceId : undefined;
|
|
1169
1186
|
const deviceSecret = typeof record.deviceSecret === 'string' ? record.deviceSecret : undefined;
|
|
1170
1187
|
const userRaw = record.user;
|
|
1171
|
-
|
|
1188
|
+
// The device pair is NOT part of this guard — see the note above. What is
|
|
1189
|
+
// still mandatory is what identifies the session at all.
|
|
1190
|
+
if (!sessionId || !userRaw || typeof userRaw !== 'object') {
|
|
1172
1191
|
throw new Error('auth/oauth/token returned an incomplete session payload');
|
|
1173
1192
|
}
|
|
1174
1193
|
const userObj = userRaw;
|
|
@@ -1181,12 +1200,17 @@ function OxyServicesAuthMixin(Base) {
|
|
|
1181
1200
|
if (accessToken) {
|
|
1182
1201
|
this.setTokens(accessToken);
|
|
1183
1202
|
}
|
|
1203
|
+
if (!deviceId || !deviceSecret) {
|
|
1204
|
+
logger_1.logger.debug('auth/oauth/token returned no device credential — this session lives only as long as its access token', { component: 'oxy.auth', method: 'exchangeOAuthCode' });
|
|
1205
|
+
}
|
|
1184
1206
|
return {
|
|
1185
1207
|
sessionId,
|
|
1186
|
-
deviceId,
|
|
1187
1208
|
expiresAt,
|
|
1188
1209
|
accessToken,
|
|
1189
|
-
|
|
1210
|
+
// Omitted rather than set to `undefined` when the server sent no device
|
|
1211
|
+
// credential, so a device-less grant serializes as the absence it is.
|
|
1212
|
+
...(deviceId ? { deviceId } : {}),
|
|
1213
|
+
...(deviceSecret ? { deviceSecret } : {}),
|
|
1190
1214
|
user: {
|
|
1191
1215
|
id: userId,
|
|
1192
1216
|
username: typeof userObj.username === 'string' ? userObj.username : undefined,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The inference API, reached with whatever bearer this session already holds
|
|
4
|
+
* (issue #972, workstream 15).
|
|
5
|
+
*
|
|
6
|
+
* ```typescript
|
|
7
|
+
* const models = await oxyServices.inference().listModels();
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* One method, and it is a FACTORY rather than a set of inference methods on
|
|
11
|
+
* `OxyServices`. The calls themselves live once, in
|
|
12
|
+
* {@link OxyInferenceClient} — which an external developer holding only an
|
|
13
|
+
* `oxy_sk_…` machine key constructs directly, with no Oxy session anywhere in
|
|
14
|
+
* the picture. Declaring the same calls a second time here would give the
|
|
15
|
+
* ecosystem two spellings of one request, and only one of them would stay
|
|
16
|
+
* correct.
|
|
17
|
+
*
|
|
18
|
+
* This is the reasoning `createLinkedClient` is already built on: the plumbing
|
|
19
|
+
* that binds an Oxy bearer to a client belongs in core, once, rather than in
|
|
20
|
+
* each app.
|
|
21
|
+
*
|
|
22
|
+
* The credential is a FUNCTION, not the current token: a session bearer rotates
|
|
23
|
+
* on refresh and on account switch, and a client that captured one at
|
|
24
|
+
* construction would start answering 401 an hour into the process's life.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.OxyServicesInferenceMixin = OxyServicesInferenceMixin;
|
|
28
|
+
const OxyInferenceClient_1 = require("../inference/OxyInferenceClient");
|
|
29
|
+
function OxyServicesInferenceMixin(Base) {
|
|
30
|
+
return class extends Base {
|
|
31
|
+
constructor() {
|
|
32
|
+
super(...arguments);
|
|
33
|
+
/** @internal Memoized so repeated calls return one object identity. */
|
|
34
|
+
this._inferenceClient = null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The inference client for this session.
|
|
38
|
+
*
|
|
39
|
+
* Bound to this instance's base URL and to `getAccessToken()`, so it
|
|
40
|
+
* follows every refresh, sign-in and account switch without being
|
|
41
|
+
* rebuilt.
|
|
42
|
+
*
|
|
43
|
+
* A service-authenticated process wants a different credential and
|
|
44
|
+
* builds {@link OxyInferenceClient} directly:
|
|
45
|
+
* `new OxyInferenceClient({ credential: () => oxy.getServiceToken() })`.
|
|
46
|
+
* The mint is asynchronous and cached, which is exactly what a
|
|
47
|
+
* credential function is for.
|
|
48
|
+
*/
|
|
49
|
+
inference() {
|
|
50
|
+
if (this._inferenceClient === null) {
|
|
51
|
+
this._inferenceClient = new OxyInferenceClient_1.OxyInferenceClient({
|
|
52
|
+
baseURL: this.getBaseURL(),
|
|
53
|
+
credential: () => this.getAccessToken(),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return this._inferenceClient;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -152,7 +152,7 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
152
152
|
* additionally checked for `aud`, `iss`, and `type` claims to prevent
|
|
153
153
|
* cross-token-type confusion attacks.
|
|
154
154
|
* - The backend's own `authMiddleware` uses `jwt.verify()` because it has
|
|
155
|
-
* direct access to `
|
|
155
|
+
* direct access to `ACCESS_TOKEN_SECRET`.
|
|
156
156
|
*
|
|
157
157
|
* **Why session-less user tokens are refused rather than trusted:**
|
|
158
158
|
* every user access token the Oxy API issues carries a `sessionId` (see
|
|
@@ -184,7 +184,7 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
184
184
|
* const oxy = new OxyServices({ baseURL: 'https://api.oxy.so' });
|
|
185
185
|
*
|
|
186
186
|
* // Protect all routes under /protected
|
|
187
|
-
* app.use('/protected', oxy.auth({ jwtSecret: process.env.
|
|
187
|
+
* app.use('/protected', oxy.auth({ jwtSecret: process.env.ACCESS_TOKEN_SECRET }));
|
|
188
188
|
*
|
|
189
189
|
* // Access user in route handler
|
|
190
190
|
* app.get('/protected/me', (req, res) => {
|
|
@@ -198,7 +198,7 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
198
198
|
* app.use('/public', oxy.auth({ optional: true }));
|
|
199
199
|
*
|
|
200
200
|
* // Require a specific scope on a service-token-protected route
|
|
201
|
-
* app.use('/internal/files', oxy.serviceAuth({ jwtSecret: process.env.
|
|
201
|
+
* app.use('/internal/files', oxy.serviceAuth({ jwtSecret: process.env.ACCESS_TOKEN_SECRET }), oxy.requireScope('files:write'));
|
|
202
202
|
* ```
|
|
203
203
|
*
|
|
204
204
|
* @param options Optional configuration
|
|
@@ -363,13 +363,19 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
363
363
|
return onError(error);
|
|
364
364
|
return res.status(401).json(error);
|
|
365
365
|
}
|
|
366
|
-
// Validate required service token fields
|
|
366
|
+
// Validate required service token fields. All of them are
|
|
367
|
+
// required, `ownerAccountId` included: an optional billing
|
|
368
|
+
// principal is one fallback away from being resolved from the
|
|
369
|
+
// delegated user, which is the exact confusion ADR 0007 forbids.
|
|
367
370
|
const appId = decoded.appId;
|
|
368
371
|
const credentialId = decoded.credentialId;
|
|
372
|
+
const ownerAccountId = decoded.ownerAccountId;
|
|
369
373
|
const environment = decoded.environment;
|
|
370
374
|
if (!appId ||
|
|
371
375
|
typeof credentialId !== 'string' ||
|
|
372
376
|
credentialId.length === 0 ||
|
|
377
|
+
typeof ownerAccountId !== 'string' ||
|
|
378
|
+
ownerAccountId.length === 0 ||
|
|
373
379
|
!isOxyServiceEnvironment(environment)) {
|
|
374
380
|
if (optional) {
|
|
375
381
|
req.userId = null;
|
|
@@ -408,6 +414,11 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
408
414
|
return onError(error);
|
|
409
415
|
return res.status(403).json(error);
|
|
410
416
|
}
|
|
417
|
+
// ATTRIBUTION ONLY. `req.userId` answers "on whose behalf", never
|
|
418
|
+
// "who pays": the billing principal stays `req.serviceApp
|
|
419
|
+
// .ownerAccountId`, which this branch does not touch. Read it
|
|
420
|
+
// through `getOxyBillingPrincipal` (`@oxyhq/core/server`), whose
|
|
421
|
+
// return type a user id cannot satisfy (ADR 0007).
|
|
411
422
|
req.userId = oxyUserId;
|
|
412
423
|
req.user = { id: oxyUserId };
|
|
413
424
|
req.serviceActingAs = { userId: oxyUserId, scopes: grant.scopes };
|
|
@@ -422,6 +433,7 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
422
433
|
appId,
|
|
423
434
|
appName: decoded.appName || 'unknown',
|
|
424
435
|
credentialId,
|
|
436
|
+
ownerAccountId,
|
|
425
437
|
scopes: Array.isArray(decoded.scopes) ? decoded.scopes : [],
|
|
426
438
|
environment,
|
|
427
439
|
};
|
|
@@ -738,7 +750,7 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
738
750
|
* @example
|
|
739
751
|
* ```typescript
|
|
740
752
|
* // Protect internal endpoints
|
|
741
|
-
* app.use('/internal', oxy.serviceAuth({ jwtSecret: process.env.
|
|
753
|
+
* app.use('/internal', oxy.serviceAuth({ jwtSecret: process.env.ACCESS_TOKEN_SECRET }));
|
|
742
754
|
*
|
|
743
755
|
* app.post('/internal/trigger', (req, res) => {
|
|
744
756
|
* console.log('Service app:', req.serviceApp);
|
|
@@ -776,7 +788,7 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
776
788
|
* ```typescript
|
|
777
789
|
* app.use(
|
|
778
790
|
* '/internal/files',
|
|
779
|
-
* oxy.serviceAuth({ jwtSecret: process.env.
|
|
791
|
+
* oxy.serviceAuth({ jwtSecret: process.env.ACCESS_TOKEN_SECRET }),
|
|
780
792
|
* oxy.requireScope('files:write'),
|
|
781
793
|
* );
|
|
782
794
|
* ```
|
package/dist/cjs/mixins/index.js
CHANGED
|
@@ -36,6 +36,7 @@ const OxyServices_chains_1 = require("./OxyServices.chains");
|
|
|
36
36
|
const OxyServices_nodes_1 = require("./OxyServices.nodes");
|
|
37
37
|
const OxyServices_links_1 = require("./OxyServices.links");
|
|
38
38
|
const OxyServices_followGraph_1 = require("./OxyServices.followGraph");
|
|
39
|
+
const OxyServices_inference_1 = require("./OxyServices.inference");
|
|
39
40
|
const OxyServices_deviceBoot_1 = require("./OxyServices.deviceBoot");
|
|
40
41
|
const OxyServices_deviceTransfer_1 = require("./OxyServices.deviceTransfer");
|
|
41
42
|
/**
|
|
@@ -100,6 +101,11 @@ const MIXIN_PIPELINE = [
|
|
|
100
101
|
// The user-owned follow graph (#809). One relationship per user and target,
|
|
101
102
|
// shared across applications, with per-application context on top.
|
|
102
103
|
OxyServices_followGraph_1.OxyServicesFollowGraphMixin,
|
|
104
|
+
// The inference model catalogue (#972). Reads only, and deliberately no
|
|
105
|
+
// request/stream/receipt methods — the public inference edge those would
|
|
106
|
+
// call is workstream 4 and does not exist yet. See
|
|
107
|
+
// `docs/inference/README.md` for what is and is not built.
|
|
108
|
+
OxyServices_inference_1.OxyServicesInferenceMixin,
|
|
103
109
|
// Device-first token mint: the client half of the zero-cookie transport
|
|
104
110
|
// (`mintFromDeviceSecret` → `POST /session/device/token`).
|
|
105
111
|
OxyServices_deviceBoot_1.OxyServicesDeviceBootMixin,
|