@oh-my-pi/pi-agent-core 18.10.3 → 18.10.4
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 +14 -0
- package/dist/types/agent.d.ts +4 -3
- package/dist/types/compaction/compaction.d.ts +4 -4
- package/dist/types/types.d.ts +11 -2
- package/package.json +8 -8
- package/src/agent.ts +10 -7
- package/src/compaction/branch-summarization.ts +13 -2
- package/src/compaction/compaction.ts +26 -9
- package/src/types.ts +12 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.10.4] - 2026-10-13
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `AgentTool.formatApprovalDetails` receives `ApprovalDetailsOptions`; `compact: true` asks for untruncated bodies so a compact approval prompt can count their lines ([#15550](https://github.com/can1357/oh-my-pi/pull/15550) by [@H4vC](https://github.com/H4vC))
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Before-model-call hooks now receive the loop context as a second argument, and `buildProviderContext` receives the summarizer model ([#15436](https://github.com/can1357/oh-my-pi/pull/15436) by [@will-bogusz](https://github.com/will-bogusz))
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Fixed a Claude compaction or branch summarizer receiving assistant reasoning from a snapcompact archive rendered under another model, which Claude refuses (`reasoning_extraction`) ([#15436](https://github.com/can1357/oh-my-pi/pull/15436) by [@will-bogusz](https://github.com/will-bogusz))
|
|
18
|
+
|
|
5
19
|
## [18.10.3] - 2026-10-12
|
|
6
20
|
|
|
7
21
|
### Breaking Changes
|
package/dist/types/agent.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { Dialect } from "@oh-my-pi/pi-ai/dialect";
|
|
|
3
3
|
import type { HarmonyAuditEvent } from "@oh-my-pi/pi-ai/utils/harmony-leak";
|
|
4
4
|
import type { AppendOnlyContextManager } from "./append-only-context.js";
|
|
5
5
|
import { Tokenizer } from "./tokenizer.js";
|
|
6
|
-
import type { AgentBeforeModelCall, AgentEvent, AgentLoopConfig, AgentMessage, AgentState, AgentTool, AgentToolContext, AgentTurnEndContext, AsideMessage, PrepareQueuedMessages, SpeculativeToolExecutionConfig, StreamFn, ToolCallContext, ToolChoiceDirective } from "./types.js";
|
|
6
|
+
import type { AgentBeforeModelCall, AgentContext, AgentEvent, AgentLoopConfig, AgentMessage, AgentState, AgentTool, AgentToolContext, AgentTurnEndContext, AsideMessage, PrepareQueuedMessages, SpeculativeToolExecutionConfig, StreamFn, ToolCallContext, ToolChoiceDirective } from "./types.js";
|
|
7
7
|
export declare class AgentBusyError extends Error {
|
|
8
8
|
constructor(message?: string);
|
|
9
9
|
}
|
|
@@ -427,8 +427,9 @@ export declare class Agent {
|
|
|
427
427
|
onQueueChange(listener: () => void): () => void;
|
|
428
428
|
/** Register an independently removable hook that runs before queued messages are consumed. */
|
|
429
429
|
addBeforeQueuedMessageDequeueHook(hook: (signal?: AbortSignal) => Promise<void> | void): () => void;
|
|
430
|
-
/** Register an independently removable hook that runs immediately before each model call
|
|
431
|
-
|
|
430
|
+
/** Register an independently removable hook that runs immediately before each model call, with the
|
|
431
|
+
* loop's live context (mutating `context.messages` changes what this and later calls send). */
|
|
432
|
+
addBeforeModelCallHook(hook: (signal: AbortSignal | undefined, context: AgentContext) => Promise<void> | void): () => void;
|
|
432
433
|
/**
|
|
433
434
|
* Take back dequeued input the transcript has not recorded yet, ahead of an abort (Esc restores
|
|
434
435
|
* it to the editor): steering live steering took for the in-flight response, then each batch
|
|
@@ -187,11 +187,11 @@ export interface SummaryOptions {
|
|
|
187
187
|
convertToLlm?: ConvertToLlm;
|
|
188
188
|
/**
|
|
189
189
|
* The provider context a live turn sends for `summarized` + `retained`, cut to
|
|
190
|
-
* `summarized
|
|
191
|
-
* thinking keeps its prefix; without
|
|
192
|
-
* `tools` and `convertToLlm`.
|
|
190
|
+
* `summarized`, as read by the summarizer `model`. Anthropic on-demand
|
|
191
|
+
* compaction sends it so retained signed thinking keeps its prefix; without
|
|
192
|
+
* it the request uses `remoteSystemPrompt`, `tools` and `convertToLlm`.
|
|
193
193
|
*/
|
|
194
|
-
buildProviderContext?: (summarized: AgentMessage[], retained: AgentMessage[], signal
|
|
194
|
+
buildProviderContext?: (summarized: AgentMessage[], retained: AgentMessage[], signal: AbortSignal | undefined, model: Model) => Promise<Context>;
|
|
195
195
|
/**
|
|
196
196
|
* Whether a message is a turn the user wrote. Remote Compaction V2 keeps these
|
|
197
197
|
* next to the compaction item. Defaults to `role === "user"`; hosts whose
|
package/dist/types/types.d.ts
CHANGED
|
@@ -637,10 +637,19 @@ export interface SpeculativePhysicalOutcome {
|
|
|
637
637
|
/** Opaque evidence binding the result to the local bytes actually consumed. */
|
|
638
638
|
evidence?: unknown;
|
|
639
639
|
}
|
|
640
|
+
/** How an approval prompt will show what `formatApprovalDetails` returns. */
|
|
641
|
+
export interface ApprovalDetailsOptions {
|
|
642
|
+
/**
|
|
643
|
+
* The prompt keeps only each detail's first line plus a count of the rest (the transcript
|
|
644
|
+
* shows the call in full): return bodies untruncated so the count is the whole value's.
|
|
645
|
+
* The host truncates the rows it keeps. Otherwise truncate long values as usual.
|
|
646
|
+
*/
|
|
647
|
+
compact?: boolean;
|
|
648
|
+
}
|
|
640
649
|
export interface SpeculativeToolReference {
|
|
641
650
|
name: string;
|
|
642
651
|
approval?: ToolApproval;
|
|
643
|
-
formatApprovalDetails?: (args: unknown) => string | string[] | undefined;
|
|
652
|
+
formatApprovalDetails?: (args: unknown, options?: ApprovalDetailsOptions) => string | string[] | undefined;
|
|
644
653
|
}
|
|
645
654
|
export interface SpeculativeOperationContext extends ToolSpeculationExecutionContext {
|
|
646
655
|
tool: SpeculativeToolReference;
|
|
@@ -1082,7 +1091,7 @@ export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any
|
|
|
1082
1091
|
/** Capability tier declaration used by approval gates. Omitted means "exec". */
|
|
1083
1092
|
approval?: ToolApproval;
|
|
1084
1093
|
/** Lines appended after the standard approval prompt header. */
|
|
1085
|
-
formatApprovalDetails?: (args: unknown) => string | string[] | undefined;
|
|
1094
|
+
formatApprovalDetails?: (args: unknown, options?: ApprovalDetailsOptions) => string | string[] | undefined;
|
|
1086
1095
|
/** The main execution callback for this tool. */
|
|
1087
1096
|
execute: AgentToolExecFn<TParameters, TDetails, TTheme>;
|
|
1088
1097
|
/** Optional custom rendering for tool call display (returns UI component) */
|
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": "18.10.
|
|
4
|
+
"version": "18.10.4",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": {
|
|
@@ -38,16 +38,16 @@
|
|
|
38
38
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@oh-my-pi/pi-ai": "18.10.
|
|
42
|
-
"@oh-my-pi/pi-catalog": "18.10.
|
|
43
|
-
"@oh-my-pi/pi-natives": "18.10.
|
|
44
|
-
"@oh-my-pi/pi-utils": "18.10.
|
|
45
|
-
"@oh-my-pi/pi-wire": "18.10.
|
|
46
|
-
"@oh-my-pi/snapcompact": "18.10.
|
|
41
|
+
"@oh-my-pi/pi-ai": "18.10.4",
|
|
42
|
+
"@oh-my-pi/pi-catalog": "18.10.4",
|
|
43
|
+
"@oh-my-pi/pi-natives": "18.10.4",
|
|
44
|
+
"@oh-my-pi/pi-utils": "18.10.4",
|
|
45
|
+
"@oh-my-pi/pi-wire": "18.10.4",
|
|
46
|
+
"@oh-my-pi/snapcompact": "18.10.4",
|
|
47
47
|
"@opentelemetry/api": "^1.9.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@oh-my-pi/omptype": "18.10.
|
|
50
|
+
"@oh-my-pi/omptype": "18.10.4",
|
|
51
51
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
|
52
52
|
"@opentelemetry/sdk-trace-base": "^2.9.0",
|
|
53
53
|
"@types/bun": "^1.3.14"
|
package/src/agent.ts
CHANGED
|
@@ -504,7 +504,7 @@ export class Agent {
|
|
|
504
504
|
#telemetry?: AgentLoopConfig["telemetry"];
|
|
505
505
|
#appendOnlyContext?: AppendOnlyContextManager;
|
|
506
506
|
#beforeQueuedMessageDequeueHooks = new Set<(signal?: AbortSignal) => Promise<void> | void>();
|
|
507
|
-
#beforeModelCallHooks = new Set<(signal
|
|
507
|
+
#beforeModelCallHooks = new Set<(signal: AbortSignal | undefined, context: AgentContext) => Promise<void> | void>();
|
|
508
508
|
|
|
509
509
|
/** Buffered Cursor tool results with text length at time of call (for correct ordering) */
|
|
510
510
|
#cursorToolResultBuffer: CursorToolResultEntry[] = [];
|
|
@@ -964,15 +964,18 @@ export class Agent {
|
|
|
964
964
|
return () => this.#beforeQueuedMessageDequeueHooks.delete(registration);
|
|
965
965
|
}
|
|
966
966
|
|
|
967
|
-
/** Register an independently removable hook that runs immediately before each model call
|
|
968
|
-
|
|
969
|
-
|
|
967
|
+
/** Register an independently removable hook that runs immediately before each model call, with the
|
|
968
|
+
* loop's live context (mutating `context.messages` changes what this and later calls send). */
|
|
969
|
+
addBeforeModelCallHook(
|
|
970
|
+
hook: (signal: AbortSignal | undefined, context: AgentContext) => Promise<void> | void,
|
|
971
|
+
): () => void {
|
|
972
|
+
const registration = (signal: AbortSignal | undefined, context: AgentContext) => hook(signal, context);
|
|
970
973
|
this.#beforeModelCallHooks.add(registration);
|
|
971
974
|
return () => this.#beforeModelCallHooks.delete(registration);
|
|
972
975
|
}
|
|
973
976
|
|
|
974
|
-
async #runBeforeModelCallHooks(signal
|
|
975
|
-
for (const hook of this.#beforeModelCallHooks) await hook(signal);
|
|
977
|
+
async #runBeforeModelCallHooks(signal: AbortSignal | undefined, context: AgentContext): Promise<void> {
|
|
978
|
+
for (const hook of this.#beforeModelCallHooks) await hook(signal, context);
|
|
976
979
|
}
|
|
977
980
|
|
|
978
981
|
async #runBeforeQueuedMessageDequeueHooks(signal?: AbortSignal): Promise<void> {
|
|
@@ -1884,7 +1887,7 @@ export class Agent {
|
|
|
1884
1887
|
getApiKey: this.getApiKey,
|
|
1885
1888
|
getToolContext: this.#getToolContext,
|
|
1886
1889
|
syncContextBeforeModelCall: async (context, signal) => {
|
|
1887
|
-
await this.#runBeforeModelCallHooks(signal);
|
|
1890
|
+
await this.#runBeforeModelCallHooks(signal, context);
|
|
1888
1891
|
if (this.#listeners.size > 0) {
|
|
1889
1892
|
await Bun.sleep(0);
|
|
1890
1893
|
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import type { Api, ApiKey, AssistantMessage, Context, Model, SimpleStreamOptions } from "@oh-my-pi/pi-ai";
|
|
9
9
|
import { preferredDialect } from "@oh-my-pi/pi-catalog/identity";
|
|
10
10
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
11
|
+
import * as snapcompact from "@oh-my-pi/snapcompact";
|
|
11
12
|
import { type AgentTelemetry, instrumentedCompleteSimple } from "../telemetry";
|
|
12
13
|
import { Tokenizer } from "../tokenizer";
|
|
13
14
|
import type { AgentMessage } from "../types";
|
|
@@ -326,12 +327,22 @@ export async function generateBranchSummary(
|
|
|
326
327
|
const tokenBudget = contextWindow - reserveTokens;
|
|
327
328
|
const tokenizer = new Tokenizer(model);
|
|
328
329
|
|
|
329
|
-
const { messages, fileOps } = prepareBranchEntries(entries, tokenizer, tokenBudget);
|
|
330
|
+
const { messages: branchMessages, fileOps } = prepareBranchEntries(entries, tokenizer, tokenBudget);
|
|
330
331
|
|
|
331
|
-
if (
|
|
332
|
+
if (branchMessages.length === 0) {
|
|
332
333
|
return { summary: "No content to summarize" };
|
|
333
334
|
}
|
|
334
335
|
|
|
336
|
+
// A snapcompact summary rendered for another model may carry reasoning
|
|
337
|
+
// sections and their legend, which Claude refuses (issue #6093).
|
|
338
|
+
const messages = snapcompact.readerIncludesThinking(model)
|
|
339
|
+
? branchMessages
|
|
340
|
+
: branchMessages.map(message =>
|
|
341
|
+
message.role === "compactionSummary"
|
|
342
|
+
? { ...message, summary: snapcompact.textWithoutThinking(message.summary) }
|
|
343
|
+
: message,
|
|
344
|
+
);
|
|
345
|
+
|
|
335
346
|
// Transform to LLM-compatible messages, then serialize to text
|
|
336
347
|
// Serialization prevents the model from treating it as a conversation to continue
|
|
337
348
|
const llmMessages = (options.convertToLlm ?? defaultConvertToLlm)(messages);
|
|
@@ -673,14 +673,15 @@ export interface SummaryOptions {
|
|
|
673
673
|
convertToLlm?: ConvertToLlm;
|
|
674
674
|
/**
|
|
675
675
|
* The provider context a live turn sends for `summarized` + `retained`, cut to
|
|
676
|
-
* `summarized
|
|
677
|
-
* thinking keeps its prefix; without
|
|
678
|
-
* `tools` and `convertToLlm`.
|
|
676
|
+
* `summarized`, as read by the summarizer `model`. Anthropic on-demand
|
|
677
|
+
* compaction sends it so retained signed thinking keeps its prefix; without
|
|
678
|
+
* it the request uses `remoteSystemPrompt`, `tools` and `convertToLlm`.
|
|
679
679
|
*/
|
|
680
680
|
buildProviderContext?: (
|
|
681
681
|
summarized: AgentMessage[],
|
|
682
682
|
retained: AgentMessage[],
|
|
683
|
-
signal
|
|
683
|
+
signal: AbortSignal | undefined,
|
|
684
|
+
model: Model,
|
|
684
685
|
) => Promise<Context>;
|
|
685
686
|
/**
|
|
686
687
|
* Whether a message is a turn the user wrote. Remote Compaction V2 keeps these
|
|
@@ -1647,12 +1648,28 @@ export async function compact(
|
|
|
1647
1648
|
oneshotRetry: options?.oneshotRetry,
|
|
1648
1649
|
};
|
|
1649
1650
|
|
|
1650
|
-
|
|
1651
|
+
// An archive keeps the reasoning policy of the model it was rendered for;
|
|
1652
|
+
// Claude refuses inputs that reproduce reasoning (issue #6093), so a
|
|
1653
|
+
// summarizer whose classifier refuses quoted reasoning (the
|
|
1654
|
+
// `classifier-cot-extraction` catalog axis) reads the archive's no-reasoning view (scrubbed
|
|
1655
|
+
// while its tool-output spans are still marked), and a summary with no
|
|
1656
|
+
// archive behind it is scrubbed of `¶think:` sections.
|
|
1657
|
+
const withoutThinking = !snapcompact.readerIncludesThinking(model);
|
|
1658
|
+
const storedSnapcompactArchive = snapcompact.getPreservedArchive(previousPreserveData);
|
|
1659
|
+
const snapcompactView =
|
|
1660
|
+
storedSnapcompactArchive && withoutThinking
|
|
1661
|
+
? snapcompact.viewWithoutThinking(previousSummary ?? "", storedSnapcompactArchive)
|
|
1662
|
+
: undefined;
|
|
1663
|
+
const previousSnapcompactArchive = snapcompactView?.archive ?? storedSnapcompactArchive;
|
|
1664
|
+
const previousSummaryForReader =
|
|
1665
|
+
withoutThinking && previousSummary !== undefined
|
|
1666
|
+
? (snapcompactView?.summary ?? snapcompact.textWithoutThinking(previousSummary))
|
|
1667
|
+
: previousSummary;
|
|
1651
1668
|
const previousSnapcompactArchiveText = previousSnapcompactArchive
|
|
1652
1669
|
? snapcompact.archiveSourceText(previousSnapcompactArchive)
|
|
1653
1670
|
: undefined;
|
|
1654
1671
|
const previousSummaryForCompaction = mergePreviousSummaryWithSnapcompactArchive(
|
|
1655
|
-
|
|
1672
|
+
previousSummaryForReader,
|
|
1656
1673
|
previousSnapcompactArchiveText,
|
|
1657
1674
|
);
|
|
1658
1675
|
const snapcompactArchiveMigrationMessage = previousSnapcompactArchiveText
|
|
@@ -1663,8 +1680,8 @@ export async function compact(
|
|
|
1663
1680
|
// A local summary has no native payload to carry it into the first remote
|
|
1664
1681
|
// request. Encode it as history; do not resend opaque native placeholders.
|
|
1665
1682
|
const previousSummaryMigrationMessage =
|
|
1666
|
-
settings.remoteEnabled !== false &&
|
|
1667
|
-
? createCompactionSummaryMessage(
|
|
1683
|
+
settings.remoteEnabled !== false && previousSummaryForReader && !previousNativeHistory
|
|
1684
|
+
? createCompactionSummaryMessage(previousSummaryForReader, tokensBefore, new Date().toISOString())
|
|
1668
1685
|
: undefined;
|
|
1669
1686
|
|
|
1670
1687
|
let preserveData = withAnthropicCompactionPreserveData(
|
|
@@ -1981,7 +1998,7 @@ export async function compact(
|
|
|
1981
1998
|
// history when the host can build them: kept thinking remains valid
|
|
1982
1999
|
// only when the summarized prefix matches what was sent byte for byte.
|
|
1983
2000
|
const context = summaryOptions.buildProviderContext
|
|
1984
|
-
? await summaryOptions.buildProviderContext(summarizedMessages, allMessages.slice(safeCut), signal)
|
|
2001
|
+
? await summaryOptions.buildProviderContext(summarizedMessages, allMessages.slice(safeCut), signal, model)
|
|
1985
2002
|
: {
|
|
1986
2003
|
systemPrompt: summaryOptions.remoteSystemPrompt ?? [],
|
|
1987
2004
|
messages: (summaryOptions.convertToLlm ?? defaultConvertToLlm)(summarizedMessages),
|
package/src/types.ts
CHANGED
|
@@ -718,10 +718,20 @@ export interface SpeculativePhysicalOutcome {
|
|
|
718
718
|
evidence?: unknown;
|
|
719
719
|
}
|
|
720
720
|
|
|
721
|
+
/** How an approval prompt will show what `formatApprovalDetails` returns. */
|
|
722
|
+
export interface ApprovalDetailsOptions {
|
|
723
|
+
/**
|
|
724
|
+
* The prompt keeps only each detail's first line plus a count of the rest (the transcript
|
|
725
|
+
* shows the call in full): return bodies untruncated so the count is the whole value's.
|
|
726
|
+
* The host truncates the rows it keeps. Otherwise truncate long values as usual.
|
|
727
|
+
*/
|
|
728
|
+
compact?: boolean;
|
|
729
|
+
}
|
|
730
|
+
|
|
721
731
|
export interface SpeculativeToolReference {
|
|
722
732
|
name: string;
|
|
723
733
|
approval?: ToolApproval;
|
|
724
|
-
formatApprovalDetails?: (args: unknown) => string | string[] | undefined;
|
|
734
|
+
formatApprovalDetails?: (args: unknown, options?: ApprovalDetailsOptions) => string | string[] | undefined;
|
|
725
735
|
}
|
|
726
736
|
|
|
727
737
|
export interface SpeculativeOperationContext extends ToolSpeculationExecutionContext {
|
|
@@ -1218,7 +1228,7 @@ export interface AgentTool<
|
|
|
1218
1228
|
approval?: ToolApproval;
|
|
1219
1229
|
|
|
1220
1230
|
/** Lines appended after the standard approval prompt header. */
|
|
1221
|
-
formatApprovalDetails?: (args: unknown) => string | string[] | undefined;
|
|
1231
|
+
formatApprovalDetails?: (args: unknown, options?: ApprovalDetailsOptions) => string | string[] | undefined;
|
|
1222
1232
|
|
|
1223
1233
|
/** The main execution callback for this tool. */
|
|
1224
1234
|
execute: AgentToolExecFn<TParameters, TDetails, TTheme>;
|