How to Implement Abort and Continue After Abort in pi-ai

You can cancel active LLM requests in pi-ai by passing an AbortSignal to the stream options, then continue the conversation normally by appending the aborted message (with stopReason: "aborted") to the context and issuing a new request with a fresh AbortController.

The pi-ai library from the badlogic/pi-mono repository provides a unified streaming interface for multiple LLM providers. When building interactive applications, you need the ability to cancel in-flight generation and resume the conversation seamlessly. This guide explains how to implement abort and continue after abort in pi-ai using the native AbortController API.

How Abort Works in pi-ai

The AbortSignal Contract

Every provider in pi-ai respects the signal property in ProviderStreamOptions. When you pass controller.signal to the stream options, the provider monitors options?.signal?.aborted before each network read.

Provider-Level Implementation

In packages/ai/src/providers/openai-completions.ts, the provider checks the signal state:

if (options?.signal?.aborted) {
    throw new Error("Request was aborted");
}
// ...
output.stopReason = options?.signal?.aborted ? "aborted" : "error";

Other providers—including openai-responses.ts, google-vertex.ts, google-gemini-cli.ts, anthropic.ts, and amazon-bedrock.ts—follow the same contract. The stream() function in packages/ai/src/stream.ts simply forwards the options to the selected provider, ensuring uniform abort handling across all LLM services.

Implementing Abort in Your Application

To cancel a stream mid-generation, create an AbortController and pass its signal to the stream options:

import { getModel } from "@mariozechner/pi-ai/src/models.js";
import { stream } from "@mariozechner/pi-ai/src/stream.js";

const model = getModel("openai", "gpt-4o-mini")!;
const controller = new AbortController();

const response = await stream(model, context, { 
  signal: controller.signal 
});

// Read tokens until a condition is met, then abort
let gathered = "";
for await (const ev of response) {
  if (ev.type === "text_delta") gathered += ev.delta;
  if (gathered.length >= 100) {
    controller.abort();          // <-- cancel the request
    break;
  }
}

const abortedMsg = await response.result();
console.log(abortedMsg.stopReason); // "aborted"

When the signal triggers, the provider throws an abort error, which the stream wrapper converts into an AssistantMessage with stopReason: "aborted" and (usually) an empty content array.

Continuing After an Abort

The abort does not corrupt the conversation history. To continue, append the aborted message to your context and send a new request with a fresh AbortController:

// 1. Preserve the aborted message in context
ctx.messages.push(abortedMsg);

// 2. Add the follow-up user prompt
ctx.messages.push({
  role: "user",
  content: "Please continue from where you left off.",
  timestamp: Date.now(),
});

// 3. Create a new request with a fresh controller
const newController = new AbortController();
const followUp = await stream(model, ctx, { 
  signal: newController.signal 
});

const result = await followUp.result();
console.log(result.stopReason); // "stop" (normal completion)

This pattern is validated in packages/ai/test/abort.test.ts, which confirms that aborted messages preserve conversation continuity and that subsequent requests work independently of previous aborts.

Key Implementation Files

Component File Path Role
Stream dispatcher packages/ai/src/stream.ts Forwards options to providers
OpenAI provider packages/ai/src/providers/openai-completions.ts Checks signal?.aborted and sets stopReason
Google Vertex packages/ai/src/providers/google-vertex.ts Abort contract for Vertex AI
Anthropic packages/ai/src/providers/anthropic.ts Abort handling for Claude
Amazon Bedrock packages/ai/src/providers/amazon-bedrock.ts Abort support for Bedrock
Test suite packages/ai/test/abort.test.ts Validates abort and continue workflows

Summary

  • Pass an AbortSignal via stream options to enable cancellation in pi-ai.
  • Providers check options?.signal?.aborted and convert the abort into a message with stopReason: "aborted".
  • Append the aborted message to your conversation context to maintain chronological order.
  • Issue subsequent requests with a fresh AbortController to continue the conversation normally.
  • All built-in providers (OpenAI, Google, Anthropic, Amazon) implement the same abort contract.

Frequently Asked Questions

Does aborting a request corrupt the conversation history in pi-ai?

No. The aborted request returns a valid AssistantMessage with stopReason: "aborted". When you append this message to your context, the conversation history remains intact. The next request receives the full message sequence including the aborted turn, allowing the model to continue as if the previous turn simply produced no output.

Can I reuse the same AbortController after calling abort()?

No. Once an AbortController is aborted, its signal remains permanently in the aborted state. To continue the conversation after an abort, you must create a new AbortController instance for the subsequent request. The test suite in packages/ai/test/abort.test.ts explicitly validates this pattern of using fresh controllers for follow-up requests.

Which providers support abort and continue in pi-ai?

All built-in providers support abort via AbortSignal: OpenAI (openai-completions.ts, openai-responses.ts), Google Vertex (google-vertex.ts), Google Gemini CLI (google-gemini-cli.ts), Anthropic (anthropic.ts), and Amazon Bedrock (amazon-bedrock.ts). Each provider checks options?.signal?.aborted and returns stopReason: "aborted" when cancelled.

How does the abort signal propagate through the stream?

The stream() function in packages/ai/src/stream.ts forwards the options object (including signal) to the selected provider. The provider then checks signal?.aborted before each network read. If aborted, the provider throws an error that is caught by the stream wrapper, which sets output.stopReason to "aborted" and closes the stream gracefully with a final error event.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →