@onesi/genesis 0.0.1-stage → 0.2.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.
Files changed (113) hide show
  1. package/README.md +29 -0
  2. package/dist/auto.d.ts +1010 -0
  3. package/dist/auto.js +125 -0
  4. package/dist/beacons.d.ts +145 -0
  5. package/dist/beacons.js +148 -0
  6. package/dist/blinds-owner-enrollment.d.ts +280 -0
  7. package/dist/blinds-owner-enrollment.js +169 -0
  8. package/dist/client.d.ts +52 -0
  9. package/dist/client.js +384 -0
  10. package/dist/context.d.ts +50 -0
  11. package/dist/context.js +153 -0
  12. package/dist/dials.d.ts +299 -0
  13. package/dist/dials.js +100 -0
  14. package/dist/errors.d.ts +35 -0
  15. package/dist/errors.js +66 -0
  16. package/dist/flares.d.ts +202 -0
  17. package/dist/flares.js +61 -0
  18. package/dist/fleets.d.ts +218 -0
  19. package/dist/fleets.js +189 -0
  20. package/dist/flocks.d.ts +439 -0
  21. package/dist/flocks.js +98 -0
  22. package/dist/foundry-billing.d.ts +97 -0
  23. package/dist/foundry-billing.js +107 -0
  24. package/dist/foundry.d.ts +424 -0
  25. package/dist/foundry.js +133 -0
  26. package/dist/garden.d.ts +754 -0
  27. package/dist/garden.js +76 -0
  28. package/dist/gateway.d.ts +365 -0
  29. package/dist/gateway.js +82 -0
  30. package/dist/gears.d.ts +226 -0
  31. package/dist/gears.js +42 -0
  32. package/dist/generated/operations.d.ts +4 -0
  33. package/dist/generated/operations.js +65134 -0
  34. package/dist/generated/schema.d.ts +87658 -0
  35. package/dist/generated/schema.js +1 -0
  36. package/dist/genesis.d.ts +9574 -0
  37. package/dist/genesis.js +271 -0
  38. package/dist/genesis.openapi.json +128522 -0
  39. package/dist/henry.d.ts +369 -0
  40. package/dist/henry.js +268 -0
  41. package/dist/hives-cells.d.ts +127 -0
  42. package/dist/hives-cells.js +151 -0
  43. package/dist/hives-environment.d.ts +37 -0
  44. package/dist/hives-environment.js +80 -0
  45. package/dist/hives-functions.d.ts +36 -0
  46. package/dist/hives-functions.js +185 -0
  47. package/dist/hives-marketplace.d.ts +128 -0
  48. package/dist/hives-marketplace.js +187 -0
  49. package/dist/hives-node.d.ts +9 -0
  50. package/dist/hives-node.js +155 -0
  51. package/dist/hives-operations.d.ts +451 -0
  52. package/dist/hives-operations.js +43 -0
  53. package/dist/hives-platform.d.ts +149 -0
  54. package/dist/hives-platform.js +183 -0
  55. package/dist/hives-queue-consumer.d.ts +56 -0
  56. package/dist/hives-queue-consumer.js +95 -0
  57. package/dist/hives-queue-funding.d.ts +28 -0
  58. package/dist/hives-queue-funding.js +81 -0
  59. package/dist/hives-queues.d.ts +137 -0
  60. package/dist/hives-queues.js +143 -0
  61. package/dist/hives-resources-transport.d.ts +23 -0
  62. package/dist/hives-resources-transport.js +204 -0
  63. package/dist/hives-response.d.ts +2 -0
  64. package/dist/hives-response.js +32 -0
  65. package/dist/hives-runtimes.d.ts +34 -0
  66. package/dist/hives-runtimes.js +32 -0
  67. package/dist/hives-sse.d.ts +24 -0
  68. package/dist/hives-sse.js +111 -0
  69. package/dist/hives-topics.d.ts +51 -0
  70. package/dist/hives-topics.js +79 -0
  71. package/dist/hives-upload.d.ts +9 -0
  72. package/dist/hives-upload.js +23 -0
  73. package/dist/hives-volumes.d.ts +64 -0
  74. package/dist/hives-volumes.js +117 -0
  75. package/dist/iam-directory.d.ts +8 -0
  76. package/dist/iam-directory.js +23 -0
  77. package/dist/iam.d.ts +391 -0
  78. package/dist/iam.js +92 -0
  79. package/dist/idempotency.d.ts +7 -0
  80. package/dist/idempotency.js +39 -0
  81. package/dist/index.d.ts +47 -0
  82. package/dist/index.js +37 -0
  83. package/dist/instances.d.ts +200 -0
  84. package/dist/instances.js +43 -0
  85. package/dist/lanes.d.ts +267 -0
  86. package/dist/lanes.js +41 -0
  87. package/dist/minds-agent.d.ts +88 -0
  88. package/dist/minds-agent.js +233 -0
  89. package/dist/minds-engine.d.ts +394 -0
  90. package/dist/minds-engine.js +555 -0
  91. package/dist/minds-owner.d.ts +110 -0
  92. package/dist/minds-owner.js +226 -0
  93. package/dist/minds.d.ts +377 -0
  94. package/dist/minds.js +228 -0
  95. package/dist/orgs.d.ts +405 -0
  96. package/dist/orgs.js +63 -0
  97. package/dist/relays-enrollment.d.ts +33 -0
  98. package/dist/relays-enrollment.js +115 -0
  99. package/dist/runner-grant.d.ts +10 -0
  100. package/dist/runner-grant.js +133 -0
  101. package/dist/runner.d.ts +72 -0
  102. package/dist/runner.js +117 -0
  103. package/dist/scopes.d.ts +310 -0
  104. package/dist/scopes.js +40 -0
  105. package/dist/taps.d.ts +118 -0
  106. package/dist/taps.js +45 -0
  107. package/dist/traces.d.ts +574 -0
  108. package/dist/traces.js +97 -0
  109. package/dist/types.d.ts +98 -0
  110. package/dist/types.js +1 -0
  111. package/dist/urls.d.ts +6 -0
  112. package/dist/urls.js +31 -0
  113. package/package.json +49 -1
@@ -0,0 +1,369 @@
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 HenryRun = components['schemas']['Henry.Run'];
5
+ export type HenryRunEvent = components['schemas']['Henry.RunEvent'];
6
+ export type HenryRunEventMessage = components['schemas']['Henry.RunEventMessage'];
7
+ export type HenryRunStreamStatus = components['schemas']['Henry.RunStreamStatus'];
8
+ export type HenryMessageDelivery = components['schemas']['Henry.RunMessageDelivery'];
9
+ /** One event of a live run stream. `end` comes once the run is completed, failed or cancelled. */
10
+ export type HenryStreamEvent = {
11
+ event: 'activity';
12
+ data: HenryRunEvent;
13
+ } | {
14
+ event: 'status' | 'end';
15
+ data: HenryRunStreamStatus;
16
+ } | {
17
+ event: 'error';
18
+ data: {
19
+ message: string;
20
+ };
21
+ };
22
+ export interface HenryStreamOptions {
23
+ /** Start after this activity seq (default 0: from the first event). */
24
+ afterSeq?: number;
25
+ /** Stops the stream; the iterator then returns without an error. */
26
+ signal?: AbortSignal;
27
+ /** Reconnects allowed after a stream closes without `end` (1-1000, default 100). Henry closes each stream within 120 s. */
28
+ maxReconnects?: number;
29
+ /** Reconnect when no byte (event or 15 s keepalive) arrives for this long (default 45000 ms). */
30
+ idleMs?: number;
31
+ }
32
+ /** A turn: the inputs that led to one model answer, and that answer. Its number is the `turn` to fork from to re-run it. */
33
+ export interface HenryTurn {
34
+ turn: number;
35
+ inputs: HenryRunEventMessage[];
36
+ events: Pick<HenryRunEvent, 'seq' | 'at' | 'type' | 'detail'>[];
37
+ answer: HenryRunEventMessage;
38
+ }
39
+ export interface HenryReplay {
40
+ run_id: string;
41
+ status?: string;
42
+ turns: HenryTurn[];
43
+ pending: Omit<HenryTurn, 'answer'>;
44
+ }
45
+ /**
46
+ * Fold a run's events into turns. Turn N holds the inputs that led to the run's (N+1)th model answer and that answer, so
47
+ * forking from turn N re-runs that answer. Events after the last answer are returned as `pending`.
48
+ */
49
+ export declare function foldHenryTurns(events: HenryRunEvent[]): {
50
+ turns: HenryTurn[];
51
+ pending: Omit<HenryTurn, 'answer'>;
52
+ };
53
+ /** Henry always derives its organization from the gateway's verified caller. */
54
+ export declare function henryModule(client: GenesisClient): {
55
+ config: () => Promise<{
56
+ credential_configured: boolean;
57
+ models: {
58
+ id: string;
59
+ }[];
60
+ }>;
61
+ credential: (api_key: string) => Promise<{
62
+ credential_configured: boolean;
63
+ models: {
64
+ id: string;
65
+ }[];
66
+ }>;
67
+ /** Foundry models the stored organization key can run; 409 when no key is stored. */
68
+ models: () => Promise<{
69
+ models: {
70
+ id: string;
71
+ }[];
72
+ }>;
73
+ receipts: {
74
+ /** Current standalone Henry public signer. Pin through trusted deployment custody before verifying a receipt. */
75
+ verificationKey: () => Promise<{
76
+ keyId: string;
77
+ publicKeyBase64: string;
78
+ }>;
79
+ };
80
+ blinds: {
81
+ /** HTTPS request through the organization's bound Blinds machine. */
82
+ proxy: (body: NonNullable<OperationInput<"henry_proxyBlinds">["body"]>) => Promise<{
83
+ request_id?: string;
84
+ status: number;
85
+ headers: {
86
+ [key: string]: string;
87
+ };
88
+ body: string;
89
+ }>;
90
+ };
91
+ runs: {
92
+ list: (page_token?: string) => Promise<{
93
+ runs: components["schemas"]["Henry.Run"][];
94
+ next_page_token?: string | null;
95
+ }>;
96
+ create: (body: NonNullable<OperationInput<"henry_createRun">["body"]>) => Promise<{
97
+ id: string;
98
+ task: string;
99
+ model: string;
100
+ status: "ready" | "running" | "acting" | "paused" | "awaiting_approval" | "completed" | "cancelled" | "failed" | "waiting_input";
101
+ created_at: string;
102
+ updated_at: string;
103
+ activity: {
104
+ id: string;
105
+ at: string;
106
+ type: string;
107
+ detail: string;
108
+ message_index?: number;
109
+ message_id?: string;
110
+ }[];
111
+ approvals: {
112
+ id: string;
113
+ action: string;
114
+ url: string;
115
+ status: string;
116
+ requested_at: string;
117
+ }[];
118
+ phases: {
119
+ id?: string;
120
+ code?: number;
121
+ receipt?: Record<string, never>;
122
+ usage?: Record<string, never> | null;
123
+ started_at?: string;
124
+ ended_at?: string;
125
+ }[];
126
+ messages: ({
127
+ role: string;
128
+ content?: string | null;
129
+ } & {
130
+ [key: string]: unknown;
131
+ })[];
132
+ trace_id?: string;
133
+ forked_from?: {
134
+ run_id: string;
135
+ turn: number;
136
+ text_sha256?: string;
137
+ };
138
+ inbox?: components["schemas"]["Henry.RunInboxMessage"][];
139
+ interrupt_execution?: string;
140
+ observatory_version?: number;
141
+ telemetry?: components["schemas"]["Henry.RunTelemetry"];
142
+ }>;
143
+ get: (run_id: string) => Promise<{
144
+ id: string;
145
+ task: string;
146
+ model: string;
147
+ status: "ready" | "running" | "acting" | "paused" | "awaiting_approval" | "completed" | "cancelled" | "failed" | "waiting_input";
148
+ created_at: string;
149
+ updated_at: string;
150
+ activity: {
151
+ id: string;
152
+ at: string;
153
+ type: string;
154
+ detail: string;
155
+ message_index?: number;
156
+ message_id?: string;
157
+ }[];
158
+ approvals: {
159
+ id: string;
160
+ action: string;
161
+ url: string;
162
+ status: string;
163
+ requested_at: string;
164
+ }[];
165
+ phases: {
166
+ id?: string;
167
+ code?: number;
168
+ receipt?: Record<string, never>;
169
+ usage?: Record<string, never> | null;
170
+ started_at?: string;
171
+ ended_at?: string;
172
+ }[];
173
+ messages: ({
174
+ role: string;
175
+ content?: string | null;
176
+ } & {
177
+ [key: string]: unknown;
178
+ })[];
179
+ trace_id?: string;
180
+ forked_from?: {
181
+ run_id: string;
182
+ turn: number;
183
+ text_sha256?: string;
184
+ };
185
+ inbox?: components["schemas"]["Henry.RunInboxMessage"][];
186
+ interrupt_execution?: string;
187
+ observatory_version?: number;
188
+ telemetry?: components["schemas"]["Henry.RunTelemetry"];
189
+ }>;
190
+ /** Page activity events: pass the previous page's next_after_seq as after_seq. */
191
+ events: (run_id: string, query?: {
192
+ after_seq?: number;
193
+ limit?: number;
194
+ }) => Promise<{
195
+ run_id: string;
196
+ status?: string;
197
+ events: components["schemas"]["Henry.RunEvent"][];
198
+ next_after_seq: number;
199
+ has_more: boolean;
200
+ }>;
201
+ /** Delete a finished run; active runs must be cancelled first (409). */
202
+ delete: (run_id: string) => Promise<{
203
+ run_id: string;
204
+ deleted: boolean;
205
+ }>;
206
+ command: (run_id: string, action: "pause" | "resume" | "cancel" | "input", text?: string) => Promise<{
207
+ id: string;
208
+ task: string;
209
+ model: string;
210
+ status: "ready" | "running" | "acting" | "paused" | "awaiting_approval" | "completed" | "cancelled" | "failed" | "waiting_input";
211
+ created_at: string;
212
+ updated_at: string;
213
+ activity: {
214
+ id: string;
215
+ at: string;
216
+ type: string;
217
+ detail: string;
218
+ message_index?: number;
219
+ message_id?: string;
220
+ }[];
221
+ approvals: {
222
+ id: string;
223
+ action: string;
224
+ url: string;
225
+ status: string;
226
+ requested_at: string;
227
+ }[];
228
+ phases: {
229
+ id?: string;
230
+ code?: number;
231
+ receipt?: Record<string, never>;
232
+ usage?: Record<string, never> | null;
233
+ started_at?: string;
234
+ ended_at?: string;
235
+ }[];
236
+ messages: ({
237
+ role: string;
238
+ content?: string | null;
239
+ } & {
240
+ [key: string]: unknown;
241
+ })[];
242
+ trace_id?: string;
243
+ forked_from?: {
244
+ run_id: string;
245
+ turn: number;
246
+ text_sha256?: string;
247
+ };
248
+ inbox?: components["schemas"]["Henry.RunInboxMessage"][];
249
+ interrupt_execution?: string;
250
+ observatory_version?: number;
251
+ telemetry?: components["schemas"]["Henry.RunTelemetry"];
252
+ }>;
253
+ approval: (run_id: string, approval_id: string, decision: "approve" | "reject") => Promise<{
254
+ id: string;
255
+ task: string;
256
+ model: string;
257
+ status: "ready" | "running" | "acting" | "paused" | "awaiting_approval" | "completed" | "cancelled" | "failed" | "waiting_input";
258
+ created_at: string;
259
+ updated_at: string;
260
+ activity: {
261
+ id: string;
262
+ at: string;
263
+ type: string;
264
+ detail: string;
265
+ message_index?: number;
266
+ message_id?: string;
267
+ }[];
268
+ approvals: {
269
+ id: string;
270
+ action: string;
271
+ url: string;
272
+ status: string;
273
+ requested_at: string;
274
+ }[];
275
+ phases: {
276
+ id?: string;
277
+ code?: number;
278
+ receipt?: Record<string, never>;
279
+ usage?: Record<string, never> | null;
280
+ started_at?: string;
281
+ ended_at?: string;
282
+ }[];
283
+ messages: ({
284
+ role: string;
285
+ content?: string | null;
286
+ } & {
287
+ [key: string]: unknown;
288
+ })[];
289
+ trace_id?: string;
290
+ forked_from?: {
291
+ run_id: string;
292
+ turn: number;
293
+ text_sha256?: string;
294
+ };
295
+ inbox?: components["schemas"]["Henry.RunInboxMessage"][];
296
+ interrupt_execution?: string;
297
+ observatory_version?: number;
298
+ telemetry?: components["schemas"]["Henry.RunTelemetry"];
299
+ }>;
300
+ /**
301
+ * Send a message into a run: `steer` lands before the next model call, `queue` (default) after the current turn,
302
+ * `interrupt` cancels the turn in flight. A completed run takes it at once and runs the next turn in this request.
303
+ */
304
+ message: (run_id: string, body: {
305
+ text: string;
306
+ delivery?: HenryMessageDelivery;
307
+ }) => Promise<{
308
+ message: components["schemas"]["Henry.RunMessageReceipt"];
309
+ run: components["schemas"]["Henry.Run"];
310
+ }>;
311
+ /**
312
+ * Start a new run from turn N of this one (0 = the original task; default the latest, which needs text). run_id is
313
+ * the new run's idempotency key: one is generated unless supplied, and a retry with it returns the same run.
314
+ */
315
+ fork: (run_id: string, body?: NonNullable<OperationInput<"henry_forkRun">["body"]>) => Promise<{
316
+ id: string;
317
+ task: string;
318
+ model: string;
319
+ status: "ready" | "running" | "acting" | "paused" | "awaiting_approval" | "completed" | "cancelled" | "failed" | "waiting_input";
320
+ created_at: string;
321
+ updated_at: string;
322
+ activity: {
323
+ id: string;
324
+ at: string;
325
+ type: string;
326
+ detail: string;
327
+ message_index?: number;
328
+ message_id?: string;
329
+ }[];
330
+ approvals: {
331
+ id: string;
332
+ action: string;
333
+ url: string;
334
+ status: string;
335
+ requested_at: string;
336
+ }[];
337
+ phases: {
338
+ id?: string;
339
+ code?: number;
340
+ receipt?: Record<string, never>;
341
+ usage?: Record<string, never> | null;
342
+ started_at?: string;
343
+ ended_at?: string;
344
+ }[];
345
+ messages: ({
346
+ role: string;
347
+ content?: string | null;
348
+ } & {
349
+ [key: string]: unknown;
350
+ })[];
351
+ trace_id?: string;
352
+ forked_from?: {
353
+ run_id: string;
354
+ turn: number;
355
+ text_sha256?: string;
356
+ };
357
+ inbox?: components["schemas"]["Henry.RunInboxMessage"][];
358
+ interrupt_execution?: string;
359
+ observatory_version?: number;
360
+ telemetry?: components["schemas"]["Henry.RunTelemetry"];
361
+ }>;
362
+ /** Follow a run live: an async iterator over its stream, reconnecting with Last-Event-ID until `end`. */
363
+ streamEvents: (run_id: string, options?: HenryStreamOptions) => AsyncGenerator<HenryStreamEvent, any, any>;
364
+ /** Read every event of a run and fold them into turns (see foldHenryTurns). */
365
+ replay: (run_id: string, options?: {
366
+ signal?: AbortSignal;
367
+ }) => Promise<HenryReplay>;
368
+ };
369
+ };
package/dist/henry.js ADDED
@@ -0,0 +1,268 @@
1
+ import { GenesisError } from './errors.js';
2
+ const MAX_SSE_EVENT_CHARS = 4 * 1024 * 1024;
3
+ const REPLAY_PAGE = 500;
4
+ const MAX_REPLAY_PAGES = 2000;
5
+ /**
6
+ * Fold a run's events into turns. Turn N holds the inputs that led to the run's (N+1)th model answer and that answer, so
7
+ * forking from turn N re-runs that answer. Events after the last answer are returned as `pending`.
8
+ */
9
+ export function foldHenryTurns(events) {
10
+ const turns = [];
11
+ let current = { turn: 0, inputs: [], events: [] };
12
+ for (const event of events) {
13
+ current.events.push({ seq: event.seq, at: event.at, type: event.type, ...(event.detail === undefined ? {} : { detail: event.detail }) });
14
+ const message = event.message;
15
+ if (message?.role === 'assistant') {
16
+ turns.push({ ...current, answer: message });
17
+ current = { turn: turns.length, inputs: [], events: [] };
18
+ }
19
+ else if (message)
20
+ current.inputs.push(message);
21
+ }
22
+ return { turns, pending: current };
23
+ }
24
+ const invalid = (operationId, message) => new GenesisError({ status: 0, type: 'invalid_request', operationId, message });
25
+ const STREAM_EVENTS = new Set(['activity', 'status', 'end', 'error']);
26
+ /** Parse one SSE frame's lines into { event, id, data }; undefined for comments, keepalives and data-less frames. */
27
+ function frame(lines) {
28
+ let event = 'message';
29
+ let id;
30
+ const data = [];
31
+ for (const line of lines) {
32
+ if (line.startsWith(':'))
33
+ continue;
34
+ const colon = line.indexOf(':');
35
+ const field = colon < 0 ? line : line.slice(0, colon);
36
+ let value = colon < 0 ? '' : line.slice(colon + 1);
37
+ if (value.startsWith(' '))
38
+ value = value.slice(1);
39
+ if (field === 'event')
40
+ event = value;
41
+ else if (field === 'id')
42
+ id = value;
43
+ else if (field === 'data')
44
+ data.push(value);
45
+ }
46
+ return data.length ? { event, ...(id === undefined ? {} : { id }), data: data.join('\n') } : undefined;
47
+ }
48
+ const wait = (ms, signal) => new Promise(resolve => {
49
+ const done = () => { clearTimeout(timer); signal?.removeEventListener('abort', done); resolve(); };
50
+ const timer = setTimeout(done, ms);
51
+ signal?.addEventListener('abort', done, { once: true });
52
+ });
53
+ /**
54
+ * Follow a run over SSE, reconnecting with Last-Event-ID after each stream closes (Henry closes one within 120 s) until
55
+ * `end`, the signal aborts or maxReconnects is used up. Never buffers a whole stream.
56
+ */
57
+ async function* streamRunEvents(client, run_id, options = {}) {
58
+ const operationId = 'henry_listRunEvents';
59
+ const { signal } = options;
60
+ let last = options.afterSeq ?? 0;
61
+ let lastStatus;
62
+ if (!Number.isSafeInteger(last) || last < 0 || last > 9_999_999)
63
+ throw invalid(operationId, 'afterSeq must be an integer from 0 to 9999999.');
64
+ const maxReconnects = options.maxReconnects ?? 100;
65
+ if (!Number.isInteger(maxReconnects) || maxReconnects < 1 || maxReconnects > 1000)
66
+ throw invalid(operationId, 'maxReconnects must be an integer from 1 to 1000.');
67
+ const idleMs = options.idleMs ?? 45_000;
68
+ if (!Number.isInteger(idleMs) || idleMs < 1000 || idleMs > 300_000)
69
+ throw invalid(operationId, 'idleMs must be an integer from 1000 to 300000.');
70
+ for (let attempt = 0; attempt <= maxReconnects && !signal?.aborted; attempt++) {
71
+ // One connection: aborted by the caller's signal, or when the stream goes quiet for idleMs.
72
+ const connection = new AbortController();
73
+ const abort = () => connection.abort();
74
+ signal?.addEventListener('abort', abort, { once: true });
75
+ let idle;
76
+ const touch = () => { clearTimeout(idle); idle = setTimeout(abort, idleMs); };
77
+ let progressed = false;
78
+ try {
79
+ touch();
80
+ let response;
81
+ try {
82
+ // Exactly text/event-stream: an Accept that also allows JSON gets the paged read instead of a stream.
83
+ response = await client.raw(operationId, { run_id }, { signal: connection.signal, headers: { accept: 'text/event-stream', 'last-event-id': String(last) } });
84
+ }
85
+ catch (error) {
86
+ if (signal?.aborted)
87
+ return;
88
+ // An HTTP answer (404, 403, 429 streams_busy...) is raised, not retried in a loop. A dropped or quiet connection is retried.
89
+ if (error instanceof GenesisError && error.status !== 0 && ![502, 503, 504].includes(error.status))
90
+ throw error;
91
+ await wait(1000, signal);
92
+ continue;
93
+ }
94
+ const reader = response.body?.getReader();
95
+ if (!reader) {
96
+ await wait(1000, signal);
97
+ continue;
98
+ }
99
+ const decoder = new TextDecoder();
100
+ let buffer = '';
101
+ let lines = [];
102
+ let size = 0;
103
+ try {
104
+ for (;;) {
105
+ let chunk;
106
+ try {
107
+ chunk = await reader.read();
108
+ }
109
+ catch {
110
+ break;
111
+ }
112
+ if (chunk.done)
113
+ break;
114
+ touch();
115
+ buffer += decoder.decode(chunk.value, { stream: true });
116
+ let index;
117
+ while ((index = buffer.search(/\r\n|\r|\n/)) >= 0) {
118
+ const line = buffer.slice(0, index);
119
+ buffer = buffer.slice(index + (buffer.startsWith('\r\n', index) ? 2 : 1));
120
+ if (line !== '') {
121
+ size += line.length;
122
+ if (size > MAX_SSE_EVENT_CHARS)
123
+ throw new GenesisError({ status: 0, type: 'invalid_response', operationId, message: 'Henry stream event exceeds 4 MiB.' });
124
+ lines.push(line);
125
+ continue;
126
+ }
127
+ const parsed = frame(lines);
128
+ lines = [];
129
+ size = 0;
130
+ if (!parsed || !STREAM_EVENTS.has(parsed.event))
131
+ continue;
132
+ let data;
133
+ try {
134
+ data = JSON.parse(parsed.data);
135
+ }
136
+ catch {
137
+ throw new GenesisError({ status: 0, type: 'invalid_response', operationId, message: 'Henry stream sent invalid JSON.' });
138
+ }
139
+ if (parsed.event === 'status') {
140
+ // Each connection opens with the current status: report a change only.
141
+ const status = data?.status;
142
+ if (status === lastStatus)
143
+ continue;
144
+ lastStatus = status;
145
+ }
146
+ if (parsed.event === 'activity') {
147
+ const seq = data?.seq;
148
+ if (typeof seq !== 'number' || !Number.isSafeInteger(seq))
149
+ throw new GenesisError({ status: 0, type: 'invalid_response', operationId, message: 'Henry stream activity has no seq.' });
150
+ if (seq <= last)
151
+ continue; // already yielded before a reconnect
152
+ last = seq;
153
+ progressed = true;
154
+ }
155
+ yield { event: parsed.event, data };
156
+ if (parsed.event === 'end')
157
+ return;
158
+ }
159
+ if (buffer.length > MAX_SSE_EVENT_CHARS)
160
+ throw new GenesisError({ status: 0, type: 'invalid_response', operationId, message: 'Henry stream event exceeds 4 MiB.' });
161
+ }
162
+ }
163
+ finally {
164
+ void reader.cancel().catch(() => { });
165
+ }
166
+ if (signal?.aborted)
167
+ return;
168
+ // A stream that closed with nothing new waits before reconnecting, so a failing upstream is never hammered.
169
+ if (!progressed)
170
+ await wait(1000, signal);
171
+ }
172
+ finally {
173
+ clearTimeout(idle);
174
+ signal?.removeEventListener('abort', abort);
175
+ }
176
+ }
177
+ if (!signal?.aborted)
178
+ throw new GenesisError({ status: 0, type: 'stream_incomplete', operationId,
179
+ message: `Henry run ${run_id} did not end within ${maxReconnects} reconnects; resume with afterSeq ${last}.` });
180
+ }
181
+ function receiptVerificationKey(value) {
182
+ if (!value || typeof value !== 'object' || Array.isArray(value))
183
+ throw new Error('Henry returned an invalid receipt verification key.');
184
+ const key = value;
185
+ if (Object.keys(key).length !== 2 || !Object.hasOwn(key, 'keyId') || !Object.hasOwn(key, 'publicKeyBase64')
186
+ || typeof key.keyId !== 'string' || !key.keyId.trim() || key.keyId.length > 256 || /[\x00-\x1f\x7f]/.test(key.keyId)
187
+ || typeof key.publicKeyBase64 !== 'string' || key.publicKeyBase64.length !== 44
188
+ || !/^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw048]=$/.test(key.publicKeyBase64)) {
189
+ throw new Error('Henry returned an invalid receipt verification key.');
190
+ }
191
+ return { keyId: key.keyId, publicKeyBase64: key.publicKeyBase64 };
192
+ }
193
+ /** Henry always derives its organization from the gateway's verified caller. */
194
+ export function henryModule(client) {
195
+ return {
196
+ config: () => client.request('henry_getConfig'),
197
+ credential: async (api_key) => {
198
+ if (typeof api_key !== 'string' || !api_key || new TextEncoder().encode(api_key).byteLength > 4096 || /[\r\n\0]/.test(api_key)) {
199
+ throw new GenesisError({ status: 0, type: 'invalid_request', operationId: 'henry_putCredential', message: 'Foundry key must contain 1–4096 UTF-8 bytes without CR, LF or NUL.' });
200
+ }
201
+ return client.request('henry_putCredential', { body: { api_key } });
202
+ },
203
+ /** Foundry models the stored organization key can run; 409 when no key is stored. */
204
+ models: () => client.request('henry_listFoundryModels'),
205
+ receipts: {
206
+ /** Current standalone Henry public signer. Pin through trusted deployment custody before verifying a receipt. */
207
+ verificationKey: async () => {
208
+ try {
209
+ return receiptVerificationKey(await client.request('henry_getReceiptVerificationKey'));
210
+ }
211
+ catch {
212
+ throw new Error('Henry returned an invalid receipt verification key.');
213
+ }
214
+ },
215
+ },
216
+ blinds: {
217
+ /** HTTPS request through the organization's bound Blinds machine. */
218
+ proxy: (body) => client.request('henry_proxyBlinds', { body }),
219
+ },
220
+ runs: {
221
+ list: (page_token) => client.request('henry_listRuns', page_token ? { page_token } : {}),
222
+ create: (body) => client.request('henry_createRun', { body: { ...body, run_id: body.run_id ?? globalThis.crypto.randomUUID() } }),
223
+ get: (run_id) => client.request('henry_getRun', { run_id }),
224
+ /** Page activity events: pass the previous page's next_after_seq as after_seq. */
225
+ events: (run_id, query = {}) => client.request('henry_listRunEvents', { run_id, ...query }),
226
+ /** Delete a finished run; active runs must be cancelled first (409). */
227
+ delete: (run_id) => client.request('henry_deleteRun', { run_id }),
228
+ command: (run_id, action, text) => client.request('henry_commandRun', { run_id, body: { action, ...(text === undefined ? {} : { text }) } }),
229
+ approval: (run_id, approval_id, decision) => client.request('henry_decideApproval', { run_id, approval_id, body: { decision } }),
230
+ /**
231
+ * Send a message into a run: `steer` lands before the next model call, `queue` (default) after the current turn,
232
+ * `interrupt` cancels the turn in flight. A completed run takes it at once and runs the next turn in this request.
233
+ */
234
+ message: (run_id, body) => {
235
+ if (typeof body?.text !== 'string' || !body.text.trim() || body.text.length > 16000)
236
+ throw invalid('henry_sendRunMessage', 'Message text must contain 1-16000 characters.');
237
+ return client.request('henry_sendRunMessage', { run_id, body: { text: body.text, ...(body.delivery === undefined ? {} : { delivery: body.delivery }) } });
238
+ },
239
+ /**
240
+ * Start a new run from turn N of this one (0 = the original task; default the latest, which needs text). run_id is
241
+ * the new run's idempotency key: one is generated unless supplied, and a retry with it returns the same run.
242
+ */
243
+ fork: (run_id, body = {}) => {
244
+ if (body.turn !== undefined && (!Number.isSafeInteger(body.turn) || body.turn < 0))
245
+ throw invalid('henry_forkRun', 'turn must be a non-negative integer.');
246
+ return client.request('henry_forkRun', { run_id, body: { ...body, run_id: body.run_id ?? globalThis.crypto.randomUUID() } });
247
+ },
248
+ /** Follow a run live: an async iterator over its stream, reconnecting with Last-Event-ID until `end`. */
249
+ streamEvents: (run_id, options = {}) => streamRunEvents(client, run_id, options),
250
+ /** Read every event of a run and fold them into turns (see foldHenryTurns). */
251
+ replay: async (run_id, options = {}) => {
252
+ const events = [];
253
+ let after = 0;
254
+ for (let pages = 0; pages < MAX_REPLAY_PAGES; pages++) {
255
+ const page = await client.request('henry_listRunEvents', { run_id, after_seq: after, limit: REPLAY_PAGE }, { signal: options.signal });
256
+ events.push(...page.events);
257
+ if (!page.has_more)
258
+ return { run_id: page.run_id, ...(page.status === undefined ? {} : { status: page.status }), ...foldHenryTurns(events) };
259
+ if (!Number.isSafeInteger(page.next_after_seq) || page.next_after_seq <= after) {
260
+ throw new GenesisError({ status: 0, type: 'invalid_response', operationId: 'henry_listRunEvents', message: 'Henry events did not advance.' });
261
+ }
262
+ after = page.next_after_seq;
263
+ }
264
+ throw new GenesisError({ status: 0, type: 'invalid_response', operationId: 'henry_listRunEvents', message: 'Henry run has too many events to replay.' });
265
+ },
266
+ },
267
+ };
268
+ }