@oh-my-pi/pi-agent-core 13.14.2 → 13.15.2

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 (2) hide show
  1. package/package.json +3 -3
  2. package/src/agent-loop.ts +29 -35
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": "13.14.2",
4
+ "version": "13.15.2",
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",
@@ -31,8 +31,8 @@
31
31
  "test": "bun test"
32
32
  },
33
33
  "dependencies": {
34
- "@oh-my-pi/pi-ai": "13.14.2",
35
- "@oh-my-pi/pi-utils": "13.14.2"
34
+ "@oh-my-pi/pi-ai": "13.15.2",
35
+ "@oh-my-pi/pi-utils": "13.15.2"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@sinclair/typebox": "^0.34",
package/src/agent-loop.ts CHANGED
@@ -196,7 +196,6 @@ async function runLoop(
196
196
  // Outer loop: continues when queued follow-up messages arrive after agent would stop
197
197
  while (true) {
198
198
  let hasMoreToolCalls = true;
199
- let steeringAfterTools: AgentMessage[] | null = null;
200
199
 
201
200
  // Inner loop: process tool calls and steering messages
202
201
  while (hasMoreToolCalls || pendingMessages.length > 0) {
@@ -225,6 +224,7 @@ async function runLoop(
225
224
  // Stream assistant response
226
225
  const message = await streamAssistantResponse(currentContext, config, signal, stream, streamFn);
227
226
  newMessages.push(message);
227
+ let steeringMessagesFromExecution: AgentMessage[] | undefined;
228
228
 
229
229
  if (message.stopReason === "error" || message.stopReason === "aborted") {
230
230
  // Create placeholder tool results for any tool calls in the aborted message
@@ -250,19 +250,20 @@ async function runLoop(
250
250
 
251
251
  const toolResults: ToolResultMessage[] = [];
252
252
  if (hasMoreToolCalls) {
253
- const toolExecution = await executeToolCalls(
253
+ const executionResult = await executeToolCalls(
254
254
  currentContext.tools,
255
255
  message,
256
256
  signal,
257
257
  stream,
258
258
  config.getSteeringMessages,
259
- config.getToolContext,
260
259
  config.interruptMode,
260
+ config.getToolContext,
261
261
  config.transformToolCallArguments,
262
262
  config.intentTracing,
263
263
  );
264
- toolResults.push(...toolExecution.toolResults);
265
- steeringAfterTools = toolExecution.steeringMessages ?? null;
264
+
265
+ toolResults.push(...executionResult.toolResults);
266
+ steeringMessagesFromExecution = executionResult.steeringMessages;
266
267
 
267
268
  for (const result of toolResults) {
268
269
  currentContext.messages.push(result);
@@ -272,13 +273,7 @@ async function runLoop(
272
273
 
273
274
  stream.push({ type: "turn_end", message, toolResults });
274
275
 
275
- // Get steering messages after turn completes
276
- if (steeringAfterTools && steeringAfterTools.length > 0) {
277
- pendingMessages = steeringAfterTools;
278
- steeringAfterTools = null;
279
- } else {
280
- pendingMessages = (await config.getSteeringMessages?.()) || [];
281
- }
276
+ pendingMessages = steeringMessagesFromExecution ?? ((await config.getSteeringMessages?.()) || []);
282
277
  }
283
278
 
284
279
  // Agent would stop here. Check for follow-up messages.
@@ -433,25 +428,37 @@ async function executeToolCalls(
433
428
  signal: AbortSignal | undefined,
434
429
  stream: EventStream<AgentEvent, AgentMessage[]>,
435
430
  getSteeringMessages?: AgentLoopConfig["getSteeringMessages"],
436
- getToolContext?: AgentLoopConfig["getToolContext"],
437
431
  interruptMode: AgentLoopConfig["interruptMode"] = "immediate",
432
+ getToolContext?: AgentLoopConfig["getToolContext"],
438
433
  transformToolCallArguments?: AgentLoopConfig["transformToolCallArguments"],
439
434
  intentTracing?: AgentLoopConfig["intentTracing"],
440
435
  ): Promise<{ toolResults: ToolResultMessage[]; steeringMessages?: AgentMessage[] }> {
441
436
  type ToolCallContent = Extract<AssistantMessage["content"][number], { type: "toolCall" }>;
442
437
  const toolCalls = assistantMessage.content.filter((c): c is ToolCallContent => c.type === "toolCall");
443
438
  const emittedToolResults: ToolResultMessage[] = [];
444
- let steeringMessages: AgentMessage[] | undefined;
445
- const shouldInterruptImmediately = interruptMode !== "wait";
446
439
  const toolCallInfos = toolCalls.map(call => ({ id: call.id, name: call.name }));
447
440
  const batchId = `${assistantMessage.timestamp ?? Date.now()}_${toolCalls[0]?.id ?? "batch"}`;
441
+ const shouldInterruptImmediately = interruptMode !== "wait";
448
442
  const steeringAbortController = new AbortController();
449
443
  const toolSignal = signal
450
444
  ? AbortSignal.any([signal, steeringAbortController.signal])
451
445
  : steeringAbortController.signal;
452
446
  const interruptState = { triggered: false };
447
+ let steeringMessages: AgentMessage[] | undefined;
453
448
  let steeringCheck: Promise<void> | null = null;
454
449
 
450
+ const records = toolCalls.map(toolCall => ({
451
+ toolCall,
452
+ tool: tools?.find(t => t.name === toolCall.name),
453
+ args: toolCall.arguments as Record<string, unknown>,
454
+ started: false,
455
+ result: undefined as AgentToolResult<any> | undefined,
456
+ isError: false,
457
+ skipped: false,
458
+ toolResultMessage: undefined as ToolResultMessage | undefined,
459
+ resultEmitted: false,
460
+ }));
461
+
455
462
  const checkSteering = async (): Promise<void> => {
456
463
  if (!shouldInterruptImmediately || !getSteeringMessages || interruptState.triggered) {
457
464
  return;
@@ -473,18 +480,6 @@ async function executeToolCalls(
473
480
  await steeringCheck;
474
481
  };
475
482
 
476
- const records = toolCalls.map(toolCall => ({
477
- toolCall,
478
- tool: tools?.find(t => t.name === toolCall.name),
479
- args: toolCall.arguments as Record<string, unknown>,
480
- started: false,
481
- result: undefined as AgentToolResult<any> | undefined,
482
- isError: false,
483
- skipped: false,
484
- toolResultMessage: undefined as ToolResultMessage | undefined,
485
- resultEmitted: false,
486
- }));
487
-
488
483
  const emitToolResult = (record: (typeof records)[number], result: AgentToolResult<any>, isError: boolean): void => {
489
484
  if (record.resultEmitted) return;
490
485
  const { toolCall } = record;
@@ -578,7 +573,6 @@ async function executeToolCalls(
578
573
  transformToolCallArguments ? transformToolCallArguments(effectiveArgs, toolCall.name) : effectiveArgs,
579
574
  tool.nonAbortable ? undefined : toolSignal,
580
575
  partialResult => {
581
- if (interruptState.triggered) return;
582
576
  stream.push({
583
577
  type: "tool_execution_update",
584
578
  toolCallId: toolCall.id,
@@ -637,13 +631,6 @@ async function executeToolCalls(
637
631
  return { toolResults: emittedToolResults, steeringMessages };
638
632
  }
639
633
 
640
- function createSkippedToolResult(): AgentToolResult<any> {
641
- return {
642
- content: [{ type: "text", text: "Skipped due to queued user message." }],
643
- details: {},
644
- };
645
- }
646
-
647
634
  /**
648
635
  * Create a tool result for a tool call that was aborted or errored before execution.
649
636
  * Maintains the tool_use/tool_result pairing required by the API.
@@ -690,3 +677,10 @@ function createAbortedToolResult(
690
677
 
691
678
  return toolResultMessage;
692
679
  }
680
+
681
+ function createSkippedToolResult(): AgentToolResult<any> {
682
+ return {
683
+ content: [{ type: "text", text: "Skipped due to queued user message." }],
684
+ details: {},
685
+ };
686
+ }