@oh-my-pi/pi-agent-core 17.2.4 → 17.2.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 +12 -0
- package/dist/types/agent-loop.d.ts +7 -1
- package/dist/types/append-only-context.d.ts +0 -2
- package/dist/types/compaction/shake.d.ts +6 -1
- package/dist/types/compaction/tool-protection.d.ts +2 -0
- package/package.json +7 -7
- package/src/agent-loop.ts +18 -15
- package/src/agent.ts +4 -7
- package/src/append-only-context.ts +4 -4
- package/src/compaction/shake.ts +12 -2
- package/src/compaction/tool-protection.ts +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.2.5] - 2026-08-03
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- Tool examples embedded in tool descriptions now always render in Python call syntax, and the `exampleDialect` option has been removed from `AppendOnlyContextManager` build options.
|
|
10
|
+
- Updated `normalizeTools` to accept a `NormalizeToolsOptions` configuration object (`{ injectIntent, pruneDescriptions }`) instead of positional booleans.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Fixed an issue where runs would fail with an error if an Anthropic stream was truncated after complete tool calls were streamed; the agent now recovers and executes those tool calls.
|
|
15
|
+
- Fixed an issue where artifact recovery reads could be incorrectly elided during compaction.
|
|
16
|
+
|
|
5
17
|
## [17.2.4] - 2026-08-01
|
|
6
18
|
|
|
7
19
|
### Fixed
|
|
@@ -80,7 +80,13 @@ export declare function agentLoopContinueDetailed(context: AgentContext, config:
|
|
|
80
80
|
readonly detailed: () => Promise<AgentLoopDetailedResult>;
|
|
81
81
|
};
|
|
82
82
|
export declare function normalizeMessagesForProvider(messages: Context["messages"], model: AgentLoopConfig["model"]): Context["messages"];
|
|
83
|
-
export
|
|
83
|
+
export interface NormalizeToolsOptions {
|
|
84
|
+
/** Inject the `i` intent field into tool schemas (subject to `PI_NO_INTENT`). */
|
|
85
|
+
injectIntent: boolean;
|
|
86
|
+
/** Strip descriptions from the wire specs when the catalog rides in the system prompt. */
|
|
87
|
+
pruneDescriptions?: boolean;
|
|
88
|
+
}
|
|
89
|
+
export declare function normalizeTools(tools: AgentContext["tools"], options: NormalizeToolsOptions): Context["tools"];
|
|
84
90
|
/** Resolve the human-readable reason an abort carried. A caller that aborts via
|
|
85
91
|
* `AbortController.abort(reason)` with a string or a non-`AbortError` `Error`
|
|
86
92
|
* (e.g. the coding agent's user-interrupt label) gets that text surfaced on the
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
* message delta is a cache miss each turn.
|
|
15
15
|
*/
|
|
16
16
|
import type { Context, Message, Tool } from "@oh-my-pi/pi-ai";
|
|
17
|
-
import type { Dialect } from "@oh-my-pi/pi-ai/dialect";
|
|
18
17
|
import type { AgentContext } from "./types.js";
|
|
19
18
|
/** Frozen system prompt + tool spec snapshot. */
|
|
20
19
|
export interface StablePrefixSnapshot {
|
|
@@ -26,7 +25,6 @@ export interface StablePrefixSnapshot {
|
|
|
26
25
|
export interface BuildOptions {
|
|
27
26
|
/** Inject the `i` intent field into tool schemas (must match agent-loop's normalizeTools). */
|
|
28
27
|
intentTracing: boolean;
|
|
29
|
-
exampleDialect?: Dialect;
|
|
30
28
|
/** Strip tool descriptions from the provider-bound specs (must match normalizeTools). */
|
|
31
29
|
pruneToolDescriptions?: boolean;
|
|
32
30
|
}
|
|
@@ -31,8 +31,13 @@ export interface ShakeConfig {
|
|
|
31
31
|
}
|
|
32
32
|
/** Auto-shake config: protects the live tail, conservative thresholds. */
|
|
33
33
|
export declare const DEFAULT_SHAKE_CONFIG: ShakeConfig;
|
|
34
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Manual `/shake`: aggressive — drops every eligible region across history,
|
|
36
|
+
* artifact recovery reads included (the user's full escape hatch).
|
|
37
|
+
*/
|
|
35
38
|
export declare const AGGRESSIVE_SHAKE_CONFIG: ShakeConfig;
|
|
39
|
+
/** Compaction dead-end rescue: aggressive reach, but artifact recovery reads stay protected. */
|
|
40
|
+
export declare const RESCUE_SHAKE_CONFIG: ShakeConfig;
|
|
36
41
|
/** A located eligible region. */
|
|
37
42
|
export interface ToolResultShakeRegion {
|
|
38
43
|
kind: "toolResult";
|
|
@@ -14,4 +14,6 @@ export declare function collectToolCallsById(entries: readonly SessionEntry[]):
|
|
|
14
14
|
*/
|
|
15
15
|
export declare function getReadToolPath({ toolResult, toolCall }: ProtectedToolContext): string | undefined;
|
|
16
16
|
export declare function isSkillReadToolResult(context: ProtectedToolContext): boolean;
|
|
17
|
+
/** Recovery reads of session artifacts — eliding one only mints another artifact and can repeat indefinitely. */
|
|
18
|
+
export declare function isArtifactRecoveryToolResult(context: ProtectedToolContext): boolean;
|
|
17
19
|
export declare function isProtectedToolResult(toolResult: ToolResultMessage, toolCall: AgentToolCall | undefined, matchers: readonly ProtectedToolMatcher[]): boolean;
|
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.2.
|
|
4
|
+
"version": "17.2.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,12 +35,12 @@
|
|
|
35
35
|
"fmt": "biome format --write ."
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@oh-my-pi/pi-ai": "17.2.
|
|
39
|
-
"@oh-my-pi/pi-catalog": "17.2.
|
|
40
|
-
"@oh-my-pi/pi-natives": "17.2.
|
|
41
|
-
"@oh-my-pi/pi-utils": "17.2.
|
|
42
|
-
"@oh-my-pi/pi-wire": "17.2.
|
|
43
|
-
"@oh-my-pi/snapcompact": "17.2.
|
|
38
|
+
"@oh-my-pi/pi-ai": "17.2.5",
|
|
39
|
+
"@oh-my-pi/pi-catalog": "17.2.5",
|
|
40
|
+
"@oh-my-pi/pi-natives": "17.2.5",
|
|
41
|
+
"@oh-my-pi/pi-utils": "17.2.5",
|
|
42
|
+
"@oh-my-pi/pi-wire": "17.2.5",
|
|
43
|
+
"@oh-my-pi/snapcompact": "17.2.5",
|
|
44
44
|
"@opentelemetry/api": "^1.9.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
package/src/agent-loop.ts
CHANGED
|
@@ -42,7 +42,6 @@ import {
|
|
|
42
42
|
recoverHarmonyToolCall,
|
|
43
43
|
signalListLabel,
|
|
44
44
|
} from "@oh-my-pi/pi-ai/utils/harmony-leak";
|
|
45
|
-
import { preferredDialect } from "@oh-my-pi/pi-catalog/identity";
|
|
46
45
|
import { logger, sanitizeText, structuredCloneJSON } from "@oh-my-pi/pi-utils";
|
|
47
46
|
import { INTENT_FIELD } from "@oh-my-pi/pi-wire";
|
|
48
47
|
import { agentPauseGate } from "./pause";
|
|
@@ -829,13 +828,16 @@ function injectIntentIntoSchema(
|
|
|
829
828
|
};
|
|
830
829
|
}
|
|
831
830
|
|
|
832
|
-
export
|
|
833
|
-
|
|
834
|
-
injectIntent: boolean
|
|
835
|
-
|
|
836
|
-
pruneDescriptions
|
|
837
|
-
|
|
838
|
-
|
|
831
|
+
export interface NormalizeToolsOptions {
|
|
832
|
+
/** Inject the `i` intent field into tool schemas (subject to `PI_NO_INTENT`). */
|
|
833
|
+
injectIntent: boolean;
|
|
834
|
+
/** Strip descriptions from the wire specs when the catalog rides in the system prompt. */
|
|
835
|
+
pruneDescriptions?: boolean;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
export function normalizeTools(tools: AgentContext["tools"], options: NormalizeToolsOptions): Context["tools"] {
|
|
839
|
+
const pruneDescriptions = options.pruneDescriptions === true;
|
|
840
|
+
const injectIntent = options.injectIntent && Bun.env.PI_NO_INTENT !== "1";
|
|
839
841
|
return tools?.map(t => {
|
|
840
842
|
const intentMode = resolveIntentMode(t.intent);
|
|
841
843
|
const doInjectIntent = injectIntent && intentMode !== "omit";
|
|
@@ -853,9 +855,7 @@ export function normalizeTools(
|
|
|
853
855
|
let parameters = toolWireSchema(t) as TSchema;
|
|
854
856
|
if (doInjectIntent) parameters = injectIntentIntoSchema(parameters, intentMode) as TSchema;
|
|
855
857
|
const description = t.description ?? "";
|
|
856
|
-
const examplesBlock =
|
|
857
|
-
? renderToolExamples({ ...t, parameters }, exampleDialect, doInjectIntent ? INTENT_FIELD : undefined)
|
|
858
|
-
: "";
|
|
858
|
+
const examplesBlock = renderToolExamples({ ...t, parameters }, doInjectIntent ? INTENT_FIELD : undefined);
|
|
859
859
|
const finalDescription = examplesBlock ? `${description}\n\n${examplesBlock}` : description;
|
|
860
860
|
return { ...t, parameters, description: finalDescription };
|
|
861
861
|
});
|
|
@@ -1498,21 +1498,22 @@ async function prepareProviderCall(
|
|
|
1498
1498
|
const llmMessages = await config.convertToLlm(messages);
|
|
1499
1499
|
const normalizedMessages = normalizeMessagesForProvider(llmMessages, model);
|
|
1500
1500
|
const ownedDialect: Dialect | undefined = config.dialect ?? resolveOwnedDialectFromEnv(Bun.env.PI_DIALECT);
|
|
1501
|
-
const exampleDialect = ownedDialect ?? preferredDialect(model.id);
|
|
1502
1501
|
const pruneToolDescriptions = !!config.pruneToolDescriptions && !ownedDialect;
|
|
1503
1502
|
let llmContext: Context;
|
|
1504
1503
|
if (config.appendOnlyContext) {
|
|
1505
1504
|
config.appendOnlyContext.syncMessages(normalizedMessages);
|
|
1506
1505
|
llmContext = config.appendOnlyContext.build(context, {
|
|
1507
1506
|
intentTracing: !!config.intentTracing,
|
|
1508
|
-
exampleDialect,
|
|
1509
1507
|
pruneToolDescriptions,
|
|
1510
1508
|
});
|
|
1511
1509
|
} else {
|
|
1512
1510
|
llmContext = {
|
|
1513
1511
|
systemPrompt: context.systemPrompt,
|
|
1514
1512
|
messages: normalizedMessages,
|
|
1515
|
-
tools: normalizeTools(context.tools,
|
|
1513
|
+
tools: normalizeTools(context.tools, {
|
|
1514
|
+
injectIntent: !!config.intentTracing,
|
|
1515
|
+
pruneDescriptions: pruneToolDescriptions,
|
|
1516
|
+
}),
|
|
1516
1517
|
};
|
|
1517
1518
|
}
|
|
1518
1519
|
if (config.transformProviderContext) {
|
|
@@ -1928,8 +1929,10 @@ function recoverTransientErrorToolTurn(
|
|
|
1928
1929
|
if (tool.customWireName !== undefined) availableToolNames.add(tool.customWireName);
|
|
1929
1930
|
}
|
|
1930
1931
|
if (!toolCalls.every(toolCall => availableToolNames.has(toolCall.name))) return message;
|
|
1932
|
+
const errorText = `${message.errorMessage ?? ""}\n${message.stopDetails?.explanation ?? ""}`;
|
|
1931
1933
|
if (
|
|
1932
|
-
!AIError.isStreamReadErrorText(
|
|
1934
|
+
!AIError.isStreamReadErrorText(errorText) &&
|
|
1935
|
+
!AIError.isStreamEnvelopeErrorText(errorText) &&
|
|
1933
1936
|
!AIError.isTransientStreamParseError(message.errorMessage) &&
|
|
1934
1937
|
!AIError.isTransientStreamParseError(message.stopDetails?.explanation)
|
|
1935
1938
|
)
|
package/src/agent.ts
CHANGED
|
@@ -24,7 +24,6 @@ import {
|
|
|
24
24
|
} from "@oh-my-pi/pi-ai";
|
|
25
25
|
import type { Dialect } from "@oh-my-pi/pi-ai/dialect";
|
|
26
26
|
import type { HarmonyAuditEvent } from "@oh-my-pi/pi-ai/utils/harmony-leak";
|
|
27
|
-
import { preferredDialect } from "@oh-my-pi/pi-catalog/identity";
|
|
28
27
|
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
|
|
29
28
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
30
29
|
import {
|
|
@@ -771,12 +770,10 @@ export class Agent {
|
|
|
771
770
|
const messages = normalizeMessagesForProvider(llmMessages, model);
|
|
772
771
|
const tools = ownedDialect
|
|
773
772
|
? []
|
|
774
|
-
: (normalizeTools(
|
|
775
|
-
this.#
|
|
776
|
-
this.#
|
|
777
|
-
|
|
778
|
-
this.#pruneToolDescriptions,
|
|
779
|
-
) ?? []);
|
|
773
|
+
: (normalizeTools(this.#toolsForModel(model), {
|
|
774
|
+
injectIntent: this.#intentTracing,
|
|
775
|
+
pruneDescriptions: this.#pruneToolDescriptions,
|
|
776
|
+
}) ?? []);
|
|
780
777
|
let context: Context = { systemPrompt, messages, tools };
|
|
781
778
|
if (this.#transformProviderContext) context = await this.#transformProviderContext(context, model);
|
|
782
779
|
return context;
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import type { Context, Message, Tool } from "@oh-my-pi/pi-ai";
|
|
18
|
-
import type { Dialect } from "@oh-my-pi/pi-ai/dialect";
|
|
19
18
|
import { normalizeTools } from "./agent-loop";
|
|
20
19
|
import type { AgentContext } from "./types";
|
|
21
20
|
|
|
@@ -34,7 +33,6 @@ export interface StablePrefixSnapshot {
|
|
|
34
33
|
export interface BuildOptions {
|
|
35
34
|
/** Inject the `i` intent field into tool schemas (must match agent-loop's normalizeTools). */
|
|
36
35
|
intentTracing: boolean;
|
|
37
|
-
exampleDialect?: Dialect;
|
|
38
36
|
/** Strip tool descriptions from the provider-bound specs (must match normalizeTools). */
|
|
39
37
|
pruneToolDescriptions?: boolean;
|
|
40
38
|
}
|
|
@@ -317,7 +315,10 @@ export class AppendOnlyContextManager {
|
|
|
317
315
|
function takeSnapshot(context: AgentContext, options: BuildOptions): StablePrefixSnapshot {
|
|
318
316
|
const systemPrompt = [...context.systemPrompt];
|
|
319
317
|
const tools =
|
|
320
|
-
normalizeTools(context.tools,
|
|
318
|
+
normalizeTools(context.tools, {
|
|
319
|
+
injectIntent: options.intentTracing,
|
|
320
|
+
pruneDescriptions: options.pruneToolDescriptions,
|
|
321
|
+
}) ?? [];
|
|
321
322
|
return {
|
|
322
323
|
systemPrompt,
|
|
323
324
|
tools,
|
|
@@ -337,7 +338,6 @@ function computeFingerprint(systemPrompt: string[], tools: Tool[], options: Buil
|
|
|
337
338
|
cw: t.customWireName,
|
|
338
339
|
})),
|
|
339
340
|
i: options.intentTracing,
|
|
340
|
-
ex: options.exampleDialect,
|
|
341
341
|
pd: options.pruneToolDescriptions,
|
|
342
342
|
});
|
|
343
343
|
let hash = 0;
|
package/src/compaction/shake.ts
CHANGED
|
@@ -18,6 +18,7 @@ import type { CustomMessageEntry, SessionEntry, SessionMessageEntry } from "./en
|
|
|
18
18
|
import { invalidateMessageCache } from "./message-cache";
|
|
19
19
|
import {
|
|
20
20
|
collectToolCallsById,
|
|
21
|
+
isArtifactRecoveryToolResult,
|
|
21
22
|
isProtectedToolResult,
|
|
22
23
|
isSkillReadToolResult,
|
|
23
24
|
type ProtectedToolMatcher,
|
|
@@ -46,11 +47,14 @@ export interface ShakeConfig {
|
|
|
46
47
|
export const DEFAULT_SHAKE_CONFIG: ShakeConfig = {
|
|
47
48
|
protectTokens: 16_000,
|
|
48
49
|
minSavings: 4_000,
|
|
49
|
-
protectedTools: ["skill", isSkillReadToolResult],
|
|
50
|
+
protectedTools: ["skill", isSkillReadToolResult, isArtifactRecoveryToolResult],
|
|
50
51
|
fenceMinTokens: 400,
|
|
51
52
|
};
|
|
52
53
|
|
|
53
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Manual `/shake`: aggressive — drops every eligible region across history,
|
|
56
|
+
* artifact recovery reads included (the user's full escape hatch).
|
|
57
|
+
*/
|
|
54
58
|
export const AGGRESSIVE_SHAKE_CONFIG: ShakeConfig = {
|
|
55
59
|
protectTokens: 0,
|
|
56
60
|
minSavings: 0,
|
|
@@ -58,6 +62,12 @@ export const AGGRESSIVE_SHAKE_CONFIG: ShakeConfig = {
|
|
|
58
62
|
fenceMinTokens: 400,
|
|
59
63
|
};
|
|
60
64
|
|
|
65
|
+
/** Compaction dead-end rescue: aggressive reach, but artifact recovery reads stay protected. */
|
|
66
|
+
export const RESCUE_SHAKE_CONFIG: ShakeConfig = {
|
|
67
|
+
...AGGRESSIVE_SHAKE_CONFIG,
|
|
68
|
+
protectedTools: [...AGGRESSIVE_SHAKE_CONFIG.protectedTools, isArtifactRecoveryToolResult],
|
|
69
|
+
};
|
|
70
|
+
|
|
61
71
|
/** Rough token cost of a placeholder line; used only for the savings gate. */
|
|
62
72
|
const PLACEHOLDER_TOKEN_ESTIMATE = 16;
|
|
63
73
|
|
|
@@ -39,6 +39,16 @@ export function isSkillReadToolResult(context: ProtectedToolContext): boolean {
|
|
|
39
39
|
return getReadToolPath(context)?.startsWith(SKILL_INTERNAL_URL_PREFIX) ?? false;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
const ARTIFACT_INTERNAL_URL_PREFIX = "artifact://";
|
|
43
|
+
|
|
44
|
+
/** Recovery reads of session artifacts — eliding one only mints another artifact and can repeat indefinitely. */
|
|
45
|
+
export function isArtifactRecoveryToolResult(context: ProtectedToolContext): boolean {
|
|
46
|
+
if (getReadToolPath(context)?.startsWith(ARTIFACT_INTERNAL_URL_PREFIX)) return true;
|
|
47
|
+
const meta = (context.toolResult.details as { meta?: { source?: { type?: string; value?: string } } } | undefined)
|
|
48
|
+
?.meta;
|
|
49
|
+
return meta?.source?.type === "internal" && (meta.source.value?.startsWith(ARTIFACT_INTERNAL_URL_PREFIX) ?? false);
|
|
50
|
+
}
|
|
51
|
+
|
|
42
52
|
export function isProtectedToolResult(
|
|
43
53
|
toolResult: ToolResultMessage,
|
|
44
54
|
toolCall: AgentToolCall | undefined,
|