@oh-my-pi/pi-agent-core 17.3.4 → 17.3.5
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
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.3.5] - 2026-08-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added automatic retry support for transient provider failures during one-shot completions, allowing callers such as compaction to opt in to resilient request handling.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed /handoff, branch summarization, and manual /compact failing outright on transient provider errors (e.g. Anthropic overloaded/429/529 responses); these operations now retry automatically instead of leaving the user's context full.
|
|
14
|
+
|
|
5
15
|
## [17.3.4] - 2026-08-14
|
|
6
16
|
|
|
7
17
|
### Fixed
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Pure functions for compaction logic. The session manager handles I/O,
|
|
5
5
|
* and after compaction the session is reloaded.
|
|
6
6
|
*/
|
|
7
|
-
import { type Api, type ApiKey, type AssistantMessage, type CodexCompactionContext, type Context, type FetchImpl, type MessageAttribution, type Model, type ProviderSessionState, type SimpleStreamOptions, type Tool, type Usage } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import { type Api, type ApiKey, type AssistantMessage, type CodexCompactionContext, type Context, type FetchImpl, type MessageAttribution, type Model, type OneshotRetryOptions, type ProviderSessionState, type SimpleStreamOptions, type Tool, type Usage } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import { type AgentTelemetry } from "../telemetry.js";
|
|
9
9
|
import { ThinkingLevel } from "../thinking.js";
|
|
10
10
|
import type { AgentMessage } from "../types.js";
|
|
@@ -217,6 +217,20 @@ export interface SummaryOptions {
|
|
|
217
217
|
* the live agent turn also bracket compaction HTTP requests.
|
|
218
218
|
*/
|
|
219
219
|
completeImpl?: <TApi extends Api>(model: Model<TApi>, ctx: Context, options: SimpleStreamOptions) => Promise<AssistantMessage>;
|
|
220
|
+
/**
|
|
221
|
+
* Transient-failure retry for the summarization oneshots (`generateSummary`,
|
|
222
|
+
* `generateShortSummary`, `generateTurnPrefixSummary`).
|
|
223
|
+
*
|
|
224
|
+
* Defaults to enabled, which is what a one-shot caller such as manual
|
|
225
|
+
* `/compact` needs: a single Anthropic `overloaded_error` / 429 / 529 should
|
|
226
|
+
* not abort compaction and leave the context full.
|
|
227
|
+
*
|
|
228
|
+
* Pass `false` when the CALLER already owns a retry loop around the whole
|
|
229
|
+
* compaction attempt — auto-compaction does — otherwise the two budgets
|
|
230
|
+
* multiply (10 outer attempts x 3 inner = 30 requests) and each outer wait
|
|
231
|
+
* stacks on top of the inner backoff.
|
|
232
|
+
*/
|
|
233
|
+
oneshotRetry?: OneshotRetryOptions | false;
|
|
220
234
|
}
|
|
221
235
|
export declare function generateSummary(currentMessages: AgentMessage[], model: Model, reserveTokens: number, apiKey: ApiKey, signal?: AbortSignal, customInstructions?: string, previousSummary?: string, options?: SummaryOptions): Promise<string>;
|
|
222
236
|
export interface HandoffOptions {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* registered, `@opentelemetry/api` returns a no-op tracer and all calls are
|
|
23
23
|
* cheap pass-throughs.
|
|
24
24
|
*/
|
|
25
|
-
import { type Api, type AssistantMessage, type Context, type Message, type Model, type ServiceTier, type SimpleStreamOptions, type StopReason, type ToolChoice, type Usage } from "@oh-my-pi/pi-ai";
|
|
25
|
+
import { type Api, type AssistantMessage, type Context, type Message, type Model, type OneshotRetryOptions, type ServiceTier, type SimpleStreamOptions, type StopReason, type ToolChoice, type Usage } from "@oh-my-pi/pi-ai";
|
|
26
26
|
import { type Attributes, type AttributeValue, type Span, SpanKind, SpanStatusCode, type Tracer, trace } from "@opentelemetry/api";
|
|
27
27
|
import { AgentRunCollector, type AgentRunCoverage, type AgentRunSummary, type ToolStatus } from "./run-collector.js";
|
|
28
28
|
import type { AgentTool } from "./types.js";
|
|
@@ -479,6 +479,23 @@ export interface InstrumentedChatSpanOptions {
|
|
|
479
479
|
* seam while still going through the chat-span lifecycle.
|
|
480
480
|
*/
|
|
481
481
|
readonly completeImpl?: <TApi extends Api>(model: Model<TApi>, ctx: Context, options: SimpleStreamOptions) => Promise<AssistantMessage>;
|
|
482
|
+
/**
|
|
483
|
+
* Opt in to transient-failure retry for this oneshot (Anthropic
|
|
484
|
+
* `overloaded_error`, `rate_limit_error`, 429/500/502/503/529). Omitted or
|
|
485
|
+
* `undefined` means **no retry** — the failure is surfaced exactly as before.
|
|
486
|
+
*
|
|
487
|
+
* Deliberately opt-in rather than default-on: `oneshotKind` is free-form and
|
|
488
|
+
* callers may pass arbitrary `ctx.tools` / `options.toolChoice`, so this
|
|
489
|
+
* funnel cannot itself prove a given request is replay-safe. Re-issuing is
|
|
490
|
+
* only safe when the call performs no side effect and nothing consumed
|
|
491
|
+
* partial output — true for summaries, titles, handoffs and image
|
|
492
|
+
* descriptions, which parse a complete response after it resolves. Enable it
|
|
493
|
+
* per call site, as a reviewed decision.
|
|
494
|
+
*
|
|
495
|
+
* Pass `{}` to accept the {@link retryTransientCompletion} defaults
|
|
496
|
+
* (3 attempts, 500ms base backoff, `retry-after` honored).
|
|
497
|
+
*/
|
|
498
|
+
readonly retry?: OneshotRetryOptions;
|
|
482
499
|
}
|
|
483
500
|
/**
|
|
484
501
|
* Wrap a {@link completeSimple} round-trip with the same chat-span lifecycle
|
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": "17.3.
|
|
4
|
+
"version": "17.3.5",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -35,16 +35,16 @@
|
|
|
35
35
|
"fmt": "biome format --write ."
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@oh-my-pi/pi-ai": "17.3.
|
|
39
|
-
"@oh-my-pi/pi-catalog": "17.3.
|
|
40
|
-
"@oh-my-pi/pi-natives": "17.3.
|
|
41
|
-
"@oh-my-pi/pi-utils": "17.3.
|
|
42
|
-
"@oh-my-pi/pi-wire": "17.3.
|
|
43
|
-
"@oh-my-pi/snapcompact": "17.3.
|
|
38
|
+
"@oh-my-pi/pi-ai": "17.3.5",
|
|
39
|
+
"@oh-my-pi/pi-catalog": "17.3.5",
|
|
40
|
+
"@oh-my-pi/pi-natives": "17.3.5",
|
|
41
|
+
"@oh-my-pi/pi-utils": "17.3.5",
|
|
42
|
+
"@oh-my-pi/pi-wire": "17.3.5",
|
|
43
|
+
"@oh-my-pi/snapcompact": "17.3.5",
|
|
44
44
|
"@opentelemetry/api": "^1.9.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@oh-my-pi/omptype": "17.3.
|
|
47
|
+
"@oh-my-pi/omptype": "17.3.5",
|
|
48
48
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
|
49
49
|
"@opentelemetry/sdk-trace-base": "^2.9.0",
|
|
50
50
|
"@types/bun": "^1.3.14"
|
|
@@ -334,12 +334,18 @@ export async function generateBranchSummary(
|
|
|
334
334
|
];
|
|
335
335
|
|
|
336
336
|
// Call LLM for summarization
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
337
|
+
let response: AssistantMessage;
|
|
338
|
+
try {
|
|
339
|
+
response = await instrumentedCompleteSimple(
|
|
340
|
+
model,
|
|
341
|
+
{ systemPrompt: [SUMMARIZATION_SYSTEM_PROMPT], messages: summarizationMessages },
|
|
342
|
+
{ apiKey, signal, maxTokens: 2048, metadata },
|
|
343
|
+
{ telemetry: options.telemetry, oneshotKind: "branch_summary", completeImpl: options.completeImpl, retry: {} },
|
|
344
|
+
);
|
|
345
|
+
} catch (error) {
|
|
346
|
+
if (signal.aborted) return { aborted: true };
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
343
349
|
|
|
344
350
|
// Check if aborted or errored
|
|
345
351
|
if (response.stopReason === "aborted") {
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type Message,
|
|
17
17
|
type MessageAttribution,
|
|
18
18
|
type Model,
|
|
19
|
+
type OneshotRetryOptions,
|
|
19
20
|
type ProviderSessionState,
|
|
20
21
|
type SimpleStreamOptions,
|
|
21
22
|
type Tool,
|
|
@@ -470,8 +471,8 @@ function computeMessageTokens(message: AgentMessage, options?: { excludeEncrypte
|
|
|
470
471
|
if (!options?.excludeEncryptedReasoning) fragments.push(block.data);
|
|
471
472
|
} else if (block.type === "anthropicServerTool") {
|
|
472
473
|
// Native Anthropic server-tool call/result replayed verbatim on the
|
|
473
|
-
// wire (server_tool_use input
|
|
474
|
-
//
|
|
474
|
+
// wire (server_tool_use input and opaque result content). This opaque
|
|
475
|
+
// provider-replay state the provider still
|
|
475
476
|
// bills for on same-provider replay; excluded from the compaction
|
|
476
477
|
// floor like other encrypted reasoning because its local byte size
|
|
477
478
|
// diverges from provider billing.
|
|
@@ -831,6 +832,31 @@ export interface SummaryOptions {
|
|
|
831
832
|
ctx: Context,
|
|
832
833
|
options: SimpleStreamOptions,
|
|
833
834
|
) => Promise<AssistantMessage>;
|
|
835
|
+
/**
|
|
836
|
+
* Transient-failure retry for the summarization oneshots (`generateSummary`,
|
|
837
|
+
* `generateShortSummary`, `generateTurnPrefixSummary`).
|
|
838
|
+
*
|
|
839
|
+
* Defaults to enabled, which is what a one-shot caller such as manual
|
|
840
|
+
* `/compact` needs: a single Anthropic `overloaded_error` / 429 / 529 should
|
|
841
|
+
* not abort compaction and leave the context full.
|
|
842
|
+
*
|
|
843
|
+
* Pass `false` when the CALLER already owns a retry loop around the whole
|
|
844
|
+
* compaction attempt — auto-compaction does — otherwise the two budgets
|
|
845
|
+
* multiply (10 outer attempts x 3 inner = 30 requests) and each outer wait
|
|
846
|
+
* stacks on top of the inner backoff.
|
|
847
|
+
*/
|
|
848
|
+
oneshotRetry?: OneshotRetryOptions | false;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* Resolve the oneshot retry policy for a summarization call. Enabled by default
|
|
853
|
+
* so a lone transient blip cannot abort compaction; `false` opts out for callers
|
|
854
|
+
* that already retry the whole attempt (see `SummaryOptions.oneshotRetry`).
|
|
855
|
+
*/
|
|
856
|
+
function summaryOneshotRetry(options: SummaryOptions | undefined): OneshotRetryOptions | undefined {
|
|
857
|
+
const configured = options?.oneshotRetry;
|
|
858
|
+
if (configured === false) return undefined;
|
|
859
|
+
return configured ?? {};
|
|
834
860
|
}
|
|
835
861
|
|
|
836
862
|
function localCodexCompaction(options: SummaryOptions | undefined) {
|
|
@@ -935,7 +961,12 @@ export async function generateSummary(
|
|
|
935
961
|
providerSessionState: options?.providerSessionState,
|
|
936
962
|
codexCompaction: localCodexCompaction(options),
|
|
937
963
|
},
|
|
938
|
-
{
|
|
964
|
+
{
|
|
965
|
+
telemetry: options?.telemetry,
|
|
966
|
+
oneshotKind: "compaction_summary",
|
|
967
|
+
completeImpl: options?.completeImpl,
|
|
968
|
+
retry: summaryOneshotRetry(options),
|
|
969
|
+
},
|
|
939
970
|
);
|
|
940
971
|
|
|
941
972
|
if (response.stopReason === "error") {
|
|
@@ -1034,13 +1065,14 @@ export async function generateHandoffFromContext(
|
|
|
1034
1065
|
telemetry: options.telemetry,
|
|
1035
1066
|
oneshotKind: "handoff",
|
|
1036
1067
|
completeImpl: options.completeImpl,
|
|
1068
|
+
retry: {},
|
|
1037
1069
|
});
|
|
1038
1070
|
if (response.stopReason === "error" && shouldRetryHandoffWithAutoToolChoice(response)) {
|
|
1039
1071
|
response = await instrumentedCompleteSimple(
|
|
1040
1072
|
model,
|
|
1041
1073
|
context,
|
|
1042
1074
|
{ ...requestOptions, toolChoice: "auto" },
|
|
1043
|
-
{ telemetry: options.telemetry, oneshotKind: "handoff", completeImpl: options.completeImpl },
|
|
1075
|
+
{ telemetry: options.telemetry, oneshotKind: "handoff", completeImpl: options.completeImpl, retry: {} },
|
|
1044
1076
|
);
|
|
1045
1077
|
}
|
|
1046
1078
|
|
|
@@ -1143,7 +1175,12 @@ async function generateShortSummary(
|
|
|
1143
1175
|
providerSessionState: options?.providerSessionState,
|
|
1144
1176
|
codexCompaction: localCodexCompaction(options),
|
|
1145
1177
|
},
|
|
1146
|
-
{
|
|
1178
|
+
{
|
|
1179
|
+
telemetry: options?.telemetry,
|
|
1180
|
+
oneshotKind: "compaction_short_summary",
|
|
1181
|
+
completeImpl: options?.completeImpl,
|
|
1182
|
+
retry: summaryOneshotRetry(options),
|
|
1183
|
+
},
|
|
1147
1184
|
);
|
|
1148
1185
|
|
|
1149
1186
|
if (response.stopReason === "error") {
|
|
@@ -1719,7 +1756,12 @@ async function generateTurnPrefixSummary(
|
|
|
1719
1756
|
providerSessionState: options?.providerSessionState,
|
|
1720
1757
|
codexCompaction: localCodexCompaction(options),
|
|
1721
1758
|
},
|
|
1722
|
-
{
|
|
1759
|
+
{
|
|
1760
|
+
telemetry: options?.telemetry,
|
|
1761
|
+
oneshotKind: "compaction_turn_prefix",
|
|
1762
|
+
completeImpl: options?.completeImpl,
|
|
1763
|
+
retry: summaryOneshotRetry(options),
|
|
1764
|
+
},
|
|
1723
1765
|
);
|
|
1724
1766
|
|
|
1725
1767
|
if (response.stopReason === "error") {
|
package/src/telemetry.ts
CHANGED
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
completeSimple,
|
|
31
31
|
type Message,
|
|
32
32
|
type Model,
|
|
33
|
+
type OneshotRetryOptions,
|
|
34
|
+
retryTransientCompletion,
|
|
33
35
|
type ServiceTier,
|
|
34
36
|
type SimpleStreamOptions,
|
|
35
37
|
type StopReason,
|
|
@@ -1663,6 +1665,23 @@ export interface InstrumentedChatSpanOptions {
|
|
|
1663
1665
|
ctx: Context,
|
|
1664
1666
|
options: SimpleStreamOptions,
|
|
1665
1667
|
) => Promise<AssistantMessage>;
|
|
1668
|
+
/**
|
|
1669
|
+
* Opt in to transient-failure retry for this oneshot (Anthropic
|
|
1670
|
+
* `overloaded_error`, `rate_limit_error`, 429/500/502/503/529). Omitted or
|
|
1671
|
+
* `undefined` means **no retry** — the failure is surfaced exactly as before.
|
|
1672
|
+
*
|
|
1673
|
+
* Deliberately opt-in rather than default-on: `oneshotKind` is free-form and
|
|
1674
|
+
* callers may pass arbitrary `ctx.tools` / `options.toolChoice`, so this
|
|
1675
|
+
* funnel cannot itself prove a given request is replay-safe. Re-issuing is
|
|
1676
|
+
* only safe when the call performs no side effect and nothing consumed
|
|
1677
|
+
* partial output — true for summaries, titles, handoffs and image
|
|
1678
|
+
* descriptions, which parse a complete response after it resolves. Enable it
|
|
1679
|
+
* per call site, as a reviewed decision.
|
|
1680
|
+
*
|
|
1681
|
+
* Pass `{}` to accept the {@link retryTransientCompletion} defaults
|
|
1682
|
+
* (3 attempts, 500ms base backoff, `retry-after` honored).
|
|
1683
|
+
*/
|
|
1684
|
+
readonly retry?: OneshotRetryOptions;
|
|
1666
1685
|
}
|
|
1667
1686
|
|
|
1668
1687
|
/**
|
|
@@ -1723,10 +1742,26 @@ export async function instrumentedCompleteSimple<TApi extends Api>(
|
|
|
1723
1742
|
try {
|
|
1724
1743
|
return await runInActiveSpan(chatSpan, async () => {
|
|
1725
1744
|
const complete = span.completeImpl ?? completeSimple;
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1745
|
+
// Opt-in only (see `retry` on InstrumentedChatSpanOptions): each attempt
|
|
1746
|
+
// re-issues the whole request, which is safe only for replay-safe
|
|
1747
|
+
// oneshots. `getResponseHeaders` hands the failed attempt's headers to
|
|
1748
|
+
// the retry layer — an AssistantMessage carries none, so this is what
|
|
1749
|
+
// makes `retry-after` on a real 429/529 actually honored.
|
|
1750
|
+
const runOnce = () => {
|
|
1751
|
+
// Clear first so a previous attempt's `retry-after` can never be
|
|
1752
|
+
// reused for a later failure that arrived without headers.
|
|
1753
|
+
capturedHeaders = undefined;
|
|
1754
|
+
return complete(model, ctx, { ...options, onResponse: captureOnResponse });
|
|
1755
|
+
};
|
|
1756
|
+
const message = span.retry
|
|
1757
|
+
? await retryTransientCompletion(runOnce, {
|
|
1758
|
+
...span.retry,
|
|
1759
|
+
// Framework-owned: the caller must not be able to detach the
|
|
1760
|
+
// abort signal or the header source by passing them itself.
|
|
1761
|
+
signal: options.signal,
|
|
1762
|
+
getResponseHeaders: () => capturedHeaders,
|
|
1763
|
+
})
|
|
1764
|
+
: await runOnce();
|
|
1730
1765
|
await finishChatSpan(telemetry, chatSpan, message, {
|
|
1731
1766
|
stepNumber,
|
|
1732
1767
|
serviceTier: options.serviceTier,
|