@posthog/agent 2.3.657 → 2.3.663
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/agent.js +7 -2
- package/dist/agent.js.map +1 -1
- package/dist/posthog-api.js +1 -1
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.d.ts +4 -0
- package/dist/server/agent-server.js +712 -4
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +720 -7
- package/dist/server/bin.cjs.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/codex/spawn.ts +9 -0
- package/src/agent.ts +1 -0
- package/src/server/agent-server.test.ts +241 -0
- package/src/server/agent-server.ts +55 -3
- package/src/server/bin.ts +12 -0
- package/src/server/event-stream-sender.test.ts +74 -0
- package/src/server/event-stream-sender.ts +101 -52
- package/src/server/streaming-upload.ts +160 -0
- package/src/server/types.ts +2 -0
- package/src/types.ts +2 -0
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
2
|
import type { Logger } from "../utils/logger";
|
|
3
|
+
import {
|
|
4
|
+
createNodeStreamingUpload,
|
|
5
|
+
type StreamingUpload,
|
|
6
|
+
type StreamingUploadFactory,
|
|
7
|
+
} from "./streaming-upload";
|
|
3
8
|
|
|
4
9
|
interface TaskRunEventStreamSenderConfig {
|
|
5
10
|
apiUrl: string;
|
|
@@ -13,12 +18,11 @@ interface TaskRunEventStreamSenderConfig {
|
|
|
13
18
|
retryDelayMs?: number;
|
|
14
19
|
requestTimeoutMs?: number;
|
|
15
20
|
stopTimeoutMs?: number;
|
|
16
|
-
maxBatchEvents?: number;
|
|
17
|
-
maxBatchBytes?: number;
|
|
18
21
|
maxEventBytes?: number;
|
|
19
22
|
maxStreamEvents?: number;
|
|
20
23
|
maxStreamBytes?: number;
|
|
21
24
|
streamWindowMs?: number;
|
|
25
|
+
createStreamingUpload?: StreamingUploadFactory;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
interface EventEnvelope {
|
|
@@ -32,21 +36,20 @@ interface IngestResponse {
|
|
|
32
36
|
|
|
33
37
|
interface ActiveStream {
|
|
34
38
|
abortController: AbortController;
|
|
35
|
-
|
|
39
|
+
upload: StreamingUpload;
|
|
36
40
|
responsePromise: Promise<Response>;
|
|
37
41
|
startedAtMs: number;
|
|
38
42
|
sentThroughSeq: number;
|
|
39
43
|
sentEvents: number;
|
|
40
44
|
sentBytes: number;
|
|
45
|
+
windowTimer: ReturnType<typeof setTimeout> | null;
|
|
41
46
|
}
|
|
42
47
|
|
|
43
|
-
type StreamingRequestInit = RequestInit & { duplex: "half" };
|
|
44
|
-
|
|
45
48
|
const DEFAULT_MAX_BUFFERED_EVENTS = 20_000;
|
|
46
49
|
const DEFAULT_MAX_STREAM_EVENTS = 900;
|
|
47
50
|
const DEFAULT_MAX_STREAM_BYTES = 4_000_000;
|
|
48
51
|
const DEFAULT_MAX_EVENT_BYTES = 900_000;
|
|
49
|
-
const
|
|
52
|
+
const DEFAULT_FLUSH_DELAY_MS = 0;
|
|
50
53
|
const DEFAULT_RETRY_DELAY_MS = 1_000;
|
|
51
54
|
const DEFAULT_REQUEST_TIMEOUT_MS = 10_000;
|
|
52
55
|
const DEFAULT_STOP_TIMEOUT_MS = 30_000;
|
|
@@ -59,17 +62,18 @@ export class TaskRunEventStreamSender {
|
|
|
59
62
|
private readonly maxStreamEvents: number;
|
|
60
63
|
private readonly maxStreamBytes: number;
|
|
61
64
|
private readonly maxEventBytes: number;
|
|
62
|
-
private readonly
|
|
65
|
+
private readonly flushDelayMs: number;
|
|
63
66
|
private readonly retryDelayMs: number;
|
|
64
67
|
private readonly requestTimeoutMs: number;
|
|
65
68
|
private readonly stopTimeoutMs: number;
|
|
66
69
|
private readonly streamWindowMs: number;
|
|
70
|
+
private readonly createStreamingUpload: StreamingUploadFactory;
|
|
67
71
|
private readonly encoder = new TextEncoder();
|
|
68
72
|
private sequence = 0;
|
|
69
73
|
private lastKnownAcceptedSeq = 0;
|
|
70
74
|
private bufferedEvents: EventEnvelope[] = [];
|
|
71
|
-
private
|
|
72
|
-
private
|
|
75
|
+
private flushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
76
|
+
private flushPromise: Promise<void> | null = null;
|
|
73
77
|
private streamClosePromise: Promise<void> | null = null;
|
|
74
78
|
private activeStream: ActiveStream | null = null;
|
|
75
79
|
private stopPromise: Promise<void> | null = null;
|
|
@@ -87,19 +91,17 @@ export class TaskRunEventStreamSender {
|
|
|
87
91
|
)}/runs/${encodeURIComponent(config.runId)}/event_stream/`;
|
|
88
92
|
this.maxBufferedEvents =
|
|
89
93
|
config.maxBufferedEvents ?? DEFAULT_MAX_BUFFERED_EVENTS;
|
|
90
|
-
this.maxStreamEvents =
|
|
91
|
-
|
|
92
|
-
config.maxBatchEvents ??
|
|
93
|
-
DEFAULT_MAX_STREAM_EVENTS;
|
|
94
|
-
this.maxStreamBytes =
|
|
95
|
-
config.maxStreamBytes ?? config.maxBatchBytes ?? DEFAULT_MAX_STREAM_BYTES;
|
|
94
|
+
this.maxStreamEvents = config.maxStreamEvents ?? DEFAULT_MAX_STREAM_EVENTS;
|
|
95
|
+
this.maxStreamBytes = config.maxStreamBytes ?? DEFAULT_MAX_STREAM_BYTES;
|
|
96
96
|
this.maxEventBytes = config.maxEventBytes ?? DEFAULT_MAX_EVENT_BYTES;
|
|
97
|
-
this.
|
|
97
|
+
this.flushDelayMs = config.flushDelayMs ?? DEFAULT_FLUSH_DELAY_MS;
|
|
98
98
|
this.retryDelayMs = config.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
99
99
|
this.requestTimeoutMs =
|
|
100
100
|
config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
101
101
|
this.stopTimeoutMs = config.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS;
|
|
102
102
|
this.streamWindowMs = config.streamWindowMs ?? DEFAULT_STREAM_WINDOW_MS;
|
|
103
|
+
this.createStreamingUpload =
|
|
104
|
+
config.createStreamingUpload ?? createNodeStreamingUpload;
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
enqueue(event: Record<string, unknown>): void {
|
|
@@ -114,7 +116,7 @@ export class TaskRunEventStreamSender {
|
|
|
114
116
|
event,
|
|
115
117
|
};
|
|
116
118
|
this.bufferedEvents.push(envelope);
|
|
117
|
-
this.
|
|
119
|
+
this.scheduleFlush();
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
async stop(): Promise<void> {
|
|
@@ -125,20 +127,20 @@ export class TaskRunEventStreamSender {
|
|
|
125
127
|
|
|
126
128
|
this.stopped = true;
|
|
127
129
|
|
|
128
|
-
if (this.
|
|
129
|
-
clearTimeout(this.
|
|
130
|
-
this.
|
|
130
|
+
if (this.flushTimer) {
|
|
131
|
+
clearTimeout(this.flushTimer);
|
|
132
|
+
this.flushTimer = null;
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
this.stopPromise = this.drainForStop();
|
|
134
136
|
await this.stopPromise;
|
|
135
137
|
}
|
|
136
138
|
|
|
137
|
-
private
|
|
138
|
-
if (this.
|
|
139
|
+
private scheduleFlush(delayMs = this.flushDelayMs): void {
|
|
140
|
+
if (this.flushTimer || this.flushPromise || this.stopped) return;
|
|
139
141
|
|
|
140
|
-
this.
|
|
141
|
-
this.
|
|
142
|
+
this.flushTimer = setTimeout(() => {
|
|
143
|
+
this.flushTimer = null;
|
|
142
144
|
void this.flush();
|
|
143
145
|
}, delayMs);
|
|
144
146
|
}
|
|
@@ -180,8 +182,8 @@ export class TaskRunEventStreamSender {
|
|
|
180
182
|
}
|
|
181
183
|
|
|
182
184
|
private async flush(): Promise<boolean> {
|
|
183
|
-
if (this.
|
|
184
|
-
await this.
|
|
185
|
+
if (this.flushPromise) {
|
|
186
|
+
await this.flushPromise.catch(() => undefined);
|
|
185
187
|
}
|
|
186
188
|
|
|
187
189
|
if (this.bufferedEvents.length === 0) {
|
|
@@ -189,11 +191,11 @@ export class TaskRunEventStreamSender {
|
|
|
189
191
|
}
|
|
190
192
|
|
|
191
193
|
const previousBufferLength = this.bufferedEvents.length;
|
|
192
|
-
const
|
|
193
|
-
this.
|
|
194
|
+
const flushPromise = this.flushBufferedEvents();
|
|
195
|
+
this.flushPromise = flushPromise;
|
|
194
196
|
|
|
195
197
|
try {
|
|
196
|
-
await
|
|
198
|
+
await flushPromise;
|
|
197
199
|
return this.bufferedEvents.length < previousBufferLength;
|
|
198
200
|
} catch (error) {
|
|
199
201
|
this.config.logger.warn(
|
|
@@ -202,20 +204,20 @@ export class TaskRunEventStreamSender {
|
|
|
202
204
|
);
|
|
203
205
|
await this.abortActiveStream();
|
|
204
206
|
if (!this.stopped) {
|
|
205
|
-
this.
|
|
207
|
+
this.scheduleFlush(this.retryDelayMs);
|
|
206
208
|
}
|
|
207
209
|
return false;
|
|
208
210
|
} finally {
|
|
209
|
-
if (this.
|
|
210
|
-
this.
|
|
211
|
+
if (this.flushPromise === flushPromise) {
|
|
212
|
+
this.flushPromise = null;
|
|
211
213
|
}
|
|
212
214
|
if (!this.stopped && this.hasUnwrittenBufferedEvents()) {
|
|
213
|
-
this.
|
|
215
|
+
this.scheduleFlush(0);
|
|
214
216
|
}
|
|
215
217
|
}
|
|
216
218
|
}
|
|
217
219
|
|
|
218
|
-
private async
|
|
220
|
+
private async flushBufferedEvents(): Promise<void> {
|
|
219
221
|
while (true) {
|
|
220
222
|
const stream = await this.ensureActiveStream();
|
|
221
223
|
const nextEvent = this.bufferedEvents.find(
|
|
@@ -232,7 +234,7 @@ export class TaskRunEventStreamSender {
|
|
|
232
234
|
continue;
|
|
233
235
|
}
|
|
234
236
|
|
|
235
|
-
await stream.
|
|
237
|
+
await stream.upload.write(this.encoder.encode(line));
|
|
236
238
|
stream.sentThroughSeq = nextEvent.seq;
|
|
237
239
|
stream.sentEvents += 1;
|
|
238
240
|
stream.sentBytes += lineBytes;
|
|
@@ -254,7 +256,7 @@ export class TaskRunEventStreamSender {
|
|
|
254
256
|
(event) => event.seq > stream.sentThroughSeq,
|
|
255
257
|
);
|
|
256
258
|
if (hasUnwrittenEvents) {
|
|
257
|
-
await this.
|
|
259
|
+
await this.flushBufferedEvents();
|
|
258
260
|
continue;
|
|
259
261
|
}
|
|
260
262
|
|
|
@@ -272,7 +274,7 @@ export class TaskRunEventStreamSender {
|
|
|
272
274
|
continue;
|
|
273
275
|
}
|
|
274
276
|
|
|
275
|
-
await stream.
|
|
277
|
+
await stream.upload.write(this.encoder.encode(line));
|
|
276
278
|
stream.sentBytes += lineBytes;
|
|
277
279
|
return;
|
|
278
280
|
}
|
|
@@ -310,32 +312,77 @@ export class TaskRunEventStreamSender {
|
|
|
310
312
|
|
|
311
313
|
await this.syncSequenceWithServer();
|
|
312
314
|
|
|
313
|
-
const bodyStream = new TransformStream<Uint8Array, Uint8Array>();
|
|
314
315
|
const abortController = new AbortController();
|
|
315
|
-
const
|
|
316
|
-
|
|
316
|
+
const upload = this.createStreamingUpload({
|
|
317
|
+
url: this.ingestUrl,
|
|
317
318
|
headers: this.buildHeaders(),
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
duplex: "half",
|
|
321
|
-
};
|
|
322
|
-
const responsePromise = fetch(this.ingestUrl, requestInit);
|
|
319
|
+
abortController,
|
|
320
|
+
});
|
|
323
321
|
const activeStream: ActiveStream = {
|
|
324
322
|
abortController,
|
|
325
|
-
|
|
326
|
-
responsePromise,
|
|
323
|
+
upload,
|
|
324
|
+
responsePromise: upload.responsePromise,
|
|
327
325
|
startedAtMs: Date.now(),
|
|
328
326
|
sentThroughSeq: this.lastKnownAcceptedSeq,
|
|
329
327
|
sentEvents: 0,
|
|
330
328
|
sentBytes: 0,
|
|
329
|
+
windowTimer: null,
|
|
331
330
|
};
|
|
332
331
|
this.activeStream = activeStream;
|
|
333
|
-
|
|
332
|
+
this.scheduleStreamWindowClose(activeStream);
|
|
333
|
+
upload.responsePromise.catch((error) => {
|
|
334
334
|
void this.handleActiveStreamResponseFailure(activeStream, error);
|
|
335
335
|
});
|
|
336
336
|
return activeStream;
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
private scheduleStreamWindowClose(
|
|
340
|
+
stream: ActiveStream,
|
|
341
|
+
delayOverrideMs?: number,
|
|
342
|
+
): void {
|
|
343
|
+
this.clearStreamWindowClose(stream);
|
|
344
|
+
// Rotate long-lived uploads even when the agent goes idle; this is a
|
|
345
|
+
// transport boundary, not a batching window.
|
|
346
|
+
const delayMs =
|
|
347
|
+
delayOverrideMs ??
|
|
348
|
+
Math.max(0, stream.startedAtMs + this.streamWindowMs - Date.now());
|
|
349
|
+
stream.windowTimer = setTimeout(() => {
|
|
350
|
+
stream.windowTimer = null;
|
|
351
|
+
void this.closeExpiredStream(stream);
|
|
352
|
+
}, delayMs);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private clearStreamWindowClose(stream: ActiveStream): void {
|
|
356
|
+
if (!stream.windowTimer) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
clearTimeout(stream.windowTimer);
|
|
360
|
+
stream.windowTimer = null;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private async closeExpiredStream(stream: ActiveStream): Promise<void> {
|
|
364
|
+
if (this.activeStream !== stream || this.stopped) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (this.flushPromise) {
|
|
369
|
+
this.scheduleStreamWindowClose(stream, 50);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
await this.closeActiveStream();
|
|
375
|
+
} catch (error) {
|
|
376
|
+
this.config.logger.warn(
|
|
377
|
+
"Task run event ingest stream window close failed",
|
|
378
|
+
this.describeError(error),
|
|
379
|
+
);
|
|
380
|
+
if (!this.stopped && this.bufferedEvents.length > 0) {
|
|
381
|
+
this.scheduleFlush(this.retryDelayMs);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
339
386
|
private async handleActiveStreamResponseFailure(
|
|
340
387
|
stream: ActiveStream,
|
|
341
388
|
error: unknown,
|
|
@@ -357,7 +404,7 @@ export class TaskRunEventStreamSender {
|
|
|
357
404
|
);
|
|
358
405
|
}
|
|
359
406
|
if (!this.stopped && this.bufferedEvents.length > 0) {
|
|
360
|
-
this.
|
|
407
|
+
this.scheduleFlush(this.retryDelayMs);
|
|
361
408
|
}
|
|
362
409
|
}
|
|
363
410
|
|
|
@@ -377,6 +424,7 @@ export class TaskRunEventStreamSender {
|
|
|
377
424
|
try {
|
|
378
425
|
await closePromise;
|
|
379
426
|
} finally {
|
|
427
|
+
this.clearStreamWindowClose(stream);
|
|
380
428
|
if (this.activeStream === stream) {
|
|
381
429
|
this.activeStream = null;
|
|
382
430
|
}
|
|
@@ -388,7 +436,7 @@ export class TaskRunEventStreamSender {
|
|
|
388
436
|
|
|
389
437
|
private async closeStream(stream: ActiveStream): Promise<void> {
|
|
390
438
|
try {
|
|
391
|
-
await stream.
|
|
439
|
+
await stream.upload.close();
|
|
392
440
|
} catch (error) {
|
|
393
441
|
stream.abortController.abort();
|
|
394
442
|
this.sequenceSynced = false;
|
|
@@ -418,10 +466,11 @@ export class TaskRunEventStreamSender {
|
|
|
418
466
|
}
|
|
419
467
|
|
|
420
468
|
stream.abortController.abort();
|
|
469
|
+
this.clearStreamWindowClose(stream);
|
|
421
470
|
try {
|
|
422
|
-
await stream.
|
|
471
|
+
await stream.upload.abort();
|
|
423
472
|
} catch {
|
|
424
|
-
// The
|
|
473
|
+
// The upload may already be closed by the transport after the abort.
|
|
425
474
|
} finally {
|
|
426
475
|
if (this.activeStream === stream) {
|
|
427
476
|
this.activeStream = null;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import {
|
|
3
|
+
type ClientRequest,
|
|
4
|
+
request as httpRequest,
|
|
5
|
+
type IncomingHttpHeaders,
|
|
6
|
+
} from "node:http";
|
|
7
|
+
import { request as httpsRequest } from "node:https";
|
|
8
|
+
import { URL } from "node:url";
|
|
9
|
+
|
|
10
|
+
export interface StreamingUpload {
|
|
11
|
+
write(chunk: Uint8Array): Promise<void>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
abort(): Promise<void>;
|
|
14
|
+
responsePromise: Promise<Response>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface StreamingUploadFactoryInput {
|
|
18
|
+
url: string;
|
|
19
|
+
headers: Record<string, string>;
|
|
20
|
+
abortController: AbortController;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type StreamingUploadFactory = (
|
|
24
|
+
input: StreamingUploadFactoryInput,
|
|
25
|
+
) => StreamingUpload;
|
|
26
|
+
|
|
27
|
+
function headersFromIncoming(headers: IncomingHttpHeaders): Headers {
|
|
28
|
+
const result = new Headers();
|
|
29
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
30
|
+
if (value === undefined) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (Array.isArray(value)) {
|
|
34
|
+
for (const item of value) {
|
|
35
|
+
result.append(name, item);
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
result.set(name, String(value));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function abortError(): Error {
|
|
45
|
+
const error = new Error("aborted");
|
|
46
|
+
error.name = "AbortError";
|
|
47
|
+
return error;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function writeRequestChunk(
|
|
51
|
+
request: ClientRequest,
|
|
52
|
+
chunk: Uint8Array,
|
|
53
|
+
): Promise<void> {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const onError = (error: Error): void => {
|
|
56
|
+
request.off("error", onError);
|
|
57
|
+
reject(error);
|
|
58
|
+
};
|
|
59
|
+
request.once("error", onError);
|
|
60
|
+
request.write(Buffer.from(chunk), (error?: Error | null) => {
|
|
61
|
+
request.off("error", onError);
|
|
62
|
+
if (error) {
|
|
63
|
+
reject(error);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
resolve();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function closeRequest(request: ClientRequest): Promise<void> {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const onError = (error: Error): void => {
|
|
74
|
+
request.off("error", onError);
|
|
75
|
+
reject(error);
|
|
76
|
+
};
|
|
77
|
+
request.once("error", onError);
|
|
78
|
+
request.end(() => {
|
|
79
|
+
request.off("error", onError);
|
|
80
|
+
resolve();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function createNodeStreamingUpload({
|
|
86
|
+
url,
|
|
87
|
+
headers,
|
|
88
|
+
abortController,
|
|
89
|
+
}: StreamingUploadFactoryInput): StreamingUpload {
|
|
90
|
+
const parsedUrl = new URL(url);
|
|
91
|
+
const requestFactory =
|
|
92
|
+
parsedUrl.protocol === "https:"
|
|
93
|
+
? httpsRequest
|
|
94
|
+
: parsedUrl.protocol === "http:"
|
|
95
|
+
? httpRequest
|
|
96
|
+
: undefined;
|
|
97
|
+
if (!requestFactory) {
|
|
98
|
+
throw new Error(`Unsupported event ingest protocol: ${parsedUrl.protocol}`);
|
|
99
|
+
}
|
|
100
|
+
const request = requestFactory(parsedUrl, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
let closed = false;
|
|
106
|
+
const responsePromise = new Promise<Response>((resolve, reject) => {
|
|
107
|
+
request.on("response", (response) => {
|
|
108
|
+
const chunks: Buffer[] = [];
|
|
109
|
+
response.on("data", (chunk: Buffer | string) => {
|
|
110
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
111
|
+
});
|
|
112
|
+
response.on("end", () => {
|
|
113
|
+
resolve(
|
|
114
|
+
new Response(Buffer.concat(chunks), {
|
|
115
|
+
status: response.statusCode ?? 0,
|
|
116
|
+
statusText: response.statusMessage,
|
|
117
|
+
headers: headersFromIncoming(response.headers),
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
response.on("error", reject);
|
|
122
|
+
});
|
|
123
|
+
request.on("error", reject);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const abortRequest = (): void => {
|
|
127
|
+
closed = true;
|
|
128
|
+
if (!request.destroyed) {
|
|
129
|
+
request.destroy(abortError());
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
abortController.signal.addEventListener("abort", abortRequest, {
|
|
133
|
+
once: true,
|
|
134
|
+
});
|
|
135
|
+
void responsePromise
|
|
136
|
+
.finally(() => {
|
|
137
|
+
abortController.signal.removeEventListener("abort", abortRequest);
|
|
138
|
+
})
|
|
139
|
+
.catch(() => undefined);
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
async write(chunk: Uint8Array): Promise<void> {
|
|
143
|
+
if (closed) {
|
|
144
|
+
throw new Error("Cannot write to closed event ingest stream");
|
|
145
|
+
}
|
|
146
|
+
await writeRequestChunk(request, chunk);
|
|
147
|
+
},
|
|
148
|
+
async close(): Promise<void> {
|
|
149
|
+
if (closed) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
closed = true;
|
|
153
|
+
await closeRequest(request);
|
|
154
|
+
},
|
|
155
|
+
async abort(): Promise<void> {
|
|
156
|
+
abortRequest();
|
|
157
|
+
},
|
|
158
|
+
responsePromise,
|
|
159
|
+
};
|
|
160
|
+
}
|
package/src/server/types.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface AgentServerConfig {
|
|
|
15
15
|
apiKey: string;
|
|
16
16
|
projectId: number;
|
|
17
17
|
jwtPublicKey: string; // RS256 public key for JWT verification
|
|
18
|
+
eventIngestToken?: string;
|
|
19
|
+
eventIngestStreamWindowMs?: number;
|
|
18
20
|
mode: AgentMode;
|
|
19
21
|
taskId: string;
|
|
20
22
|
runId: string;
|
package/src/types.ts
CHANGED
|
@@ -125,6 +125,8 @@ export interface TaskExecutionOptions {
|
|
|
125
125
|
processCallbacks?: ProcessSpawnedCallback;
|
|
126
126
|
/** Callback invoked when the agent calls the create_output tool for structured output */
|
|
127
127
|
onStructuredOutput?: (output: Record<string, unknown>) => Promise<void>;
|
|
128
|
+
/** Additional directories the agent process can access beyond cwd. */
|
|
129
|
+
additionalDirectories?: string[];
|
|
128
130
|
}
|
|
129
131
|
|
|
130
132
|
export type LogLevel = "debug" | "info" | "warn" | "error";
|