Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions packages/agent/src/pi/conversation/translatePiConversation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,17 @@ describe("createPiConversationTranslator", () => {
delayMs: 1000,
},
]);
const retriedMessage = assistant([{ type: "text", text: "Done" }]);
expect(
translator.translateEvent({
type: "auto_retry_end",
success: true,
attempt: 1,
type: "message_update",
message: retriedMessage,
assistantMessageEvent: {
type: "text_delta",
contentIndex: 0,
delta: "Done",
partial: retriedMessage,
},
}),
).toEqual([
{
Expand All @@ -175,7 +181,19 @@ describe("createPiConversationTranslator", () => {
status: "retrying",
isComplete: true,
},
{
type: "assistant_message_chunk",
timestamp: 10,
content: { type: "text", text: "Done" },
},
]);
expect(
translator.translateEvent({
type: "auto_retry_end",
success: true,
attempt: 1,
}),
).toEqual([]);
});

it("renders terminal Pi runtime errors inline", () => {
Expand Down
35 changes: 27 additions & 8 deletions packages/agent/src/pi/conversation/translatePiConversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,25 @@ export function createPiConversationTranslator(): PiConversationTranslator {
let latestRuntimeTimestamp = 0;
let latestConversationTimestamp = 0;
let pendingRuntimeError: AgentConversationEvent | undefined;
let retrying = false;
let directBashSequence = 0;

function completeRetry(timestamp: number): AgentConversationEvent[] {
if (!retrying) {
return [];
}

retrying = false;
return [
{
type: "runtime_status",
timestamp,
status: "retrying",
isComplete: true,
},
];
}

let activeDirectBash:
| {
nextOutputBytes: number;
Expand Down Expand Up @@ -257,6 +275,7 @@ export function createPiConversationTranslator(): PiConversationTranslator {
if (update.type === "text_delta" && update.delta) {
streamedAssistantTimestamps.add(event.message.timestamp);
return [
...completeRetry(event.message.timestamp),
{
type: "assistant_message_chunk",
timestamp: event.message.timestamp,
Expand All @@ -268,6 +287,7 @@ export function createPiConversationTranslator(): PiConversationTranslator {
if (update.type === "thinking_delta" && update.delta) {
streamedAssistantTimestamps.add(event.message.timestamp);
return [
...completeRetry(event.message.timestamp),
{
type: "assistant_thought_chunk",
timestamp: event.message.timestamp,
Expand Down Expand Up @@ -407,7 +427,11 @@ export function createPiConversationTranslator(): PiConversationTranslator {
}

if (event.type === "auto_retry_start") {
const completedEvents = completeRetry(latestConversationTimestamp);
retrying = true;

return [
...completedEvents,
{
type: "runtime_status",
timestamp: latestConversationTimestamp,
Expand All @@ -421,14 +445,9 @@ export function createPiConversationTranslator(): PiConversationTranslator {
}

if (event.type === "auto_retry_end") {
const events: AgentConversationEvent[] = [
{
type: "runtime_status",
timestamp: latestConversationTimestamp,
status: "retrying",
isComplete: true,
},
];
const events: AgentConversationEvent[] = completeRetry(
latestConversationTimestamp,
);

if (!event.success && event.finalError) {
events.push({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { describe, expect, it } from "vitest";
import { formatCompactionFailure } from "./StatusNotificationView";
import {
formatCompactionFailure,
formatRetryStatus,
} from "./StatusNotificationView";

describe("formatCompactionFailure", () => {
it.each([
Expand All @@ -16,3 +19,37 @@ describe("formatCompactionFailure", () => {
expect(formatCompactionFailure(error)).toBe(expected);
});
});

describe("formatRetryStatus", () => {
it.each([
{
input: {
attempt: 1,
maxAttempts: 3,
message: "Rate limit reached for gpt-5.6-terra on token ...",
remainingMs: 0,
},
expected: "Rate limit reached. Retrying now (attempt 1 of 3)",
},
{
input: {
attempt: 2,
maxAttempts: 3,
message: "Rate limited",
remainingMs: 2_000,
},
expected: "Rate limit reached. Retrying in 2s (attempt 2 of 3)",
},
{
input: {
attempt: 2,
maxAttempts: 3,
message: "Server overloaded",
remainingMs: 2_000,
},
expected: "Retrying in 2s (attempt 2 of 3)",
},
])("renders a concise retry message", ({ input, expected }) => {
expect(formatRetryStatus(input)).toBe(expected);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,29 @@ export function formatCompactionFailure(error?: string): string {
return detail ? `Compacting failed: ${detail}` : "Compacting failed";
}

export function formatRetryStatus({
attempt,
maxAttempts,
message,
remainingMs,
}: {
attempt?: number;
maxAttempts?: number;
message?: string;
remainingMs: number;
}): string {
const rateLimited = /\b429\b|rate[ _]limit(?:ed)?|too many requests/i.test(
message ?? "",
);
const retryAt =
remainingMs > 0 ? `in ${formatDuration(remainingMs, 0)}` : "now";
const attemptLabel =
attempt && maxAttempts ? ` (attempt ${attempt} of ${maxAttempts})` : "";
const prefix = rateLimited ? "Rate limit reached. " : "";

return `${prefix}Retrying ${retryAt}${attemptLabel}`;
}

export function StatusNotificationView({
status,
isComplete,
Expand Down Expand Up @@ -179,24 +202,19 @@ function RetryingStatusView({
return () => clearInterval(interval);
}, [delayMs, startedAt]);

const attemptLabel =
attempt && maxAttempts
? `Attempt ${attempt} of ${maxAttempts}`
: "Retrying";
const retryLabel =
remainingMs > 0
? `${attemptLabel} in ${formatDuration(remainingMs, 1)}`
: `${attemptLabel} now`;
const retryLabel = formatRetryStatus({
attempt,
maxAttempts,
message,
remainingMs,
});

return (
<ChatMarker variant="separator">
<ChatMarkerContent>
<Flex align="center" gap="2">
<ArrowsClockwise size={13} className="animate-spin text-amber-9" />
<Text className="text-[13px] text-gray-11">{retryLabel}</Text>
{message && (
<Text className="truncate text-[13px] text-gray-10">{message}</Text>
)}
</Flex>
</ChatMarkerContent>
</ChatMarker>
Expand Down
Loading