@workser/runtime 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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +71 -2
  3. package/dist/cjs/client/follow.js +103 -0
  4. package/dist/cjs/client/index.js +6 -0
  5. package/dist/cjs/contract/actions.js +223 -0
  6. package/dist/cjs/contract/catalog.js +335 -0
  7. package/dist/cjs/contract/index.js +20 -0
  8. package/dist/cjs/contract/ordered-feed.js +94 -0
  9. package/dist/cjs/contract/types.js +9 -0
  10. package/dist/cjs/engine/engine.js +2 -0
  11. package/dist/cjs/index.js +27 -0
  12. package/dist/cjs/internal/async-queue.js +56 -0
  13. package/dist/cjs/internal/sse.js +170 -0
  14. package/dist/cjs/package.json +1 -0
  15. package/dist/cjs/place/place.js +2 -0
  16. package/dist/cjs/runner/index.js +6 -0
  17. package/dist/cjs/runner/journal.js +82 -0
  18. package/dist/cjs/runner/runner-client.js +477 -0
  19. package/dist/client/follow.d.ts +45 -0
  20. package/dist/client/follow.js +100 -0
  21. package/dist/client/follow.js.map +1 -0
  22. package/dist/client/index.d.ts +1 -0
  23. package/dist/client/index.js +2 -0
  24. package/dist/client/index.js.map +1 -0
  25. package/dist/contract/actions.d.ts +73 -0
  26. package/dist/contract/actions.js +217 -0
  27. package/dist/contract/actions.js.map +1 -0
  28. package/dist/contract/catalog.d.ts +97 -0
  29. package/dist/contract/catalog.js +328 -0
  30. package/dist/contract/catalog.js.map +1 -0
  31. package/dist/contract/index.d.ts +4 -0
  32. package/dist/contract/index.js +5 -0
  33. package/dist/contract/index.js.map +1 -0
  34. package/dist/contract/ordered-feed.d.ts +56 -0
  35. package/dist/contract/ordered-feed.js +91 -0
  36. package/dist/contract/ordered-feed.js.map +1 -0
  37. package/dist/contract/types.d.ts +311 -0
  38. package/dist/contract/types.js +9 -0
  39. package/dist/contract/types.js.map +1 -0
  40. package/dist/engine/engine.d.ts +39 -0
  41. package/dist/engine/engine.js +2 -0
  42. package/dist/engine/engine.js.map +1 -0
  43. package/dist/index.d.ts +10 -0
  44. package/dist/index.js +9 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/internal/async-queue.d.ts +14 -0
  47. package/dist/internal/async-queue.js +53 -0
  48. package/dist/internal/async-queue.js.map +1 -0
  49. package/dist/internal/sse.d.ts +57 -0
  50. package/dist/internal/sse.js +164 -0
  51. package/dist/internal/sse.js.map +1 -0
  52. package/dist/place/place.d.ts +81 -0
  53. package/dist/place/place.js +2 -0
  54. package/dist/place/place.js.map +1 -0
  55. package/dist/runner/index.d.ts +2 -0
  56. package/dist/runner/index.js +2 -0
  57. package/dist/runner/index.js.map +1 -0
  58. package/dist/runner/journal.d.ts +32 -0
  59. package/dist/runner/journal.js +79 -0
  60. package/dist/runner/journal.js.map +1 -0
  61. package/dist/runner/runner-client.d.ts +185 -0
  62. package/dist/runner/runner-client.js +473 -0
  63. package/dist/runner/runner-client.js.map +1 -0
  64. package/package.json +30 -4
@@ -0,0 +1,335 @@
1
+ "use strict";
2
+ /**
3
+ * The run event catalog: every event a runner may send, and what it must
4
+ * carry. Pure and dependency-free: this file is the source of truth in
5
+ * `@workser/runtime`, and core-api keeps a generated copy of it (plan Phase 0).
6
+ *
7
+ * TWO GATES, ON PURPOSE
8
+ * ---------------------
9
+ * - A runner (`v1/runner/runs/:id/events`) is strict: an unknown type or a
10
+ * malformed payload is refused, so a screen never has to guess what an event
11
+ * means.
12
+ * - The service engine (`internal/runs/:id/events`) is lenient: it already
13
+ * sends ~30 types, some built at runtime, and refusing one would lose a live
14
+ * run's history. Unknown types are accepted and counted (`unknownLegacyType`)
15
+ * so the catalog can grow to cover them before that gate tightens.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.LEGACY_EVENT_TYPES = exports.TERMINAL_EVENT_TYPES = exports.RUN_EVENT_CATALOG = exports.MAX_EVENT_BATCH = exports.MAX_EVENT_DATA_BYTES = exports.RUNNER_PROTOCOL_VERSION = void 0;
19
+ exports.checkRunnerEvent = checkRunnerEvent;
20
+ exports.checkLegacyEvent = checkLegacyEvent;
21
+ exports.unknownLegacyType = unknownLegacyType;
22
+ exports.statusForRunnerEvent = statusForRunnerEvent;
23
+ exports.checkBatchSeq = checkBatchSeq;
24
+ exports.RUNNER_PROTOCOL_VERSION = 1;
25
+ /** Largest event payload we keep, serialised. Bigger outputs go to files. */
26
+ exports.MAX_EVENT_DATA_BYTES = 64 * 1024;
27
+ /** Largest batch a runner may send in one call. */
28
+ exports.MAX_EVENT_BATCH = 100;
29
+ const text = (maxLength = 20_000, required = true) => ({
30
+ type: 'string',
31
+ required,
32
+ maxLength,
33
+ });
34
+ const id = (required = true) => text(120, required);
35
+ /**
36
+ * What each event means, for the people reading it on a screen:
37
+ *
38
+ * - run.* the run's life. Terminal ones end the stream.
39
+ * - step.* one thing the agent did (a command, an edit, a tool call).
40
+ * - message* what the agent said; `.delta` streams it as it is written.
41
+ * - file/diff what changed on disk.
42
+ * - check.result a done-check (tests, typecheck, deck validates, numbers match).
43
+ * - preview.ready a URL the owner can open.
44
+ * - handoff.* a sub-job this run asked for started or ended (core-api
45
+ * writes these on the parent; `run.completed.result` is the
46
+ * child's typed answer).
47
+ * - usage tokens and cost as the runner saw them; billing uses the
48
+ * gateway's own figure, never this one.
49
+ * - log plain lines for the details view.
50
+ */
51
+ exports.RUN_EVENT_CATALOG = {
52
+ 'run.started': { engine: text(80, false), place: text(40, false) },
53
+ 'run.paused': { reason: text(500, false) },
54
+ 'run.resumed': {},
55
+ // `result`: the typed answer a handoff's parent asked for (`Handoff.expected`).
56
+ 'run.completed': { summary: text(20_000, false), result: { type: 'object' } },
57
+ 'run.failed': { error_kind: text(80, false), message: text(4_000, false) },
58
+ 'run.cancelled': { reason: text(500, false) },
59
+ // A person stopped the job before it read some messages sent into it (its
60
+ // unacked `message` commands). Core writes it on Stop; nothing new starts.
61
+ // `messages`: [{ pos, text (≤500 chars), author (name or null) }], oldest first.
62
+ 'run.messages_unseen': {
63
+ count: { type: 'number', required: true },
64
+ messages: { type: 'array', required: true },
65
+ },
66
+ // A session-mode job finished a turn and is waiting for the next message.
67
+ 'run.idle': { summary: text(20_000, false), turn: { type: 'number' } },
68
+ // A reply turned into a job (reply-or-job contract §2c): the AI agent called
69
+ // `begin_job`, or the safety net saw an action, a computer, a fan-out, a
70
+ // helper or a long run. Core makes it a job once (migration 300).
71
+ 'run.became_job': {
72
+ reason: { type: 'string', required: true, oneOf: ['begin_job', 'action', 'computer', 'fan_out', 'long', 'helper', 'owner'] },
73
+ title: text(500, false),
74
+ eta: text(200, false),
75
+ },
76
+ // The engine's own session, so a later runner can reattach to it
77
+ // (core-api puts the latest into the brief as `run.resume`).
78
+ 'engine.session': { engine: text(80), session_id: text(200), workdir: text(1_000) },
79
+ // The agent is waiting on a person (`owner`) or on an approval it raised.
80
+ // `run.resumed` says it carried on.
81
+ 'run.waiting': {
82
+ on: { type: 'string', required: true, oneOf: ['owner', 'approval'] },
83
+ ask_id: id(false),
84
+ },
85
+ 'step.started': {
86
+ step_id: id(),
87
+ title: text(500),
88
+ tool: text(120, false),
89
+ input: { type: 'object' },
90
+ // The sub-job this step handed work to, and the step it is nested under.
91
+ child_run_id: id(false),
92
+ parent_step_id: id(false),
93
+ },
94
+ // Live output of a long step. Throttle to about one a second per step.
95
+ 'step.progress': {
96
+ step_id: id(),
97
+ text: text(2_000, false),
98
+ pct: { type: 'number' },
99
+ stdout_tail: text(2_000, false),
100
+ },
101
+ // The agent's to-do list changed (`write_todos`): the whole list, every time.
102
+ 'plan.updated': {
103
+ todos: { type: 'array', required: true },
104
+ },
105
+ 'step.completed': {
106
+ step_id: id(),
107
+ status: { type: 'string', required: true, oneOf: ['ok', 'error', 'skipped'] },
108
+ output: { type: 'object' },
109
+ error: text(4_000, false),
110
+ duration_ms: { type: 'number' },
111
+ },
112
+ message: {
113
+ message_id: id(false),
114
+ role: {
115
+ type: 'string',
116
+ required: true,
117
+ oneOf: ['assistant', 'user', 'system'],
118
+ },
119
+ text: text(),
120
+ },
121
+ 'message.delta': { message_id: id(), text: text() },
122
+ 'file.changed': {
123
+ path: text(1_000),
124
+ change: {
125
+ type: 'string',
126
+ required: true,
127
+ oneOf: ['added', 'modified', 'deleted', 'renamed'],
128
+ },
129
+ },
130
+ 'diff.ready': {
131
+ files: { type: 'number', required: true },
132
+ additions: { type: 'number' },
133
+ deletions: { type: 'number' },
134
+ },
135
+ 'check.result': {
136
+ name: text(200),
137
+ status: {
138
+ type: 'string',
139
+ required: true,
140
+ oneOf: ['pass', 'fail', 'skip'],
141
+ },
142
+ detail: text(8_000, false),
143
+ },
144
+ 'preview.ready': { url: text(2_000) },
145
+ // Written by core-api on the PARENT when a handoff starts and ends.
146
+ 'handoff.created': { child_run_id: id(), goal: text(2_000, false) },
147
+ 'handoff.finished': {
148
+ child_run_id: id(),
149
+ status: {
150
+ type: 'string',
151
+ required: true,
152
+ oneOf: ['completed', 'failed', 'cancelled'],
153
+ },
154
+ },
155
+ usage: {
156
+ model: text(200, false),
157
+ tokens_in: { type: 'number' },
158
+ tokens_out: { type: 'number' },
159
+ cost_usd: { type: 'number' },
160
+ },
161
+ log: {
162
+ level: {
163
+ type: 'string',
164
+ required: true,
165
+ oneOf: ['debug', 'info', 'warn', 'error'],
166
+ },
167
+ text: text(),
168
+ },
169
+ };
170
+ /** Events that end a run. The stream closes after it delivers one. */
171
+ exports.TERMINAL_EVENT_TYPES = new Set([
172
+ 'run.completed',
173
+ 'run.failed',
174
+ 'run.cancelled',
175
+ ]);
176
+ /**
177
+ * Types the service engine sends today that aren't runner events. Accepted on
178
+ * the lenient gate without being counted as unknown.
179
+ */
180
+ exports.LEGACY_EVENT_TYPES = new Set([
181
+ 'browser.live',
182
+ 'composio.authorization',
183
+ 'computer.busy',
184
+ 'computer.ready',
185
+ 'computer.requested',
186
+ 'computer.viewer.available',
187
+ 'computer.viewer.unavailable',
188
+ 'connector.execution.receipt',
189
+ 'run.capabilities_attached',
190
+ 'run.inference_overridden',
191
+ 'run.limit_reached',
192
+ 'run.llm_call',
193
+ 'run.model_fallback',
194
+ 'run.running',
195
+ 'run.starting',
196
+ 'run.tool_call',
197
+ 'run.tool_refused',
198
+ 'run.training_loaded',
199
+ 'setup.change',
200
+ 'ui.card',
201
+ 'workser_computer.conversation.gate',
202
+ 'workser_computer.route.selected',
203
+ ]);
204
+ /** Is this a well-formed runner event? The strict gate. */
205
+ function checkRunnerEvent(event) {
206
+ if (!event || typeof event !== 'object') {
207
+ return { ok: false, reason: 'An event must be an object.' };
208
+ }
209
+ if (typeof event.event_id !== 'string' || !event.event_id || event.event_id.length > 40) {
210
+ return { ok: false, reason: 'event_id must be a string of 1–40 characters.' };
211
+ }
212
+ if (!Number.isInteger(event.seq) || event.seq < 1) {
213
+ return { ok: false, reason: 'seq must be a positive whole number.' };
214
+ }
215
+ const shape = exports.RUN_EVENT_CATALOG[event.type];
216
+ if (!shape) {
217
+ return { ok: false, reason: `Unknown event type "${String(event.type)}".` };
218
+ }
219
+ const data = event.data ?? {};
220
+ if (typeof data !== 'object' || Array.isArray(data) || data === null) {
221
+ return { ok: false, reason: `${event.type}: data must be an object.` };
222
+ }
223
+ const size = byteLength(data);
224
+ if (size > exports.MAX_EVENT_DATA_BYTES) {
225
+ return {
226
+ ok: false,
227
+ reason: `${event.type}: data is ${size} bytes; the limit is ${exports.MAX_EVENT_DATA_BYTES}. Put large output in a file.`,
228
+ };
229
+ }
230
+ for (const [field, rule] of Object.entries(shape)) {
231
+ const value = data[field];
232
+ if (value === undefined || value === null) {
233
+ if (rule.required) {
234
+ return { ok: false, reason: `${event.type}: "${field}" is required.` };
235
+ }
236
+ continue;
237
+ }
238
+ const actual = Array.isArray(value) ? 'array' : typeof value;
239
+ if (actual !== rule.type) {
240
+ return {
241
+ ok: false,
242
+ reason: `${event.type}: "${field}" must be ${rule.type}, got ${actual}.`,
243
+ };
244
+ }
245
+ if (rule.type === 'number' && !Number.isFinite(value)) {
246
+ return { ok: false, reason: `${event.type}: "${field}" must be finite.` };
247
+ }
248
+ if (rule.oneOf && !rule.oneOf.includes(value)) {
249
+ return {
250
+ ok: false,
251
+ reason: `${event.type}: "${field}" must be one of ${rule.oneOf.join(', ')}.`,
252
+ };
253
+ }
254
+ if (rule.maxLength && value.length > rule.maxLength) {
255
+ return {
256
+ ok: false,
257
+ reason: `${event.type}: "${field}" is longer than ${rule.maxLength} characters.`,
258
+ };
259
+ }
260
+ }
261
+ return { ok: true };
262
+ }
263
+ /** The lenient gate: only the envelope is checked. */
264
+ function checkLegacyEvent(event) {
265
+ if (typeof event.event_type !== 'string' || !event.event_type || event.event_type.length > 80) {
266
+ return { ok: false, reason: 'event_type must be a string of 1–80 characters.' };
267
+ }
268
+ if (typeof event.event_id !== 'string' || !event.event_id || event.event_id.length > 40) {
269
+ return { ok: false, reason: 'event_id must be a string of 1–40 characters.' };
270
+ }
271
+ if (!Number.isInteger(event.seq)) {
272
+ return { ok: false, reason: 'seq must be a whole number.' };
273
+ }
274
+ return { ok: true };
275
+ }
276
+ /** A type neither in the catalog nor in the known legacy list. */
277
+ function unknownLegacyType(type) {
278
+ return !exports.RUN_EVENT_CATALOG[type] && !exports.LEGACY_EVENT_TYPES.has(type);
279
+ }
280
+ /**
281
+ * The run status a runner event moves the run to, or null when it doesn't.
282
+ * Status only moves forward: a terminal run never changes again.
283
+ */
284
+ function statusForRunnerEvent(type) {
285
+ switch (type) {
286
+ case 'run.started':
287
+ case 'run.resumed':
288
+ return 'RUNNING';
289
+ case 'run.completed':
290
+ return 'COMPLETED';
291
+ case 'run.failed':
292
+ return 'FAILED';
293
+ case 'run.cancelled':
294
+ return 'CANCELLED';
295
+ default:
296
+ return null;
297
+ }
298
+ }
299
+ /**
300
+ * Check a runner batch against the run's cursor (the last seq accepted).
301
+ *
302
+ * Events at or below the cursor are retries and are dropped here — their
303
+ * `event_id` is checked against the stored row by the caller. What remains
304
+ * must continue the cursor exactly: `cursor+1, cursor+2, …`. A gap means the
305
+ * runner lost an event, and it is told where to resend from rather than
306
+ * having the hole papered over.
307
+ */
308
+ function checkBatchSeq(cursor, events) {
309
+ const sorted = [...events].sort((a, b) => a.seq - b.seq);
310
+ const fresh = sorted.filter((one) => one.seq > cursor);
311
+ let expected = cursor + 1;
312
+ for (const one of fresh) {
313
+ if (one.seq !== expected) {
314
+ return {
315
+ ok: false,
316
+ expected,
317
+ reason: one.seq < expected
318
+ ? `seq ${one.seq} appears twice in this batch.`
319
+ : `seq ${one.seq} skips ahead; expected ${expected}. Resend from ${expected}.`,
320
+ };
321
+ }
322
+ expected += 1;
323
+ }
324
+ return { ok: true, fresh, nextCursor: expected - 1 };
325
+ }
326
+ // TextEncoder, not Buffer: the same file runs in browsers and in core-api.
327
+ const encoder = new TextEncoder();
328
+ function byteLength(value) {
329
+ try {
330
+ return encoder.encode(JSON.stringify(value)).length;
331
+ }
332
+ catch {
333
+ return Number.POSITIVE_INFINITY;
334
+ }
335
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./catalog.js"), exports);
18
+ __exportStar(require("./ordered-feed.js"), exports);
19
+ __exportStar(require("./types.js"), exports);
20
+ __exportStar(require("./actions.js"), exports);
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OrderedFeed = exports.FEED_PAGE = void 0;
4
+ exports.FEED_PAGE = 500;
5
+ class OrderedFeed {
6
+ fetchAfter;
7
+ emit;
8
+ onError;
9
+ last;
10
+ chain = Promise.resolve();
11
+ closed = false;
12
+ constructor(start, fetchAfter, emit, onError = () => undefined) {
13
+ this.fetchAfter = fetchAfter;
14
+ this.emit = emit;
15
+ this.onError = onError;
16
+ this.last = Math.max(0, Math.floor(start || 0));
17
+ }
18
+ /** The position of the last row emitted. */
19
+ get cursor() {
20
+ return this.last;
21
+ }
22
+ /**
23
+ * Replay everything after the start cursor. Call once, right after creating.
24
+ *
25
+ * `show`, when given, hides replayed rows it rejects while still moving the
26
+ * cursor past them — for the old `after_seq` resume, which skipped rows by
27
+ * the writer's own number. Live rows are never filtered.
28
+ */
29
+ start(show) {
30
+ return this.enqueue(() => this.drain(undefined, show));
31
+ }
32
+ /** A live row. Emitted in order; a skip ahead reads the gap first. */
33
+ offer(row) {
34
+ return this.enqueue(async () => {
35
+ const pos = row.pos;
36
+ if (pos == null || pos <= this.last)
37
+ return;
38
+ if (pos > this.last + 1) {
39
+ await this.drain(pos);
40
+ if (pos <= this.last)
41
+ return;
42
+ }
43
+ this.push(row);
44
+ });
45
+ }
46
+ /** Read anything after the cursor that live delivery didn't bring. */
47
+ catchUp() {
48
+ return this.enqueue(() => this.drain());
49
+ }
50
+ /** Stop emitting. Pending work finishes silently. */
51
+ close() {
52
+ this.closed = true;
53
+ }
54
+ push(row, show) {
55
+ if (this.closed || row.pos == null || row.pos <= this.last)
56
+ return;
57
+ this.last = row.pos;
58
+ if (show && !show(row))
59
+ return;
60
+ this.emit(row);
61
+ }
62
+ /** Emit stored rows after the cursor, up to `until` when given. */
63
+ async drain(until, show) {
64
+ for (;;) {
65
+ if (this.closed)
66
+ return;
67
+ const rows = await this.fetchAfter(this.last, exports.FEED_PAGE);
68
+ for (const row of rows) {
69
+ if (row.pos == null)
70
+ continue;
71
+ if (until !== undefined && row.pos > until)
72
+ return;
73
+ this.push(row, show);
74
+ }
75
+ if (rows.length < exports.FEED_PAGE)
76
+ return;
77
+ }
78
+ }
79
+ enqueue(work) {
80
+ const next = this.chain.then(async () => {
81
+ if (this.closed)
82
+ return;
83
+ try {
84
+ await work();
85
+ }
86
+ catch (error) {
87
+ this.onError(error);
88
+ }
89
+ });
90
+ this.chain = next;
91
+ return next;
92
+ }
93
+ }
94
+ exports.OrderedFeed = OrderedFeed;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ /**
3
+ * The shapes a run is made of, as core-api sends and accepts them.
4
+ *
5
+ * One shape per thing, with optional fields, rather than tagged unions:
6
+ * core-api builds without strictNullChecks, where a union doesn't narrow, and
7
+ * the same types are used on both sides of the wire.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.connectSse = exports.SseParser = void 0;
18
+ /**
19
+ * @workser/runtime — the run contract every Workser runner, screen and service
20
+ * speaks. See README.md for runner protocol v1.
21
+ */
22
+ __exportStar(require("./contract/index.js"), exports);
23
+ __exportStar(require("./runner/index.js"), exports);
24
+ __exportStar(require("./client/index.js"), exports);
25
+ var sse_js_1 = require("./internal/sse.js");
26
+ Object.defineProperty(exports, "SseParser", { enumerable: true, get: function () { return sse_js_1.SseParser; } });
27
+ Object.defineProperty(exports, "connectSse", { enumerable: true, get: function () { return sse_js_1.connectSse; } });
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AsyncQueue = void 0;
4
+ /** A push-to-pull bridge: callbacks push, an async iterator pulls. */
5
+ class AsyncQueue {
6
+ onReturn;
7
+ items = [];
8
+ waiters = [];
9
+ ended = false;
10
+ failure = null;
11
+ constructor(onReturn = () => undefined) {
12
+ this.onReturn = onReturn;
13
+ }
14
+ push(item) {
15
+ if (this.ended)
16
+ return;
17
+ const waiter = this.waiters.shift();
18
+ if (waiter)
19
+ waiter({ value: item, done: false });
20
+ else
21
+ this.items.push(item);
22
+ }
23
+ end(error) {
24
+ if (this.ended)
25
+ return;
26
+ this.ended = true;
27
+ if (error)
28
+ this.failure = error;
29
+ for (const waiter of this.waiters.splice(0)) {
30
+ waiter({ value: undefined, done: true });
31
+ }
32
+ }
33
+ next() {
34
+ const item = this.items.shift();
35
+ if (item !== undefined)
36
+ return Promise.resolve({ value: item, done: false });
37
+ if (this.ended) {
38
+ if (this.failure) {
39
+ const error = this.failure;
40
+ this.failure = null;
41
+ return Promise.reject(error);
42
+ }
43
+ return Promise.resolve({ value: undefined, done: true });
44
+ }
45
+ return new Promise((resolve) => this.waiters.push(resolve));
46
+ }
47
+ return() {
48
+ this.end();
49
+ this.onReturn();
50
+ return Promise.resolve({ value: undefined, done: true });
51
+ }
52
+ [Symbol.asyncIterator]() {
53
+ return this;
54
+ }
55
+ }
56
+ exports.AsyncQueue = AsyncQueue;