@oh-my-pi/pi-agent-core 14.5.13 → 14.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -1
- package/package.json +4 -4
- package/src/agent-loop.ts +93 -30
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [14.6.0] - 2026-05-02
|
|
6
|
+
### Fixed
|
|
7
|
+
|
|
8
|
+
- Fixed request cancellation before provider events by emitting an aborted assistant message and ending the stream with `stopReason: "aborted"`
|
|
9
|
+
|
|
5
10
|
## [14.5.10] - 2026-04-30
|
|
6
11
|
|
|
7
12
|
### Added
|
|
@@ -328,4 +333,4 @@ Initial release under @oh-my-pi scope. See previous releases at [badlogic/pi-mon
|
|
|
328
333
|
|
|
329
334
|
- `Agent` constructor now has all options optional (empty options use defaults).
|
|
330
335
|
|
|
331
|
-
- `queueMessage()` is now synchronous (no longer returns a Promise).
|
|
336
|
+
- `queueMessage()` is now synchronous (no longer returns a Promise).
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-agent-core",
|
|
4
|
-
"version": "14.
|
|
4
|
+
"version": "14.6.0",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"fmt": "biome format --write ."
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@oh-my-pi/pi-ai": "14.
|
|
39
|
-
"@oh-my-pi/pi-natives": "14.
|
|
40
|
-
"@oh-my-pi/pi-utils": "14.
|
|
38
|
+
"@oh-my-pi/pi-ai": "14.6.0",
|
|
39
|
+
"@oh-my-pi/pi-natives": "14.6.0",
|
|
40
|
+
"@oh-my-pi/pi-utils": "14.6.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@sinclair/typebox": "^0.34.49",
|
package/src/agent-loop.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import {
|
|
6
6
|
type AssistantMessage,
|
|
7
|
+
type AssistantMessageEvent,
|
|
7
8
|
type Context,
|
|
8
9
|
EventStream,
|
|
9
10
|
streamSimple,
|
|
@@ -348,38 +349,23 @@ async function streamAssistantResponse(
|
|
|
348
349
|
let partialMessage: AssistantMessage | null = null;
|
|
349
350
|
let addedPartial = false;
|
|
350
351
|
|
|
351
|
-
|
|
352
|
+
const responseIterator = response[Symbol.asyncIterator]();
|
|
353
|
+
while (true) {
|
|
354
|
+
const read = await readResponseEvent(responseIterator, signal);
|
|
355
|
+
if (read.type === "aborted") {
|
|
356
|
+
return emitAbortedAssistantMessage(partialMessage, addedPartial, context, config, stream);
|
|
357
|
+
}
|
|
358
|
+
if (read.type === "error") {
|
|
359
|
+
throw read.error;
|
|
360
|
+
}
|
|
361
|
+
if (read.result.done) {
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const event = read.result.value;
|
|
352
366
|
// Check for abort signal before processing each event
|
|
353
367
|
if (signal?.aborted) {
|
|
354
|
-
|
|
355
|
-
const abortedMessage: AssistantMessage = partialMessage
|
|
356
|
-
? { ...partialMessage, stopReason: "aborted", errorMessage }
|
|
357
|
-
: {
|
|
358
|
-
role: "assistant",
|
|
359
|
-
content: [],
|
|
360
|
-
api: config.model.api,
|
|
361
|
-
provider: config.model.provider,
|
|
362
|
-
model: config.model.id,
|
|
363
|
-
usage: {
|
|
364
|
-
input: 0,
|
|
365
|
-
output: 0,
|
|
366
|
-
cacheRead: 0,
|
|
367
|
-
cacheWrite: 0,
|
|
368
|
-
totalTokens: 0,
|
|
369
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
370
|
-
},
|
|
371
|
-
stopReason: "aborted",
|
|
372
|
-
errorMessage,
|
|
373
|
-
timestamp: Date.now(),
|
|
374
|
-
};
|
|
375
|
-
if (addedPartial) {
|
|
376
|
-
context.messages[context.messages.length - 1] = abortedMessage;
|
|
377
|
-
} else {
|
|
378
|
-
context.messages.push(abortedMessage);
|
|
379
|
-
stream.push({ type: "message_start", message: { ...abortedMessage } });
|
|
380
|
-
}
|
|
381
|
-
stream.push({ type: "message_end", message: abortedMessage });
|
|
382
|
-
return abortedMessage;
|
|
368
|
+
return emitAbortedAssistantMessage(partialMessage, addedPartial, context, config, stream);
|
|
383
369
|
}
|
|
384
370
|
|
|
385
371
|
switch (event.type) {
|
|
@@ -434,6 +420,83 @@ async function streamAssistantResponse(
|
|
|
434
420
|
return await response.result();
|
|
435
421
|
}
|
|
436
422
|
|
|
423
|
+
type ResponseEventRead =
|
|
424
|
+
| { type: "event"; result: IteratorResult<AssistantMessageEvent> }
|
|
425
|
+
| { type: "error"; error: unknown }
|
|
426
|
+
| { type: "aborted" };
|
|
427
|
+
|
|
428
|
+
async function readResponseEvent(
|
|
429
|
+
iterator: AsyncIterator<AssistantMessageEvent>,
|
|
430
|
+
signal: AbortSignal | undefined,
|
|
431
|
+
): Promise<ResponseEventRead> {
|
|
432
|
+
if (!signal) {
|
|
433
|
+
return { type: "event", result: await iterator.next() };
|
|
434
|
+
}
|
|
435
|
+
if (signal.aborted) {
|
|
436
|
+
const returnPromise = iterator.return?.();
|
|
437
|
+
if (returnPromise) void returnPromise.catch(() => {});
|
|
438
|
+
return { type: "aborted" };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const { promise: abortPromise, resolve: resolveAbort } = Promise.withResolvers<ResponseEventRead>();
|
|
442
|
+
const onAbort = () => resolveAbort({ type: "aborted" });
|
|
443
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
444
|
+
|
|
445
|
+
const eventPromise = iterator.next().then(
|
|
446
|
+
result => ({ type: "event" as const, result }),
|
|
447
|
+
error => ({ type: "error" as const, error }),
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
try {
|
|
451
|
+
const read = await Promise.race([eventPromise, abortPromise]);
|
|
452
|
+
if (read.type === "aborted") {
|
|
453
|
+
const returnPromise = iterator.return?.();
|
|
454
|
+
if (returnPromise) void returnPromise.catch(() => {});
|
|
455
|
+
}
|
|
456
|
+
return read;
|
|
457
|
+
} finally {
|
|
458
|
+
signal.removeEventListener("abort", onAbort);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function emitAbortedAssistantMessage(
|
|
463
|
+
partialMessage: AssistantMessage | null,
|
|
464
|
+
addedPartial: boolean,
|
|
465
|
+
context: AgentContext,
|
|
466
|
+
config: AgentLoopConfig,
|
|
467
|
+
stream: EventStream<AgentEvent, AgentMessage[]>,
|
|
468
|
+
): AssistantMessage {
|
|
469
|
+
const errorMessage = "Request was aborted";
|
|
470
|
+
const abortedMessage: AssistantMessage = partialMessage
|
|
471
|
+
? { ...partialMessage, stopReason: "aborted", errorMessage }
|
|
472
|
+
: {
|
|
473
|
+
role: "assistant",
|
|
474
|
+
content: [],
|
|
475
|
+
api: config.model.api,
|
|
476
|
+
provider: config.model.provider,
|
|
477
|
+
model: config.model.id,
|
|
478
|
+
usage: {
|
|
479
|
+
input: 0,
|
|
480
|
+
output: 0,
|
|
481
|
+
cacheRead: 0,
|
|
482
|
+
cacheWrite: 0,
|
|
483
|
+
totalTokens: 0,
|
|
484
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
485
|
+
},
|
|
486
|
+
stopReason: "aborted",
|
|
487
|
+
errorMessage,
|
|
488
|
+
timestamp: Date.now(),
|
|
489
|
+
};
|
|
490
|
+
if (addedPartial) {
|
|
491
|
+
context.messages[context.messages.length - 1] = abortedMessage;
|
|
492
|
+
} else {
|
|
493
|
+
context.messages.push(abortedMessage);
|
|
494
|
+
stream.push({ type: "message_start", message: { ...abortedMessage } });
|
|
495
|
+
}
|
|
496
|
+
stream.push({ type: "message_end", message: abortedMessage });
|
|
497
|
+
return abortedMessage;
|
|
498
|
+
}
|
|
499
|
+
|
|
437
500
|
/**
|
|
438
501
|
* Execute tool calls from an assistant message.
|
|
439
502
|
*/
|