@lifesi/genesis 0.0.0-stage → 0.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.
package/dist/client.js ADDED
@@ -0,0 +1,94 @@
1
+ import { operationTable, services } from './generated/operations.js';
2
+ import { GenesisError } from './errors.js';
3
+ export const DEFAULT_BASE_URL = 'https://console.life.one/api';
4
+ const env = globalThis.process?.env ?? {};
5
+ export class GenesisClient {
6
+ baseUrl;
7
+ routing;
8
+ opts;
9
+ fetchImpl;
10
+ constructor(opts = {}) {
11
+ this.opts = opts;
12
+ this.baseUrl = (opts.baseUrl ?? env.GENESIS_BASE_URL ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
13
+ this.routing = opts.routing ?? (env.GENESIS_ROUTING === 'direct' ? 'direct' : 'gateway');
14
+ const f = opts.fetch ?? globalThis.fetch;
15
+ if (!f)
16
+ throw new Error('No fetch available; pass options.fetch');
17
+ this.fetchImpl = f.bind(globalThis);
18
+ }
19
+ static operations = operationTable;
20
+ static services = services;
21
+ /** Resolve the credential: explicit apiKey > token > env GENESIS_API_KEY > GENESIS_TOKEN > provider. */
22
+ async credential() {
23
+ return this.opts.apiKey ?? this.opts.token ?? env.GENESIS_API_KEY ?? env.GENESIS_TOKEN ?? (await this.opts.credentials?.());
24
+ }
25
+ /** Build the URL for an operation (exposed for tests and `--dry-run`). */
26
+ url(operationId, input = {}) {
27
+ const op = operationTable[operationId];
28
+ if (!op)
29
+ throw new Error(`Unknown Genesis operation: ${operationId}`);
30
+ const fill = (tpl) => tpl.replace(/\{([^}]+)\}/g, (_, k) => {
31
+ const v = input[k];
32
+ if (v === undefined || v === null || v === '')
33
+ throw new GenesisError({ status: 0, type: 'invalid_request', message: `Missing required path parameter "${k}" for ${operationId}`, operationId });
34
+ return encodeURIComponent(String(v));
35
+ });
36
+ let base;
37
+ let p;
38
+ if (this.routing === 'direct') {
39
+ base = (this.opts.upstreams?.[op.service] ?? op.upstream.base).replace(/\/+$/, '');
40
+ p = fill(op.upstream.path);
41
+ }
42
+ else {
43
+ base = this.baseUrl;
44
+ p = fill(op.path);
45
+ }
46
+ const qs = new URLSearchParams();
47
+ for (const q of op.queryParams) {
48
+ const v = input[q];
49
+ if (v === undefined || v === null)
50
+ continue;
51
+ if (Array.isArray(v))
52
+ v.forEach((x) => qs.append(q, String(x)));
53
+ else
54
+ qs.set(q, String(v));
55
+ }
56
+ const s = qs.toString();
57
+ return `${base}${p}${s ? `?${s}` : ''}`;
58
+ }
59
+ /** Call any Genesis operation by id, fully typed from the merged spec. */
60
+ async request(operationId, input, init = {}) {
61
+ const res = await this.raw(operationId, (input ?? {}), init);
62
+ if (res.status === 204)
63
+ return undefined;
64
+ const ct = res.headers.get('content-type') ?? '';
65
+ return (ct.includes('json') ? await res.json() : await res.text());
66
+ }
67
+ /** Like request() but returns the raw Response (for streaming). Throws GenesisError on non-2xx. */
68
+ async raw(operationId, input = {}, init = {}) {
69
+ const op = operationTable[operationId];
70
+ const url = this.url(operationId, input);
71
+ const headers = {
72
+ accept: 'application/json',
73
+ 'user-agent': this.opts.userAgent ?? '@lifesi/genesis/0.1.0',
74
+ ...this.opts.headers, ...init.headers,
75
+ };
76
+ if (!op.public) {
77
+ const cred = await this.credential();
78
+ // Direct mode: let the upstream decide (some, like Foundry's model list, are public).
79
+ if (!cred && this.routing !== 'direct')
80
+ throw new GenesisError({ status: 401, type: 'unauthorized', message: 'No Genesis credential. Run `genesis login`, or set GENESIS_API_KEY.', operationId });
81
+ if (cred)
82
+ headers.authorization = `Bearer ${cred}`;
83
+ }
84
+ let body;
85
+ if (op.hasBody && input.body !== undefined) {
86
+ headers['content-type'] = 'application/json';
87
+ body = JSON.stringify(input.body);
88
+ }
89
+ const res = await this.fetchImpl(url, { method: op.method, headers, body, signal: init.signal });
90
+ if (!res.ok)
91
+ throw await GenesisError.fromResponse(res, operationId);
92
+ return res;
93
+ }
94
+ }
@@ -0,0 +1,51 @@
1
+ import type { GenesisClient } from './client.js';
2
+ import type { OperationInput } from './types.js';
3
+ import type { components } from './generated/schema.js';
4
+ export type DialsScope = components['schemas']['Dials.Scope'];
5
+ export type DialsCallControl = components['schemas']['Dials.CallControl'];
6
+ /** The public call contract requires customer-selected behavior for every caller. */
7
+ export type DialsOutboundCall = components['schemas']['Dials.OutboundCall'];
8
+ export type DialsEmergencyAddress = components['schemas']['Dials.RegisterEmergencyAddress'];
9
+ /** Scope is selection only; the Dials backend verifies its org against signed identity. */
10
+ export declare function dialsModule(client: GenesisClient): {
11
+ numbers: {
12
+ list: (scope: DialsScope) => Promise<{
13
+ ok: boolean;
14
+ numbers: components["schemas"]["Dials.Number"][];
15
+ }>;
16
+ search: (scope: DialsScope, query?: {
17
+ areaCode?: string;
18
+ limit?: number;
19
+ }) => Promise<{
20
+ [key: string]: unknown;
21
+ }>;
22
+ provision: (body: OperationInput<"dials_provisionNumber">["body"]) => Promise<{
23
+ [key: string]: unknown;
24
+ }>;
25
+ assign: (body: OperationInput<"dials_assignNumber">["body"]) => Promise<{
26
+ [key: string]: unknown;
27
+ }>;
28
+ release: (body: OperationInput<"dials_releaseNumber">["body"]) => Promise<{
29
+ [key: string]: unknown;
30
+ }>;
31
+ registerEmergencyAddress: (body: DialsEmergencyAddress) => Promise<{
32
+ ok: boolean;
33
+ number: components["schemas"]["Dials.Number"];
34
+ address: components["schemas"]["Dials.EmergencyAddress"];
35
+ auditId: string;
36
+ next: string;
37
+ }>;
38
+ };
39
+ calls: {
40
+ list: (scope: DialsScope) => Promise<{
41
+ ok: boolean;
42
+ calls: components["schemas"]["Dials.Call"][];
43
+ }>;
44
+ validate: (body: DialsOutboundCall) => Promise<{
45
+ [key: string]: unknown;
46
+ }>;
47
+ create: (body: DialsOutboundCall) => Promise<{
48
+ [key: string]: unknown;
49
+ }>;
50
+ };
51
+ };
package/dist/dials.js ADDED
@@ -0,0 +1,18 @@
1
+ /** Scope is selection only; the Dials backend verifies its org against signed identity. */
2
+ export function dialsModule(client) {
3
+ return {
4
+ numbers: {
5
+ list: (scope) => client.request('dials_listNumbers', scope),
6
+ search: (scope, query = {}) => client.request('dials_searchNumbers', { ...scope, ...query }),
7
+ provision: (body) => client.request('dials_provisionNumber', { body }),
8
+ assign: (body) => client.request('dials_assignNumber', { body }),
9
+ release: (body) => client.request('dials_releaseNumber', { body }),
10
+ registerEmergencyAddress: (body) => client.request('dials_registerEmergencyAddress', { body }),
11
+ },
12
+ calls: {
13
+ list: (scope) => client.request('dials_listCalls', scope),
14
+ validate: (body) => client.request('dials_validateOutboundCall', { body }),
15
+ create: (body) => client.request('dials_createOutboundCall', { body }),
16
+ },
17
+ };
18
+ }
@@ -0,0 +1,19 @@
1
+ /** Normalized error for every Genesis service (gateway `{error:{type,message}}`, IAM `{error,error_description}`, Relays `{error}`, Foundry/OpenAI shape). */
2
+ export declare class GenesisError extends Error {
3
+ readonly status: number;
4
+ readonly type: string;
5
+ readonly code?: string;
6
+ readonly requestId?: string;
7
+ readonly operationId?: string;
8
+ readonly body: unknown;
9
+ constructor(init: {
10
+ status: number;
11
+ type: string;
12
+ message: string;
13
+ code?: string;
14
+ requestId?: string;
15
+ operationId?: string;
16
+ body?: unknown;
17
+ });
18
+ static fromResponse(res: Response, operationId?: string): Promise<GenesisError>;
19
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,35 @@
1
+ /** Normalized error for every Genesis service (gateway `{error:{type,message}}`, IAM `{error,error_description}`, Relays `{error}`, Foundry/OpenAI shape). */
2
+ export class GenesisError extends Error {
3
+ status;
4
+ type;
5
+ code;
6
+ requestId;
7
+ operationId;
8
+ body;
9
+ constructor(init) {
10
+ super(init.message);
11
+ this.name = 'GenesisError';
12
+ Object.assign(this, init);
13
+ this.status = init.status;
14
+ this.type = init.type;
15
+ this.body = init.body;
16
+ }
17
+ static async fromResponse(res, operationId) {
18
+ const text = await res.text().catch(() => '');
19
+ let body = text;
20
+ try {
21
+ body = text ? JSON.parse(text) : undefined;
22
+ }
23
+ catch { /* keep text */ }
24
+ const e = body?.error;
25
+ const type = typeof e === 'object' && e ? (e.type ?? e.code ?? 'error') : typeof e === 'string' ? e : statusType(res.status);
26
+ const message = (typeof e === 'object' && e?.message) || body?.error_description || body?.message ||
27
+ (typeof e === 'string' && e) || (typeof body === 'string' && body) || `${res.status} ${res.statusText}`;
28
+ return new GenesisError({
29
+ status: res.status, type, message, code: typeof e === 'object' ? e?.code : undefined,
30
+ requestId: res.headers.get('x-request-id') ?? (typeof e === 'object' ? e?.request_id : undefined) ?? body?.request_id,
31
+ operationId, body,
32
+ });
33
+ }
34
+ }
35
+ const statusType = (s) => s === 401 ? 'unauthorized' : s === 403 ? 'forbidden' : s === 404 ? 'not_found' : s === 429 ? 'rate_limited' : s >= 500 ? 'upstream_error' : 'error';
@@ -0,0 +1,71 @@
1
+ import type { GenesisClient } from './client.js';
2
+ import type { OperationInput } from './types.js';
3
+ type TaskInput = NonNullable<OperationInput<'fleets_submitTask'>['body']>;
4
+ /** Managed agent tasks, always scoped to the credential's verified organization. */
5
+ export declare function fleetsModule(client: GenesisClient): {
6
+ connection: {
7
+ get: () => Promise<{
8
+ connected: boolean;
9
+ model: string;
10
+ updated_at?: number;
11
+ }>;
12
+ set: (api_key: string) => Promise<{
13
+ connected: boolean;
14
+ model: string;
15
+ updated_at?: number;
16
+ }>;
17
+ delete: () => Promise<{
18
+ connected: boolean;
19
+ model: string;
20
+ updated_at?: number;
21
+ }>;
22
+ };
23
+ tasks: {
24
+ list: (query?: OperationInput<"fleets_listTasks">) => Promise<{
25
+ tasks: import("./index.js").components["schemas"]["Fleets.Summary"][];
26
+ next_before: string | null;
27
+ }>;
28
+ submit: (body: TaskInput) => Promise<{
29
+ task_id: string;
30
+ status: import("./index.js").components["schemas"]["Fleets.Status"];
31
+ submitted_at: string;
32
+ started_at?: string | null;
33
+ completed_at?: string | null;
34
+ }>;
35
+ get: (task_id: string) => Promise<{
36
+ task_id: string;
37
+ status: import("./index.js").components["schemas"]["Fleets.Status"];
38
+ submitted_at: string;
39
+ started_at?: string | null;
40
+ completed_at?: string | null;
41
+ }>;
42
+ cancel: (task_id: string) => Promise<{
43
+ task_id: string;
44
+ status: import("./index.js").components["schemas"]["Fleets.Status"];
45
+ }>;
46
+ receipt: (task_id: string) => Promise<{
47
+ task_id: string;
48
+ status: import("./index.js").components["schemas"]["Fleets.Status"];
49
+ submitted_at: string;
50
+ started_at?: string | null;
51
+ completed_at?: string | null;
52
+ } & {
53
+ tenant: string;
54
+ principal: string;
55
+ idempotency_key: string | null;
56
+ request_fingerprint: string;
57
+ timeout_seconds: number;
58
+ exit_code: number | null;
59
+ exit_signal: number | null;
60
+ cleanup_result: {
61
+ process_group_gone?: boolean;
62
+ workdir_removed?: boolean;
63
+ sigkill_sent?: boolean;
64
+ } | null;
65
+ henry_receipt?: {
66
+ [key: string]: unknown;
67
+ };
68
+ }>;
69
+ };
70
+ };
71
+ export {};
package/dist/fleets.js ADDED
@@ -0,0 +1,44 @@
1
+ import { GenesisError } from './errors.js';
2
+ const byteLength = (value) => new TextEncoder().encode(value).byteLength;
3
+ const invalid = (operationId, message) => new GenesisError({
4
+ status: 0, type: 'invalid_request', operationId, message,
5
+ });
6
+ function validateTask(body) {
7
+ if (typeof body?.input !== 'string' || !body.input || byteLength(body.input) > 65536 || body.input.includes('\0')) {
8
+ throw invalid('fleets_submitTask', 'Task input must contain 1–65536 UTF-8 bytes without NUL.');
9
+ }
10
+ if (body.timeout_seconds !== undefined && (!Number.isInteger(body.timeout_seconds) || body.timeout_seconds < 1 || body.timeout_seconds > 3600)) {
11
+ throw invalid('fleets_submitTask', 'Timeout must be 1–3600 seconds.');
12
+ }
13
+ if (body.idempotency_key !== undefined && (typeof body.idempotency_key !== 'string' || !/^[!-~]{1,128}$/.test(body.idempotency_key))) {
14
+ throw invalid('fleets_submitTask', 'Idempotency key must contain 1–128 visible ASCII characters.');
15
+ }
16
+ }
17
+ function validateKey(apiKey) {
18
+ if (typeof apiKey !== 'string' || !apiKey || byteLength(apiKey) > 4096 || /[\r\n\0]/.test(apiKey)) {
19
+ throw invalid('fleets_setConnection', 'Foundry key must contain 1–4096 UTF-8 bytes without CR, LF or NUL.');
20
+ }
21
+ }
22
+ /** Managed agent tasks, always scoped to the credential's verified organization. */
23
+ export function fleetsModule(client) {
24
+ return {
25
+ connection: {
26
+ get: () => client.request('fleets_getConnection', {}),
27
+ set: async (api_key) => {
28
+ validateKey(api_key);
29
+ return client.request('fleets_setConnection', { body: { api_key } });
30
+ },
31
+ delete: () => client.request('fleets_deleteConnection', {}),
32
+ },
33
+ tasks: {
34
+ list: (query = {}) => client.request('fleets_listTasks', query),
35
+ submit: async (body) => {
36
+ validateTask(body);
37
+ return client.request('fleets_submitTask', { body });
38
+ },
39
+ get: (task_id) => client.request('fleets_getTask', { task_id }),
40
+ cancel: (task_id) => client.request('fleets_cancelTask', { task_id }),
41
+ receipt: (task_id) => client.request('fleets_getTaskReceipt', { task_id }),
42
+ },
43
+ };
44
+ }