@lifesi/genesis 0.1.1 → 0.1.3

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.
Files changed (44) hide show
  1. package/dist/beacons.d.ts +46 -0
  2. package/dist/beacons.js +20 -0
  3. package/dist/client.d.ts +7669 -1635
  4. package/dist/client.js +54 -5
  5. package/dist/dials.d.ts +171 -1
  6. package/dist/dials.js +40 -0
  7. package/dist/fleets.d.ts +109 -1
  8. package/dist/fleets.js +89 -1
  9. package/dist/flocks.d.ts +73 -0
  10. package/dist/flocks.js +22 -0
  11. package/dist/generated/operations.d.ts +7669 -1635
  12. package/dist/generated/operations.js +9193 -1691
  13. package/dist/generated/schema.d.ts +16979 -5698
  14. package/dist/genesis.d.ts +795 -13
  15. package/dist/genesis.js +21 -0
  16. package/dist/genesis.openapi.json +23483 -7599
  17. package/dist/henry.d.ts +7 -0
  18. package/dist/henry.js +23 -0
  19. package/dist/hives-cells.d.ts +127 -0
  20. package/dist/hives-cells.js +151 -0
  21. package/dist/hives-environment.d.ts +28 -0
  22. package/dist/hives-environment.js +58 -0
  23. package/dist/hives-functions.d.ts +1 -1
  24. package/dist/hives-functions.js +3 -1
  25. package/dist/hives-marketplace.d.ts +128 -0
  26. package/dist/hives-marketplace.js +187 -0
  27. package/dist/hives-node.js +2 -0
  28. package/dist/hives-operations.d.ts +304 -0
  29. package/dist/hives-operations.js +39 -0
  30. package/dist/hives-queues.d.ts +121 -0
  31. package/dist/hives-queues.js +106 -0
  32. package/dist/hives-resources-transport.d.ts +18 -0
  33. package/dist/hives-resources-transport.js +165 -0
  34. package/dist/hives-runtimes.d.ts +34 -0
  35. package/dist/hives-runtimes.js +32 -0
  36. package/dist/hives-volumes.d.ts +64 -0
  37. package/dist/hives-volumes.js +117 -0
  38. package/dist/index.d.ts +8 -1
  39. package/dist/index.js +3 -0
  40. package/dist/runner-grant.d.ts +10 -0
  41. package/dist/runner-grant.js +133 -0
  42. package/dist/runner.d.ts +16 -0
  43. package/dist/runner.js +4 -0
  44. package/package.json +6 -1
package/dist/client.js CHANGED
@@ -1,6 +1,9 @@
1
+ import { isHivesObjectUpload, hivesObjectBody, hivesContentType, isHivesResourceOperation, resourceError } from './hives-resources-transport.js';
1
2
  import { operationTable, services } from './generated/operations.js';
2
3
  import { GenesisError } from './errors.js';
3
4
  import { validateHivesInvocation, hivesFunctionResponseHeaders, hivesFunctionMetadata } from './hives-functions.js';
5
+ import { validateHivesEnvironment, hivesEnvironmentMetadata, hivesEnvironmentReveal } from './hives-environment.js';
6
+ import { RUNNER_SELF_GRANT_OPERATION, snapshotRunnerSelfGrant, runnerGrantResponse, runnerGrantTransportError } from './runner-grant.js';
4
7
  export const DEFAULT_BASE_URL = 'https://console.life.one/api';
5
8
  const env = globalThis.process?.env ?? {};
6
9
  export class GenesisClient {
@@ -70,6 +73,8 @@ export class GenesisClient {
70
73
  value = ct.includes('json') ? await res.json() : await res.text();
71
74
  }
72
75
  catch (error) {
76
+ if (String(operationId).startsWith('hives_') && String(operationId).endsWith('_project_environment'))
77
+ throw new Error('Hives returned invalid environment data.');
73
78
  if (String(operationId).startsWith('hives_') && /_functions?$/.test(operationId))
74
79
  throw new Error('Hives returned invalid function metadata.');
75
80
  throw error;
@@ -81,21 +86,48 @@ export class GenesisClient {
81
86
  return { data: value.data.map((row) => hivesFunctionMetadata(row, input?.project_id)), pagination };
82
87
  }
83
88
  if (['hives_get_function', 'hives_create_function', 'hives_update_function'].includes(operationId)) {
84
- return hivesFunctionMetadata(value, input?.project_id);
89
+ return hivesFunctionMetadata(value, input?.project_id, operationId === 'hives_create_function' ? undefined : input?.function_id);
90
+ }
91
+ if (operationId === 'hives_list_project_environment' || operationId === 'hives_update_project_environment') {
92
+ return hivesEnvironmentMetadata(value, input?.project_id, operationId === 'hives_update_project_environment');
93
+ }
94
+ if (operationId === 'hives_reveal_project_environment') {
95
+ return hivesEnvironmentReveal(value, input?.key);
85
96
  }
86
97
  return value;
87
98
  }
88
99
  /** Raw Response. Completed Hives guest responses retain their actual status, including non-2xx. */
89
100
  async raw(operationId, input = {}, init = {}) {
101
+ const runnerGrant = operationId === RUNNER_SELF_GRANT_OPERATION ? snapshotRunnerSelfGrant(input, this.routing) : undefined;
102
+ if (runnerGrant)
103
+ input = { body: runnerGrant };
90
104
  const op = operationTable[operationId];
91
105
  if (op.rawResponse && op.service === 'hives')
92
106
  validateHivesInvocation(input.body);
107
+ const flocksNative = ['flocks_exchangeJoinToken', 'flocks_activateNode', 'flocks_rotateNodeCredential'].includes(operationId);
108
+ if (flocksNative && this.routing === 'direct')
109
+ throw new GenesisError({ status: 0, type: 'invalid_request', message: 'Native Flocks credentials are brokered only through the Genesis gateway.', operationId });
110
+ if (operationId === 'hives_update_project_environment')
111
+ input = { ...input, body: validateHivesEnvironment(input.body) };
112
+ const objectBody = isHivesObjectUpload(operationId) ? await hivesObjectBody(input.body) : undefined;
93
113
  const url = this.url(operationId, input);
94
114
  const headers = {
95
115
  accept: 'application/json',
96
116
  'user-agent': this.opts.userAgent ?? '@lifesi/genesis/0.1.0',
97
117
  ...this.opts.headers, ...init.headers,
98
118
  };
119
+ if (runnerGrant) {
120
+ for (const key of Object.keys(headers))
121
+ if (!['accept', 'user-agent'].includes(key.toLowerCase()))
122
+ delete headers[key];
123
+ headers['x-request-id'] = runnerGrant.requestId;
124
+ headers['idempotency-key'] = runnerGrant.operationId;
125
+ }
126
+ if (operationId === 'flocks_exchangeJoinToken') {
127
+ for (const key of Object.keys(headers))
128
+ if (!['accept', 'user-agent'].includes(key.toLowerCase()))
129
+ delete headers[key];
130
+ }
99
131
  if (!op.public) {
100
132
  const cred = await this.credential();
101
133
  // Direct mode: let the upstream decide (some, like Foundry's model list, are public).
@@ -104,13 +136,21 @@ export class GenesisClient {
104
136
  if (cred)
105
137
  headers.authorization = `Bearer ${cred}`;
106
138
  }
139
+ // A timed-out credential provider must not dispatch a later resource mutation.
140
+ if (init.signal?.aborted && isHivesResourceOperation(operationId))
141
+ throw resourceError(0, operationId);
107
142
  let body;
108
143
  if (op.hasBody && input.body !== undefined) {
109
144
  // Fetch owns multipart boundary generation. Never serialize a File/Blob as JSON.
110
145
  for (const key of Object.keys(headers))
111
146
  if (key.toLowerCase() === 'content-type')
112
147
  delete headers[key];
113
- if (op.requestContentType === 'multipart/form-data') {
148
+ if (objectBody !== undefined && op.requestContentType === 'application/octet-stream') {
149
+ headers['content-type'] = hivesContentType(input.content_type);
150
+ headers['content-length'] = String(objectBody.byteLength);
151
+ body = objectBody;
152
+ }
153
+ else if (op.requestContentType === 'multipart/form-data') {
114
154
  if (!(input.body instanceof FormData))
115
155
  throw new GenesisError({ status: 0, type: 'invalid_request', operationId,
116
156
  message: 'This operation requires a FormData body containing a real binary file.' });
@@ -124,14 +164,17 @@ export class GenesisClient {
124
164
  body = JSON.stringify(input.body);
125
165
  }
126
166
  }
127
- const secretOperation = op.service === 'hives' && /_secrets?$/.test(operationId);
167
+ const environmentOperation = op.service === 'hives' && operationId.endsWith('_project_environment');
168
+ const secretOperation = op.service === 'hives' && (/_secrets?$/.test(operationId) || environmentOperation);
128
169
  const functionOperation = op.service === 'hives' && (op.rawResponse || /_function(?:s|_async)?$/.test(operationId));
129
170
  let res;
130
171
  try {
131
172
  res = await this.fetchImpl(url, { method: op.method, headers, body, signal: init.signal,
132
- ...(op.service === 'hives' ? { cache: 'no-store', redirect: op.rawResponse ? 'manual' : 'error' } : {}) });
173
+ ...(op.service === 'hives' || op.service === 'flocks' || runnerGrant ? { cache: 'no-store', redirect: op.rawResponse && !runnerGrant ? 'manual' : 'error' } : {}) });
133
174
  }
134
175
  catch (error) {
176
+ if (runnerGrant)
177
+ throw runnerGrantTransportError();
135
178
  if (functionOperation)
136
179
  throw new GenesisError({ status: 0, type: 'transport_error', operationId,
137
180
  message: 'Hives function transport failed. No redirects are followed; check the service address and retry.' });
@@ -140,6 +183,8 @@ export class GenesisClient {
140
183
  throw new GenesisError({ status: 0, type: 'transport_error', operationId,
141
184
  message: 'Hives secret transport failed. Redirects are refused; check the service address and retry.' });
142
185
  }
186
+ if (runnerGrant)
187
+ return runnerGrantResponse(res, runnerGrant);
143
188
  if (op.rawResponse && /^exec_[a-f0-9]{32}$/.test(res.headers.get('x-execution-id') ?? '')) {
144
189
  return new Response(res.body, { status: res.status, statusText: res.statusText, headers: hivesFunctionResponseHeaders(res.headers) });
145
190
  }
@@ -170,7 +215,11 @@ export class GenesisClient {
170
215
  : `Hives secret operation failed (${res.status}). Refresh metadata and retry.`;
171
216
  // Validation/provider bodies may reflect submitted plaintext. Never retain or log them.
172
217
  throw new GenesisError({ status: res.status, type: `http_${res.status}`, message, operationId,
173
- requestId: res.headers.get('x-request-id') ?? undefined });
218
+ requestId: environmentOperation ? undefined : res.headers.get('x-request-id') ?? undefined });
219
+ }
220
+ if (!res.ok && isHivesResourceOperation(operationId)) {
221
+ await res.body?.cancel().catch(() => { });
222
+ throw resourceError(res.status, operationId);
174
223
  }
175
224
  if (!res.ok)
176
225
  throw await GenesisError.fromResponse(res, operationId);
package/dist/dials.d.ts CHANGED
@@ -5,9 +5,151 @@ export type DialsScope = components['schemas']['Dials.Scope'];
5
5
  export type DialsCallControl = components['schemas']['Dials.CallControl'];
6
6
  /** The public call contract requires customer-selected behavior for every caller. */
7
7
  export type DialsOutboundCall = components['schemas']['Dials.OutboundCall'];
8
+ export type DialsOutboundCallReceipt = components['schemas']['Dials.OutboundCallReceipt'];
9
+ export type DialsRecoveredOutboundCallReceipt = components['schemas']['Dials.RecoveredOutboundCallReceipt'];
8
10
  export type DialsEmergencyAddress = components['schemas']['Dials.RegisterEmergencyAddress'];
11
+ export type DialsCallListItem = components['schemas']['Dials.CallListItem'];
12
+ export type DialsCallDispatchState = components['schemas']['Dials.CallDispatchState'];
13
+ export type DialsMessage = components['schemas']['Dials.MessageEvent'];
14
+ export type DialsMessageTemplate = components['schemas']['Dials.MessageTemplate'];
15
+ export type DialsMessagingBrand = components['schemas']['Dials.MessagingBrand'];
16
+ export type DialsMessagingCampaign = components['schemas']['Dials.MessagingCampaign'];
17
+ export type DialsWorkflowDocument = components['schemas']['Dials.WorkflowDocument'];
18
+ export type DialsRoutingAction = components['schemas']['Dials.RoutingAction'];
19
+ export type DialsRoutingRule = components['schemas']['Dials.RoutingRule'];
20
+ export type DialsRoutingRuleReceipt = components['schemas']['Dials.RoutingRuleReceipt'];
9
21
  /** Scope is selection only; the Dials backend verifies its org against signed identity. */
10
22
  export declare function dialsModule(client: GenesisClient): {
23
+ routing: {
24
+ list: (scope: DialsScope) => Promise<{
25
+ ok: true;
26
+ scope: components["schemas"]["Dials.Scope"];
27
+ rules: components["schemas"]["Dials.RoutingRule"][];
28
+ }>;
29
+ create: (body: OperationInput<"dials_createRoutingRule">["body"]) => Promise<{
30
+ ok: true;
31
+ rule: components["schemas"]["Dials.RoutingRule"];
32
+ auditId: string;
33
+ idempotentReplay: boolean;
34
+ }>;
35
+ disable: (ruleId: string, body: OperationInput<"dials_disableRoutingRule">["body"]) => Promise<{
36
+ ok: true;
37
+ rule: components["schemas"]["Dials.RoutingRule"];
38
+ auditId: string | null;
39
+ idempotentReplay: boolean;
40
+ }>;
41
+ };
42
+ people: {
43
+ list: (scope: DialsScope) => Promise<{
44
+ ok: boolean;
45
+ humans: components["schemas"]["Dials.HumanProfile"][];
46
+ }>;
47
+ create: (body: OperationInput<"dials_createHumanProfile">["body"]) => Promise<{
48
+ ok: boolean;
49
+ human: components["schemas"]["Dials.HumanProfile"];
50
+ auditId: string;
51
+ usageEventId: string;
52
+ next: string;
53
+ }>;
54
+ };
55
+ agents: {
56
+ list: (scope: DialsScope) => Promise<{
57
+ ok: boolean;
58
+ agents: components["schemas"]["Dials.AgentProfile"][];
59
+ }>;
60
+ create: (body: OperationInput<"dials_createAgent">["body"]) => Promise<{
61
+ ok: boolean;
62
+ agent: components["schemas"]["Dials.AgentProfile"];
63
+ auditId: string;
64
+ usageEventId: string;
65
+ next: string;
66
+ }>;
67
+ };
68
+ voice: {
69
+ list: (scope: DialsScope) => Promise<{
70
+ ok: boolean;
71
+ voiceProfiles: components["schemas"]["Dials.VoiceProfile"][];
72
+ }>;
73
+ create: (body: OperationInput<"dials_createVoiceProfile">["body"]) => Promise<{
74
+ ok: boolean;
75
+ voiceProfile: components["schemas"]["Dials.VoiceProfile"];
76
+ auditId: string;
77
+ usageEventId: string;
78
+ next: string;
79
+ }>;
80
+ };
81
+ messages: {
82
+ list: (scope: DialsScope) => Promise<{
83
+ ok: boolean;
84
+ messages: components["schemas"]["Dials.MessageEvent"][];
85
+ }>;
86
+ send: (body: OperationInput<"dials_createOutboundMessage">["body"]) => Promise<{
87
+ ok: boolean;
88
+ message: components["schemas"]["Dials.MessageEvent"];
89
+ auditId: string;
90
+ usageEventId: string;
91
+ next: string;
92
+ idempotentReplay: boolean;
93
+ }>;
94
+ templates: {
95
+ list: (scope: DialsScope) => Promise<{
96
+ ok: boolean;
97
+ templates: components["schemas"]["Dials.MessageTemplate"][];
98
+ }>;
99
+ create: (body: OperationInput<"dials_createMessageTemplate">["body"]) => Promise<{
100
+ ok: boolean;
101
+ template: components["schemas"]["Dials.MessageTemplate"];
102
+ auditId: string;
103
+ usageEventId: string;
104
+ next: string;
105
+ }>;
106
+ };
107
+ brands: {
108
+ list: (scope: DialsScope) => Promise<{
109
+ ok: boolean;
110
+ brands: components["schemas"]["Dials.MessagingBrand"][];
111
+ }>;
112
+ create: (body: OperationInput<"dials_createMessagingBrand">["body"]) => Promise<{
113
+ ok: boolean;
114
+ brand: components["schemas"]["Dials.MessagingBrand"];
115
+ auditId: string;
116
+ next: string;
117
+ }>;
118
+ };
119
+ campaigns: {
120
+ list: (scope: DialsScope) => Promise<{
121
+ ok: boolean;
122
+ campaigns: components["schemas"]["Dials.MessagingCampaign"][];
123
+ }>;
124
+ create: (body: OperationInput<"dials_createMessagingCampaign">["body"]) => Promise<{
125
+ ok: boolean;
126
+ campaign: components["schemas"]["Dials.MessagingCampaign"];
127
+ auditId: string;
128
+ next: string;
129
+ }>;
130
+ assignNumber: (campaignId: string, body: OperationInput<"dials_assignCampaignNumber">["body"]) => Promise<{
131
+ ok: boolean;
132
+ campaignId: string;
133
+ phoneNumberId: string;
134
+ auditId: string;
135
+ next: string;
136
+ }>;
137
+ };
138
+ };
139
+ workflows: {
140
+ list: (scope: DialsScope) => Promise<{
141
+ ok: boolean;
142
+ workflows: components["schemas"]["Dials.WorkflowDocument"][];
143
+ }>;
144
+ deploy: (body: OperationInput<"dials_deployWorkflow">["body"]) => Promise<{
145
+ ok: boolean;
146
+ workflow: components["schemas"]["Dials.WorkflowDocument"];
147
+ auditId: string;
148
+ usageEventId: string;
149
+ next: string;
150
+ route: components["schemas"]["Dials.RoutingRule"];
151
+ }>;
152
+ };
11
153
  overview: {
12
154
  summary: (scope: DialsScope) => Promise<{
13
155
  ok: boolean;
@@ -100,6 +242,13 @@ export declare function dialsModule(client: GenesisClient): {
100
242
  assign: (body: OperationInput<"dials_assignNumber">["body"]) => Promise<{
101
243
  [key: string]: unknown;
102
244
  }>;
245
+ delete: (input: OperationInput<"dials_deleteNumber">) => Promise<{
246
+ ok: boolean;
247
+ number: components["schemas"]["Dials.Number"];
248
+ auditId: string;
249
+ usageEventId: string;
250
+ next: string;
251
+ }>;
103
252
  release: (body: OperationInput<"dials_releaseNumber">["body"]) => Promise<{
104
253
  [key: string]: unknown;
105
254
  }>;
@@ -114,14 +263,35 @@ export declare function dialsModule(client: GenesisClient): {
114
263
  calls: {
115
264
  list: (scope: DialsScope) => Promise<{
116
265
  ok: boolean;
117
- calls: components["schemas"]["Dials.Call"][];
266
+ calls: components["schemas"]["Dials.CallListItem"][];
118
267
  }>;
119
268
  validate: (body: DialsOutboundCall) => Promise<{
120
269
  [key: string]: unknown;
121
270
  }>;
122
271
  create: (body: DialsOutboundCall) => Promise<{
272
+ ok: boolean;
273
+ call: components["schemas"]["Dials.CallSession"];
274
+ auditId: string;
275
+ dispatchJobId: string;
276
+ usageEventId: string;
277
+ next: string;
278
+ idempotentReplay: boolean;
279
+ } & {
123
280
  [key: string]: unknown;
124
281
  }>;
282
+ recover: (body: DialsOutboundCall) => Promise<{
283
+ ok: boolean;
284
+ call: components["schemas"]["Dials.CallSession"];
285
+ auditId: string;
286
+ dispatchJobId: string;
287
+ usageEventId: string;
288
+ next: string;
289
+ idempotentReplay: boolean;
290
+ } & {
291
+ [key: string]: unknown;
292
+ } & {
293
+ idempotentReplay: true;
294
+ }>;
125
295
  };
126
296
  };
127
297
  export type DialsEmergencyAddressFeedback = components['schemas']['Dials.EmergencyAddressFeedback'];
package/dist/dials.js CHANGED
@@ -2,6 +2,44 @@ import { GenesisError } from './errors.js';
2
2
  /** Scope is selection only; the Dials backend verifies its org against signed identity. */
3
3
  export function dialsModule(client) {
4
4
  return {
5
+ routing: {
6
+ list: (scope) => client.request('dials_listRoutingRules', scope),
7
+ create: (body) => client.request('dials_createRoutingRule', { body }),
8
+ disable: (ruleId, body) => client.request('dials_disableRoutingRule', { rule_id: ruleId, body }),
9
+ },
10
+ people: {
11
+ list: (scope) => client.request('dials_listHumanProfiles', scope),
12
+ create: (body) => client.request('dials_createHumanProfile', { body }),
13
+ },
14
+ agents: {
15
+ list: (scope) => client.request('dials_listAgents', scope),
16
+ create: (body) => client.request('dials_createAgent', { body }),
17
+ },
18
+ voice: {
19
+ list: (scope) => client.request('dials_listVoiceProfiles', scope),
20
+ create: (body) => client.request('dials_createVoiceProfile', { body }),
21
+ },
22
+ messages: {
23
+ list: (scope) => client.request('dials_listMessages', scope),
24
+ send: (body) => client.request('dials_createOutboundMessage', { body }),
25
+ templates: {
26
+ list: (scope) => client.request('dials_listMessageTemplates', scope),
27
+ create: (body) => client.request('dials_createMessageTemplate', { body }),
28
+ },
29
+ brands: {
30
+ list: (scope) => client.request('dials_listMessagingBrands', scope),
31
+ create: (body) => client.request('dials_createMessagingBrand', { body }),
32
+ },
33
+ campaigns: {
34
+ list: (scope) => client.request('dials_listMessagingCampaigns', scope),
35
+ create: (body) => client.request('dials_createMessagingCampaign', { body }),
36
+ assignNumber: (campaignId, body) => client.request('dials_assignCampaignNumber', { campaign_id: campaignId, body }),
37
+ },
38
+ },
39
+ workflows: {
40
+ list: (scope) => client.request('dials_listWorkflows', scope),
41
+ deploy: (body) => client.request('dials_deployWorkflow', { body }),
42
+ },
5
43
  overview: {
6
44
  summary: (scope) => client.request('dials_getConsoleSummary', scope),
7
45
  operatingSurface: (scope) => client.request('dials_getOperatingSurface', scope),
@@ -15,6 +53,7 @@ export function dialsModule(client) {
15
53
  search: (scope, query = {}) => client.request('dials_searchNumbers', { ...query, ...scope }),
16
54
  provision: (body) => client.request('dials_provisionNumber', { body }),
17
55
  assign: (body) => client.request('dials_assignNumber', { body }),
56
+ delete: (input) => client.request('dials_deleteNumber', input),
18
57
  release: (body) => client.request('dials_releaseNumber', { body }),
19
58
  registerEmergencyAddress: (body) => client.request('dials_registerEmergencyAddress', { body }),
20
59
  },
@@ -22,6 +61,7 @@ export function dialsModule(client) {
22
61
  list: (scope) => client.request('dials_listCalls', scope),
23
62
  validate: (body) => client.request('dials_validateOutboundCall', { body }),
24
63
  create: (body) => client.request('dials_createOutboundCall', { body }),
64
+ recover: (body) => client.request('dials_recoverOutboundCall', { body }),
25
65
  },
26
66
  };
27
67
  }
package/dist/fleets.d.ts CHANGED
@@ -1,8 +1,116 @@
1
1
  import type { GenesisClient } from './client.js';
2
2
  import type { OperationInput } from './types.js';
3
3
  type TaskInput = NonNullable<OperationInput<'fleets_submitTask'>['body']>;
4
- /** Managed agent tasks, always scoped to the credential's verified organization. */
4
+ type AgentLaunchInput = NonNullable<OperationInput<'fleets_launchAgent'>['body']>;
5
+ type AgentCommandInput = NonNullable<OperationInput<'fleets_commandAgent'>['body']>;
6
+ export interface FleetsInstanceOptions {
7
+ idempotencyKey?: string;
8
+ signal?: AbortSignal;
9
+ }
10
+ /** Managed Henry instances and tasks scoped to the credential's verified org. */
5
11
  export declare function fleetsModule(client: GenesisClient): {
12
+ agents: {
13
+ list: () => Promise<{
14
+ agents: import("./index.js").components["schemas"]["Fleets.Agent"][];
15
+ }>;
16
+ launch: (body: AgentLaunchInput) => Promise<{
17
+ id: string;
18
+ name: string;
19
+ state: "launching" | "running" | "stopping" | "stopped" | "recovering" | "recovery_required";
20
+ memory: import("./index.js").components["schemas"]["Fleets.Memory"];
21
+ model?: string | null;
22
+ inference: {
23
+ provider: "foundry";
24
+ mode: "managed";
25
+ };
26
+ created_at_ms: number;
27
+ updated_at_ms: number;
28
+ last_operation_id?: string | null;
29
+ }>;
30
+ get: (agent_id: string) => Promise<{
31
+ id: string;
32
+ name: string;
33
+ state: "launching" | "running" | "stopping" | "stopped" | "recovering" | "recovery_required";
34
+ memory: import("./index.js").components["schemas"]["Fleets.Memory"];
35
+ model?: string | null;
36
+ inference: {
37
+ provider: "foundry";
38
+ mode: "managed";
39
+ };
40
+ created_at_ms: number;
41
+ updated_at_ms: number;
42
+ last_operation_id?: string | null;
43
+ }>;
44
+ command: (agent_id: string, body: AgentCommandInput) => Promise<{
45
+ agent: import("./index.js").components["schemas"]["Fleets.Agent"];
46
+ operation_id: string;
47
+ result?: {
48
+ [key: string]: unknown;
49
+ };
50
+ }>;
51
+ stop: (agent_id: string) => Promise<{
52
+ agent: import("./index.js").components["schemas"]["Fleets.Agent"];
53
+ operation_id: string;
54
+ result?: {
55
+ [key: string]: unknown;
56
+ };
57
+ }>;
58
+ recover: (agent_id: string) => Promise<{
59
+ agent: import("./index.js").components["schemas"]["Fleets.Agent"];
60
+ operation_id: string;
61
+ result?: {
62
+ [key: string]: unknown;
63
+ };
64
+ }>;
65
+ };
66
+ instances: {
67
+ list: (query?: OperationInput<"fleets_listInstances">) => Promise<{
68
+ instances: import("./index.js").components["schemas"]["Fleets.Instance"][];
69
+ next_cursor: string | null;
70
+ }>;
71
+ get: (instance_id: string) => Promise<{
72
+ id: string;
73
+ name: string;
74
+ organization_id: string;
75
+ workspace_id: string;
76
+ project_id: string;
77
+ admission_held: boolean | null;
78
+ physical_state: "created" | "running" | "held" | "stopped" | "recovery_required" | "released";
79
+ status_evidence: "live_controller" | "confirmed_release_receipt";
80
+ release_acknowledged_at_ms: number | null;
81
+ harness_ready: boolean;
82
+ observed_at_ms: number | null;
83
+ lease_expires: {
84
+ wall_ms: number;
85
+ logical: number;
86
+ } | null;
87
+ }>;
88
+ stop: (instance_id: string, options?: FleetsInstanceOptions) => Promise<{
89
+ id: string;
90
+ state: "released";
91
+ reaped: true;
92
+ data_retained: true;
93
+ status_evidence: "confirmed_release_receipt";
94
+ release_acknowledged_at_ms: number;
95
+ }>;
96
+ start: (instance_id: string, options?: FleetsInstanceOptions) => Promise<{
97
+ id: string;
98
+ name: string;
99
+ organization_id: string;
100
+ workspace_id: string;
101
+ project_id: string;
102
+ admission_held: boolean | null;
103
+ physical_state: "created" | "running" | "held" | "stopped" | "recovery_required" | "released";
104
+ status_evidence: "live_controller" | "confirmed_release_receipt";
105
+ release_acknowledged_at_ms: number | null;
106
+ harness_ready: boolean;
107
+ observed_at_ms: number | null;
108
+ lease_expires: {
109
+ wall_ms: number;
110
+ logical: number;
111
+ } | null;
112
+ }>;
113
+ };
6
114
  connection: {
7
115
  get: () => Promise<{
8
116
  connected: boolean;
package/dist/fleets.js CHANGED
@@ -3,6 +3,13 @@ const byteLength = (value) => new TextEncoder().encode(value).byteLength;
3
3
  const invalid = (operationId, message) => new GenesisError({
4
4
  status: 0, type: 'invalid_request', operationId, message,
5
5
  });
6
+ function instanceOptions(operation, options) {
7
+ const key = options.idempotencyKey;
8
+ if (key !== undefined && (typeof key !== 'string' || !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(key))) {
9
+ throw invalid(operation, 'Idempotency-Key must be one canonical UUID4.');
10
+ }
11
+ return { signal: options.signal, ...(key === undefined ? {} : { headers: { 'idempotency-key': key } }) };
12
+ }
6
13
  function validateTask(body) {
7
14
  if (typeof body?.input !== 'string' || !body.input || byteLength(body.input) > 65536 || body.input.includes('\0')) {
8
15
  throw invalid('fleets_submitTask', 'Task input must contain 1–65536 UTF-8 bytes without NUL.');
@@ -19,9 +26,90 @@ function validateKey(apiKey) {
19
26
  throw invalid('fleets_setConnection', 'Foundry key must contain 1–4096 UTF-8 bytes without CR, LF or NUL.');
20
27
  }
21
28
  }
22
- /** Managed agent tasks, always scoped to the credential's verified organization. */
29
+ const instanceId = (value, operation) => {
30
+ 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
+ throw invalid(operation, 'Invalid Fleets instance ID.');
32
+ }
33
+ };
34
+ const agentId = (value, operation) => {
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)) {
36
+ throw invalid(operation, 'Invalid Fleets agent ID.');
37
+ }
38
+ };
39
+ function validateAgentLaunch(body) {
40
+ if (!body || typeof body.name !== 'string' || !body.name.trim() || byteLength(body.name) > 128) {
41
+ throw invalid('fleets_launchAgent', 'Agent name must contain 1–128 UTF-8 bytes.');
42
+ }
43
+ if (body.instructions !== undefined && (byteLength(body.instructions) > 65536 || body.instructions.includes('\0'))) {
44
+ throw invalid('fleets_launchAgent', 'Agent instructions must contain at most 65536 UTF-8 bytes without NUL.');
45
+ }
46
+ if (body.model !== undefined && (!body.model || byteLength(body.model) > 128)) {
47
+ throw invalid('fleets_launchAgent', 'Agent model must contain 1–128 UTF-8 bytes.');
48
+ }
49
+ if (body.memory !== undefined) {
50
+ if (!['minds', 'files', 'custom'].includes(body.memory.provider))
51
+ throw invalid('fleets_launchAgent', 'Memory provider must be minds, files, or custom.');
52
+ if (body.memory.provider === 'custom' && !body.memory.endpoint)
53
+ throw invalid('fleets_launchAgent', 'Custom memory requires an endpoint.');
54
+ }
55
+ if (body.idempotency_key !== undefined && (typeof body.idempotency_key !== 'string' || !/^[!-~]{1,128}$/.test(body.idempotency_key))) {
56
+ throw invalid('fleets_launchAgent', 'Idempotency key must contain 1–128 visible ASCII characters.');
57
+ }
58
+ }
59
+ function validateAgentCommand(body) {
60
+ if (!body || typeof body.input !== 'string' || !body.input.trim() || byteLength(body.input) > 65536 || body.input.includes('\0')) {
61
+ throw invalid('fleets_commandAgent', 'Agent input must contain 1–65536 UTF-8 bytes without NUL.');
62
+ }
63
+ }
64
+ /** Managed Henry instances and tasks scoped to the credential's verified org. */
23
65
  export function fleetsModule(client) {
24
66
  return {
67
+ agents: {
68
+ list: () => client.request('fleets_listAgents'),
69
+ launch: async (body) => {
70
+ validateAgentLaunch(body);
71
+ return client.request('fleets_launchAgent', { body });
72
+ },
73
+ get: async (agent_id) => {
74
+ agentId(agent_id, 'fleets_getAgent');
75
+ return client.request('fleets_getAgent', { agent_id });
76
+ },
77
+ command: async (agent_id, body) => {
78
+ agentId(agent_id, 'fleets_commandAgent');
79
+ validateAgentCommand(body);
80
+ return client.request('fleets_commandAgent', { agent_id, body });
81
+ },
82
+ stop: async (agent_id) => {
83
+ agentId(agent_id, 'fleets_stopAgent');
84
+ return client.request('fleets_stopAgent', { agent_id });
85
+ },
86
+ recover: async (agent_id) => {
87
+ agentId(agent_id, 'fleets_recoverAgent');
88
+ return client.request('fleets_recoverAgent', { agent_id });
89
+ },
90
+ },
91
+ instances: {
92
+ list: async (query = {}) => {
93
+ if (query.limit !== undefined && (!Number.isInteger(query.limit) || query.limit < 1 || query.limit > 50)) {
94
+ throw invalid('fleets_listInstances', 'Limit must be 1–50.');
95
+ }
96
+ if (query.after !== undefined)
97
+ instanceId(query.after, 'fleets_listInstances');
98
+ return client.request('fleets_listInstances', query);
99
+ },
100
+ get: async (instance_id) => {
101
+ instanceId(instance_id, 'fleets_getInstance');
102
+ return client.request('fleets_getInstance', { instance_id });
103
+ },
104
+ stop: async (instance_id, options = {}) => {
105
+ instanceId(instance_id, 'fleets_stopInstance');
106
+ return client.request('fleets_stopInstance', { instance_id }, instanceOptions('fleets_stopInstance', options));
107
+ },
108
+ start: async (instance_id, options = {}) => {
109
+ instanceId(instance_id, 'fleets_startInstance');
110
+ return client.request('fleets_startInstance', { instance_id }, instanceOptions('fleets_startInstance', options));
111
+ },
112
+ },
25
113
  connection: {
26
114
  get: () => client.request('fleets_getConnection', {}),
27
115
  set: async (api_key) => {