@onesi/genesis 0.2.3 → 0.2.5
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/README.md +1 -1
- package/dist/boxes.d.ts +1 -1
- package/dist/boxes.js +1 -1
- package/dist/client.d.ts +18 -1
- package/dist/client.js +62 -6
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +30 -1
- package/dist/fleets.d.ts +4 -1
- package/dist/fleets.js +10 -2
- package/dist/foundry.d.ts +3 -2
- package/dist/foundry.js +3 -2
- package/dist/generated/operations.js +181 -153
- package/dist/generated/schema.d.ts +167 -142
- package/dist/genesis.d.ts +16 -6
- package/dist/genesis.js +2 -0
- package/dist/genesis.openapi.json +209 -167
- package/dist/hives-project.d.ts +18 -0
- package/dist/hives-project.js +51 -0
- package/dist/idempotency.js +6 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/redact.d.ts +0 -6
- package/dist/redact.js +3 -2
- package/dist/urls.d.ts +9 -0
- package/dist/urls.js +83 -0
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/boxes.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { OperationInput as In } from './types.js';
|
|
|
3
3
|
/** Boxes (mail, docs, files, sheets, slides, calendar, identity, MCP): one method per operation, grouped as the
|
|
4
4
|
* Boxes resource tree. Path parameters come first, then the body, then query options, then an optional
|
|
5
5
|
* idempotency key on the operations that take one. Operations that stream or download bytes return the raw Response;
|
|
6
|
-
* uploads take a FormData body. GET needs boxes:read, everything else boxes:write. Generated from the Boxes spec. */
|
|
6
|
+
* uploads take a FormData body. GET needs boxes:read, everything else boxes:write; mail domain administration needs boxes:admin. Generated from the Boxes spec. */
|
|
7
7
|
export declare function boxesModule(client: GenesisClient): {
|
|
8
8
|
mail: {
|
|
9
9
|
addresses: {
|
package/dist/boxes.js
CHANGED
|
@@ -2,7 +2,7 @@ import { idempotencyHeaders } from './idempotency.js';
|
|
|
2
2
|
/** Boxes (mail, docs, files, sheets, slides, calendar, identity, MCP): one method per operation, grouped as the
|
|
3
3
|
* Boxes resource tree. Path parameters come first, then the body, then query options, then an optional
|
|
4
4
|
* idempotency key on the operations that take one. Operations that stream or download bytes return the raw Response;
|
|
5
|
-
* uploads take a FormData body. GET needs boxes:read, everything else boxes:write. Generated from the Boxes spec. */
|
|
5
|
+
* uploads take a FormData body. GET needs boxes:read, everything else boxes:write; mail domain administration needs boxes:admin. Generated from the Boxes spec. */
|
|
6
6
|
export function boxesModule(client) {
|
|
7
7
|
return {
|
|
8
8
|
mail: {
|
package/dist/client.d.ts
CHANGED
|
@@ -25,8 +25,15 @@ export interface GenesisOptions {
|
|
|
25
25
|
headers?: Record<string, string>;
|
|
26
26
|
userAgent?: string;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Redact URL userinfo from an error's message, stack and cause chain (bounded). If the error is frozen or any of
|
|
30
|
+
* those properties cannot be rewritten, return a new sanitized Error instead of the original.
|
|
31
|
+
*/
|
|
32
|
+
export declare function scrubError(e: unknown, depth?: number): unknown;
|
|
28
33
|
export declare class GenesisClient {
|
|
29
|
-
|
|
34
|
+
/** The configured gateway root, kept without userinfo so it is safe to log or print. */
|
|
35
|
+
get baseUrl(): string;
|
|
36
|
+
private readonly root;
|
|
30
37
|
readonly routing: 'gateway' | 'direct';
|
|
31
38
|
private readonly opts;
|
|
32
39
|
private readonly fetchImpl;
|
|
@@ -34,6 +41,16 @@ export declare class GenesisClient {
|
|
|
34
41
|
constructor(opts?: GenesisOptions);
|
|
35
42
|
static operations: Readonly<Record<OperationId, OperationMeta>>;
|
|
36
43
|
static services: import("./types.js").ServiceMeta[];
|
|
44
|
+
/** This client's one read of the selected context's Hives binding (cleared on failure). */
|
|
45
|
+
private hivesBinding?;
|
|
46
|
+
/**
|
|
47
|
+
* The Hives project id (prj_…) for a Hives project argument. A prj_… id is returned unchanged. Omitted, or the
|
|
48
|
+
* Genesis pr_… id of the selected context's project, it resolves to the Hives project bound to that selection
|
|
49
|
+
* (`hives_get_context_project`, read at most once per client); otherwise a GenesisError says how to find the prj_… id.
|
|
50
|
+
*/
|
|
51
|
+
hivesProjectId(project_id?: string, init?: {
|
|
52
|
+
signal?: AbortSignal;
|
|
53
|
+
}): Promise<string>;
|
|
37
54
|
/** Resolve the credential: explicit apiKey > token > env GENESIS_API_KEY > GENESIS_TOKEN > provider. */
|
|
38
55
|
credential(): Promise<string | undefined>;
|
|
39
56
|
/** Build the URL for an operation (exposed for tests and `--dry-run`). */
|
package/dist/client.js
CHANGED
|
@@ -8,10 +8,11 @@ import { blindsOperationKey, blindsOwnerTransport, isBlindsOwnerOperation } from
|
|
|
8
8
|
import { isHivesObjectUpload, hivesObjectBody, hivesContentType, isHivesResourceOperation, resourceError, resourceErrorFromResponse } from './hives-resources-transport.js';
|
|
9
9
|
import { operationTable, services } from './generated/operations.js';
|
|
10
10
|
import { GenesisError } from './errors.js';
|
|
11
|
-
import { requireSecureUrl } from './urls.js';
|
|
11
|
+
import { requireSecureUrl, redactUserinfo, stripUserinfo } from './urls.js';
|
|
12
12
|
import { iamDirectoryFailure, isIamDirectoryOperation } from './iam-directory.js';
|
|
13
13
|
import { validateHivesInvocation, hivesFunctionResponseHeaders, hivesFunctionMetadata, hivesOperationError } from './hives-functions.js';
|
|
14
14
|
import { validateHivesEnvironment, validateHivesEnvironmentNames, hivesEnvironmentSetNames, hivesEnvironmentMetadata, hivesEnvironmentReveal } from './hives-environment.js';
|
|
15
|
+
import { hivesProjectIdRefusal, isGenesisProjectId, isHivesProjectOperation, resolveHivesProjectId } from './hives-project.js';
|
|
15
16
|
import { RUNNER_SELF_GRANT_OPERATION, snapshotRunnerSelfGrant, runnerGrantResponse, runnerGrantTransportError } from './runner-grant.js';
|
|
16
17
|
/** The Genesis API gateway. `https://console.life.one/api` remains an alias for the same gateway. */
|
|
17
18
|
export const DEFAULT_BASE_URL = 'https://os.one.ml/api';
|
|
@@ -21,23 +22,75 @@ const ENDLESS_STREAMS = {
|
|
|
21
22
|
hives_stream_logs: 'genesis.hives.logs.stream() or genesis.hives.logs.window()',
|
|
22
23
|
hives_stream_deployment_logs: 'hivesSseWindow(await genesis.raw(...))',
|
|
23
24
|
};
|
|
25
|
+
const SCRUB_DEPTH = 5;
|
|
26
|
+
/**
|
|
27
|
+
* Redact URL userinfo from an error's message, stack and cause chain (bounded). If the error is frozen or any of
|
|
28
|
+
* those properties cannot be rewritten, return a new sanitized Error instead of the original.
|
|
29
|
+
*/
|
|
30
|
+
export function scrubError(e, depth = 0) {
|
|
31
|
+
if (typeof e === 'string')
|
|
32
|
+
return redactUserinfo(e);
|
|
33
|
+
if (!(e instanceof Error))
|
|
34
|
+
return e;
|
|
35
|
+
if (depth >= SCRUB_DEPTH)
|
|
36
|
+
return undefined;
|
|
37
|
+
const message = redactUserinfo(String(e.message));
|
|
38
|
+
const stack = typeof e.stack === 'string' ? redactUserinfo(e.stack) : e.stack;
|
|
39
|
+
const hasCause = 'cause' in e;
|
|
40
|
+
const cause = hasCause ? scrubError(e.cause, depth + 1) : undefined;
|
|
41
|
+
if (message === e.message && stack === e.stack && cause === e.cause)
|
|
42
|
+
return e;
|
|
43
|
+
try {
|
|
44
|
+
if (Object.isFrozen(e))
|
|
45
|
+
throw e;
|
|
46
|
+
Object.defineProperty(e, 'message', { value: message, writable: true, configurable: true, enumerable: false });
|
|
47
|
+
Object.defineProperty(e, 'stack', { value: stack, writable: true, configurable: true, enumerable: false });
|
|
48
|
+
if (hasCause)
|
|
49
|
+
Object.defineProperty(e, 'cause', { value: cause, writable: true, configurable: true, enumerable: false });
|
|
50
|
+
if (e.message === message && e.stack === stack && (!hasCause || e.cause === cause))
|
|
51
|
+
return e;
|
|
52
|
+
}
|
|
53
|
+
catch { /* fall through to a fresh error */ }
|
|
54
|
+
const fresh = new (e instanceof TypeError ? TypeError : Error)(message, hasCause ? { cause } : undefined);
|
|
55
|
+
fresh.name = redactUserinfo(String(e.name));
|
|
56
|
+
fresh.stack = stack;
|
|
57
|
+
return fresh;
|
|
58
|
+
}
|
|
24
59
|
export class GenesisClient {
|
|
25
|
-
|
|
60
|
+
/** The configured gateway root, kept without userinfo so it is safe to log or print. */
|
|
61
|
+
get baseUrl() { return stripUserinfo(this.root); }
|
|
62
|
+
root;
|
|
26
63
|
routing;
|
|
27
64
|
opts;
|
|
28
65
|
fetchImpl;
|
|
29
66
|
get contextFetch() { return this.fetchImpl; }
|
|
30
67
|
constructor(opts = {}) {
|
|
31
68
|
this.opts = opts;
|
|
32
|
-
this.
|
|
69
|
+
this.root = requireSecureUrl(opts.baseUrl ?? env.GENESIS_BASE_URL ?? DEFAULT_BASE_URL, 'baseUrl').replace(/\/+$/, '');
|
|
33
70
|
this.routing = opts.routing ?? (env.GENESIS_ROUTING === 'direct' ? 'direct' : 'gateway');
|
|
34
71
|
const f = opts.fetch ?? globalThis.fetch;
|
|
35
72
|
if (!f)
|
|
36
73
|
throw new Error('No fetch available; pass options.fetch');
|
|
37
|
-
|
|
74
|
+
// fetch errors can quote the request URL (and its userinfo); scrub them before they surface.
|
|
75
|
+
this.fetchImpl = ((input, init) => f.call(globalThis, input, init).catch((e) => { throw scrubError(e); }));
|
|
38
76
|
}
|
|
39
77
|
static operations = operationTable;
|
|
40
78
|
static services = services;
|
|
79
|
+
/** This client's one read of the selected context's Hives binding (cleared on failure). */
|
|
80
|
+
hivesBinding;
|
|
81
|
+
/**
|
|
82
|
+
* The Hives project id (prj_…) for a Hives project argument. A prj_… id is returned unchanged. Omitted, or the
|
|
83
|
+
* Genesis pr_… id of the selected context's project, it resolves to the Hives project bound to that selection
|
|
84
|
+
* (`hives_get_context_project`, read at most once per client); otherwise a GenesisError says how to find the prj_… id.
|
|
85
|
+
*/
|
|
86
|
+
async hivesProjectId(project_id, init = {}) {
|
|
87
|
+
return resolveHivesProjectId(project_id, () => {
|
|
88
|
+
const read = this.hivesBinding ??= this.request('hives_get_context_project', {}, { signal: init.signal });
|
|
89
|
+
read.catch(() => { if (this.hivesBinding === read)
|
|
90
|
+
this.hivesBinding = undefined; });
|
|
91
|
+
return read;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
41
94
|
/** Resolve the credential: explicit apiKey > token > env GENESIS_API_KEY > GENESIS_TOKEN > provider. */
|
|
42
95
|
async credential() {
|
|
43
96
|
return this.opts.apiKey ?? this.opts.token ?? env.GENESIS_API_KEY ?? env.GENESIS_TOKEN ?? (await this.opts.credentials?.());
|
|
@@ -68,7 +121,7 @@ export class GenesisClient {
|
|
|
68
121
|
p = fill(op.upstream.path);
|
|
69
122
|
}
|
|
70
123
|
else {
|
|
71
|
-
base = this.
|
|
124
|
+
base = this.root;
|
|
72
125
|
p = fill(op.path);
|
|
73
126
|
}
|
|
74
127
|
const qs = new URLSearchParams();
|
|
@@ -147,6 +200,9 @@ export class GenesisClient {
|
|
|
147
200
|
}
|
|
148
201
|
/** Raw Response. Completed Hives guest responses retain their actual status, including non-2xx. */
|
|
149
202
|
async raw(operationId, input = {}, init = {}) {
|
|
203
|
+
// A Genesis pr_… id where Hives wants its own prj_… id would only be a remote 404: refuse it before any request.
|
|
204
|
+
if (isGenesisProjectId(input.project_id) && isHivesProjectOperation(operationTable[operationId]))
|
|
205
|
+
throw hivesProjectIdRefusal(input.project_id, operationId);
|
|
150
206
|
if (!isIamDirectoryOperation(operationId))
|
|
151
207
|
return this.dispatch(operationId, input, init);
|
|
152
208
|
// The gateway binds the organization context itself. A refusal is only explained here; the client never changes a session or its context.
|
|
@@ -189,7 +245,7 @@ export class GenesisClient {
|
|
|
189
245
|
const url = this.url(operationId, input);
|
|
190
246
|
const headers = {
|
|
191
247
|
accept: 'application/json',
|
|
192
|
-
'user-agent': this.opts.userAgent ?? '@onesi/genesis/0.2.
|
|
248
|
+
'user-agent': this.opts.userAgent ?? '@onesi/genesis/0.2.5',
|
|
193
249
|
...this.opts.headers, ...init.headers,
|
|
194
250
|
};
|
|
195
251
|
if (op.service === 'blinds' && !['GET', 'HEAD', 'OPTIONS'].includes(op.method))
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/** An error body is read to at most this many bytes, before any parsing or redaction; the rest is dropped. */
|
|
2
|
+
export declare const ERROR_BODY_READ_LIMIT: number;
|
|
3
|
+
/** Read at most `limit` bytes of a response body as text, cancelling the stream once the limit is reached. */
|
|
4
|
+
export declare function readCappedText(res: Response, limit?: number): Promise<string>;
|
|
1
5
|
/** Normalized error for every Genesis service (gateway `{error:{type,message}}`, IAM `{error,error_description}`, Relays `{error}`, Foundry/OpenAI shape). */
|
|
2
6
|
export declare class GenesisError extends Error {
|
|
3
7
|
readonly status: number;
|
package/dist/errors.js
CHANGED
|
@@ -1,4 +1,33 @@
|
|
|
1
1
|
import { authorizationCode, redactErrorBody } from './redact.js';
|
|
2
|
+
/** An error body is read to at most this many bytes, before any parsing or redaction; the rest is dropped. */
|
|
3
|
+
export const ERROR_BODY_READ_LIMIT = 64 * 1024;
|
|
4
|
+
/** Read at most `limit` bytes of a response body as text, cancelling the stream once the limit is reached. */
|
|
5
|
+
export async function readCappedText(res, limit = ERROR_BODY_READ_LIMIT) {
|
|
6
|
+
const reader = res.body?.getReader();
|
|
7
|
+
if (!reader)
|
|
8
|
+
return (await res.text()).slice(0, limit);
|
|
9
|
+
const decoder = new TextDecoder();
|
|
10
|
+
let text = '';
|
|
11
|
+
let bytes = 0;
|
|
12
|
+
try {
|
|
13
|
+
for (;;) {
|
|
14
|
+
const { done, value } = await reader.read();
|
|
15
|
+
if (done)
|
|
16
|
+
break;
|
|
17
|
+
const room = limit - bytes;
|
|
18
|
+
text += decoder.decode(value.byteLength > room ? value.subarray(0, room) : value, { stream: true });
|
|
19
|
+
bytes += Math.min(value.byteLength, room);
|
|
20
|
+
if (bytes >= limit) {
|
|
21
|
+
void reader.cancel().catch(() => { });
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return text + decoder.decode();
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
reader.releaseLock?.();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
2
31
|
/** Normalized error for every Genesis service (gateway `{error:{type,message}}`, IAM `{error,error_description}`, Relays `{error}`, Foundry/OpenAI shape). */
|
|
3
32
|
export class GenesisError extends Error {
|
|
4
33
|
status;
|
|
@@ -16,7 +45,7 @@ export class GenesisError extends Error {
|
|
|
16
45
|
this.body = init.body;
|
|
17
46
|
}
|
|
18
47
|
static async fromResponse(res, operationId) {
|
|
19
|
-
const text = await res
|
|
48
|
+
const text = await readCappedText(res).catch(() => '');
|
|
20
49
|
let body = text;
|
|
21
50
|
try {
|
|
22
51
|
body = text ? JSON.parse(text) : undefined;
|
package/dist/fleets.d.ts
CHANGED
|
@@ -141,7 +141,10 @@ export declare function fleetsModule(client: GenesisClient): {
|
|
|
141
141
|
model: string;
|
|
142
142
|
updated_at?: number;
|
|
143
143
|
}>;
|
|
144
|
-
|
|
144
|
+
/** `model` (optional) must be a model Foundry lists for the key; omit it for the Fleets default. */
|
|
145
|
+
set: (api_key: string, options?: {
|
|
146
|
+
model?: string;
|
|
147
|
+
}) => Promise<{
|
|
145
148
|
connected: boolean;
|
|
146
149
|
model: string;
|
|
147
150
|
updated_at?: number;
|
package/dist/fleets.js
CHANGED
|
@@ -26,6 +26,11 @@ function validateKey(apiKey) {
|
|
|
26
26
|
throw invalid('fleets_setConnection', 'Foundry key must contain 1–4096 UTF-8 bytes without CR, LF or NUL.');
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
function validateModel(model) {
|
|
30
|
+
if (typeof model !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(model)) {
|
|
31
|
+
throw invalid('fleets_setConnection', 'Model must be a Foundry model id of 1–128 characters (letters, digits, . _ : / -).');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
29
34
|
const instanceId = (value, operation) => {
|
|
30
35
|
if (typeof value !== 'string' || !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)) {
|
|
31
36
|
throw invalid(operation, 'Invalid Fleets instance ID.');
|
|
@@ -160,9 +165,12 @@ export function fleetsModule(client) {
|
|
|
160
165
|
},
|
|
161
166
|
connection: {
|
|
162
167
|
get: () => client.request('fleets_getConnection', {}),
|
|
163
|
-
|
|
168
|
+
/** `model` (optional) must be a model Foundry lists for the key; omit it for the Fleets default. */
|
|
169
|
+
set: async (api_key, options = {}) => {
|
|
164
170
|
validateKey(api_key);
|
|
165
|
-
|
|
171
|
+
if (options.model !== undefined)
|
|
172
|
+
validateModel(options.model);
|
|
173
|
+
return client.request('fleets_setConnection', { body: { api_key, ...(options.model !== undefined ? { model: options.model } : {}) } });
|
|
166
174
|
},
|
|
167
175
|
delete: () => client.request('fleets_deleteConnection', {}),
|
|
168
176
|
},
|
package/dist/foundry.d.ts
CHANGED
|
@@ -266,7 +266,8 @@ export declare function foundryModule(client: GenesisClient): {
|
|
|
266
266
|
[key: string]: unknown;
|
|
267
267
|
}>;
|
|
268
268
|
};
|
|
269
|
-
/** OpenRouter-style generation lookup by request id
|
|
269
|
+
/** OpenRouter-style generation lookup by request id, retained content and feedback for your own generations (not billed).
|
|
270
|
+
* Feedback requires an idempotency key (8-128 characters of A-Z a-z 0-9 _ . : -); reuse it to retry. */
|
|
270
271
|
generation: {
|
|
271
272
|
get: (id: string) => Promise<{
|
|
272
273
|
data: {
|
|
@@ -286,7 +287,7 @@ export declare function foundryModule(client: GenesisClient): {
|
|
|
286
287
|
content: (id: string) => Promise<{
|
|
287
288
|
[key: string]: unknown;
|
|
288
289
|
}>;
|
|
289
|
-
feedback: (body: In<"foundry_submitGenerationFeedback">["body"]) => Promise<{
|
|
290
|
+
feedback: (body: In<"foundry_submitGenerationFeedback">["body"], idempotencyKey: string) => Promise<{
|
|
290
291
|
[key: string]: unknown;
|
|
291
292
|
}>;
|
|
292
293
|
};
|
package/dist/foundry.js
CHANGED
|
@@ -85,11 +85,12 @@ export function foundryModule(client) {
|
|
|
85
85
|
status: (workload_id) => client.request('foundry_getWorkloadStatus', { workload_id }),
|
|
86
86
|
terminate: (workload_id) => client.request('foundry_terminateWorkload', { workload_id }),
|
|
87
87
|
},
|
|
88
|
-
/** OpenRouter-style generation lookup by request id
|
|
88
|
+
/** OpenRouter-style generation lookup by request id, retained content and feedback for your own generations (not billed).
|
|
89
|
+
* Feedback requires an idempotency key (8-128 characters of A-Z a-z 0-9 _ . : -); reuse it to retry. */
|
|
89
90
|
generation: {
|
|
90
91
|
get: (id) => client.request('foundry_getGeneration', { id }),
|
|
91
92
|
content: (id) => client.request('foundry_getGenerationContent', { id }),
|
|
92
|
-
feedback: (body) => client.request('foundry_submitGenerationFeedback', { body }),
|
|
93
|
+
feedback: async (body, idempotencyKey) => client.request('foundry_submitGenerationFeedback', { body }, { headers: idempotencyHeaders('foundry_submitGenerationFeedback', idempotencyKey) }),
|
|
93
94
|
},
|
|
94
95
|
batches: {
|
|
95
96
|
create: (body) => client.request('foundry_createBatch', { body }),
|