@oh-my-pi/pi-agent-core 16.1.22 → 16.1.23
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 +10 -0
- package/dist/types/agent.d.ts +2 -2
- package/dist/types/compaction/compaction.d.ts +1 -0
- package/dist/types/types.d.ts +12 -4
- package/package.json +7 -7
- package/src/agent-loop.ts +7 -3
- package/src/agent.ts +8 -3
- package/src/compaction/compaction.ts +56 -7
- package/src/compaction/prompts/snapcompact-archive-context.md +3 -0
- package/src/types.ts +13 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [16.1.23] - 2026-06-26
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Changed `AgentLoopConfig.onTurnEnd` and `Agent.setOnTurnEnd` callbacks to receive whether the loop will continue with another provider request.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed stale snapcompact archive frames leaking into context-full compaction after `compaction.strategy` was switched from `snapcompact` to `context-full`. Switching strategy left the latest compaction entry's `preserveData.snapcompact` in place, so context-full kept rebuilding context with old image frames attached — inflating context/token usage and making sessions appear to compact early (around ~60% apparent window use). The first context-full compaction after the switch now folds the prior archive's plaintext into the LLM summary input and strips `preserveData.snapcompact` from the new entry; legacy frame-only archives (no plaintext to migrate) are stripped outright. ([#3561](https://github.com/can1357/oh-my-pi/pull/3561) by [@serverinspector](https://github.com/serverinspector))
|
|
14
|
+
|
|
5
15
|
## [16.1.18] - 2026-06-25
|
|
6
16
|
|
|
7
17
|
### Fixed
|
package/dist/types/agent.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type ApiKey, type AssistantMessage, type AssistantMessageEvent, type Co
|
|
|
2
2
|
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";
|
|
5
|
-
import type { AgentEvent, AgentLoopConfig, AgentMessage, AgentState, AgentTool, AgentToolContext, AsideMessage, StreamFn, ToolCallContext, ToolChoiceDirective } from "./types";
|
|
5
|
+
import type { AgentEvent, AgentLoopConfig, AgentMessage, AgentState, AgentTool, AgentToolContext, AgentTurnEndContext, AsideMessage, StreamFn, ToolCallContext, ToolChoiceDirective } from "./types";
|
|
6
6
|
export declare class AgentBusyError extends Error {
|
|
7
7
|
constructor(message?: string);
|
|
8
8
|
}
|
|
@@ -341,7 +341,7 @@ export declare class Agent {
|
|
|
341
341
|
setRawSseEventInterceptor(fn: SimpleStreamOptions["onSseEvent"] | undefined): void;
|
|
342
342
|
setAssistantMessageEventInterceptor(fn: ((message: AssistantMessage, event: AssistantMessageEvent) => void) | undefined): void;
|
|
343
343
|
setOnBeforeYield(fn: (() => Promise<void> | void) | undefined): void;
|
|
344
|
-
setOnTurnEnd(fn: ((messages: AgentMessage[], signal?: AbortSignal) => Promise<void> | void) | undefined): void;
|
|
344
|
+
setOnTurnEnd(fn: ((messages: AgentMessage[], signal?: AbortSignal, context?: AgentTurnEndContext) => Promise<void> | void) | undefined): void;
|
|
345
345
|
/**
|
|
346
346
|
* Provide a source of non-interrupting "aside" messages (e.g. background-job
|
|
347
347
|
* completions, late LSP diagnostics) drained at each step boundary. Never
|
|
@@ -33,6 +33,7 @@ export interface CompactionSettings {
|
|
|
33
33
|
strategy?: "context-full" | "handoff" | "shake" | "snapcompact" | "off";
|
|
34
34
|
thresholdPercent?: number;
|
|
35
35
|
thresholdTokens?: number;
|
|
36
|
+
midTurnEnabled?: boolean;
|
|
36
37
|
reserveTokens: number;
|
|
37
38
|
keepRecentTokens: number;
|
|
38
39
|
autoContinue?: boolean;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -13,6 +13,14 @@ export type StreamFn = (...args: Parameters<typeof streamSimple>) => AssistantMe
|
|
|
13
13
|
* (e.g. dropping late diagnostics a newer edit superseded).
|
|
14
14
|
*/
|
|
15
15
|
export type AsideMessage = AgentMessage | (() => AgentMessage | null);
|
|
16
|
+
export interface AgentTurnEndContext {
|
|
17
|
+
/** Assistant/user message that just completed this turn boundary. */
|
|
18
|
+
message: AgentMessage;
|
|
19
|
+
/** Tool results produced by this turn, already paired with `message` in the live context. */
|
|
20
|
+
toolResults: ToolResultMessage[];
|
|
21
|
+
/** True when the current tool-loop batch is continuing without yielding to post-turn steering. */
|
|
22
|
+
willContinue: boolean;
|
|
23
|
+
}
|
|
16
24
|
/**
|
|
17
25
|
* A soft tool requirement: the host wants `toolName` called before the loop
|
|
18
26
|
* runs other tools or yields, but WITHOUT paying the forced-`toolChoice` cost
|
|
@@ -292,11 +300,11 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
292
300
|
beforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined> | BeforeToolCallResult | undefined;
|
|
293
301
|
/**
|
|
294
302
|
* Called after a turn ends and before the loop polls steering/asides for the
|
|
295
|
-
* next iteration.
|
|
296
|
-
*
|
|
297
|
-
*
|
|
303
|
+
* next iteration. `context` carries the just-finished turn; `context.willContinue`
|
|
304
|
+
* is true when the current tool-loop batch is continuing without yielding to
|
|
305
|
+
* post-turn steering.
|
|
298
306
|
*/
|
|
299
|
-
onTurnEnd?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<void> | void;
|
|
307
|
+
onTurnEnd?: (messages: AgentMessage[], signal?: AbortSignal, context?: AgentTurnEndContext) => Promise<void> | void;
|
|
300
308
|
/**
|
|
301
309
|
* Called once an assistant message is finalized from the model stream, before
|
|
302
310
|
* it is appended to the context, emitted as `message_end`, or its tool calls
|
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": "16.1.
|
|
4
|
+
"version": "16.1.23",
|
|
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": "16.1.
|
|
39
|
-
"@oh-my-pi/pi-catalog": "16.1.
|
|
40
|
-
"@oh-my-pi/pi-natives": "16.1.
|
|
41
|
-
"@oh-my-pi/pi-utils": "16.1.
|
|
42
|
-
"@oh-my-pi/pi-wire": "16.1.
|
|
43
|
-
"@oh-my-pi/snapcompact": "16.1.
|
|
38
|
+
"@oh-my-pi/pi-ai": "16.1.23",
|
|
39
|
+
"@oh-my-pi/pi-catalog": "16.1.23",
|
|
40
|
+
"@oh-my-pi/pi-natives": "16.1.23",
|
|
41
|
+
"@oh-my-pi/pi-utils": "16.1.23",
|
|
42
|
+
"@oh-my-pi/pi-wire": "16.1.23",
|
|
43
|
+
"@oh-my-pi/snapcompact": "16.1.23",
|
|
44
44
|
"@opentelemetry/api": "^1.9.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
package/src/agent-loop.ts
CHANGED
|
@@ -62,6 +62,7 @@ import type {
|
|
|
62
62
|
AgentMessage,
|
|
63
63
|
AgentTool,
|
|
64
64
|
AgentToolResult,
|
|
65
|
+
AgentTurnEndContext,
|
|
65
66
|
AsideMessage,
|
|
66
67
|
StreamFn,
|
|
67
68
|
} from "./types";
|
|
@@ -406,12 +407,13 @@ async function emitTurnEnd(
|
|
|
406
407
|
toolResults: ToolResultMessage[],
|
|
407
408
|
config: AgentLoopConfig,
|
|
408
409
|
signal?: AbortSignal,
|
|
410
|
+
context?: Omit<AgentTurnEndContext, "message" | "toolResults">,
|
|
409
411
|
): Promise<void> {
|
|
410
412
|
stream.push({ type: "turn_end", message, toolResults });
|
|
411
413
|
const isAbortedOrError =
|
|
412
414
|
message.role === "assistant" && (message.stopReason === "aborted" || message.stopReason === "error");
|
|
413
415
|
if (signal?.aborted || isAbortedOrError) return;
|
|
414
|
-
await config.onTurnEnd?.(currentContext.messages, signal);
|
|
416
|
+
await config.onTurnEnd?.(currentContext.messages, signal, { message, toolResults, willContinue: false, ...context });
|
|
415
417
|
}
|
|
416
418
|
|
|
417
419
|
/**
|
|
@@ -903,7 +905,7 @@ async function runLoopBody(
|
|
|
903
905
|
status: message.stopReason === "aborted" ? "aborted" : "error",
|
|
904
906
|
});
|
|
905
907
|
}
|
|
906
|
-
await emitTurnEnd(stream, currentContext, message, toolResults, config, signal);
|
|
908
|
+
await emitTurnEnd(stream, currentContext, message, toolResults, config, signal, { willContinue: false });
|
|
907
909
|
|
|
908
910
|
stream.push(buildAgentEndEvent(newMessages, telemetry, stepCounter.count));
|
|
909
911
|
stream.end(newMessages);
|
|
@@ -1032,7 +1034,9 @@ async function runLoopBody(
|
|
|
1032
1034
|
hasMoreToolCalls = true;
|
|
1033
1035
|
}
|
|
1034
1036
|
|
|
1035
|
-
await emitTurnEnd(stream, currentContext, message, toolResults, config, signal
|
|
1037
|
+
await emitTurnEnd(stream, currentContext, message, toolResults, config, signal, {
|
|
1038
|
+
willContinue: hasMoreToolCalls && !isDeadlineExceeded(config.deadline),
|
|
1039
|
+
});
|
|
1036
1040
|
|
|
1037
1041
|
if (isDeadlineExceeded(config.deadline)) {
|
|
1038
1042
|
endAgentStream(stream, newMessages, telemetry, stepCounter.count);
|
package/src/agent.ts
CHANGED
|
@@ -44,6 +44,7 @@ import type {
|
|
|
44
44
|
AgentState,
|
|
45
45
|
AgentTool,
|
|
46
46
|
AgentToolContext,
|
|
47
|
+
AgentTurnEndContext,
|
|
47
48
|
AsideMessage,
|
|
48
49
|
StreamFn,
|
|
49
50
|
ToolCallContext,
|
|
@@ -370,7 +371,7 @@ export class Agent {
|
|
|
370
371
|
#onAssistantMessageEvent?: (message: AssistantMessage, event: AssistantMessageEvent) => void;
|
|
371
372
|
#onHarmonyLeak?: (event: HarmonyAuditEvent) => void | Promise<void>;
|
|
372
373
|
#onBeforeYield?: () => Promise<void> | void;
|
|
373
|
-
#onTurnEnd?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<void> | void;
|
|
374
|
+
#onTurnEnd?: (messages: AgentMessage[], signal?: AbortSignal, context?: AgentTurnEndContext) => Promise<void> | void;
|
|
374
375
|
#asideMessageProvider?: () => AsideMessage[] | Promise<AsideMessage[]>;
|
|
375
376
|
#telemetry?: AgentLoopConfig["telemetry"];
|
|
376
377
|
#appendOnlyContext?: AppendOnlyContextManager;
|
|
@@ -726,7 +727,11 @@ export class Agent {
|
|
|
726
727
|
setOnBeforeYield(fn: (() => Promise<void> | void) | undefined): void {
|
|
727
728
|
this.#onBeforeYield = fn;
|
|
728
729
|
}
|
|
729
|
-
setOnTurnEnd(
|
|
730
|
+
setOnTurnEnd(
|
|
731
|
+
fn:
|
|
732
|
+
| ((messages: AgentMessage[], signal?: AbortSignal, context?: AgentTurnEndContext) => Promise<void> | void)
|
|
733
|
+
| undefined,
|
|
734
|
+
): void {
|
|
730
735
|
this.#onTurnEnd = fn;
|
|
731
736
|
}
|
|
732
737
|
|
|
@@ -1142,7 +1147,7 @@ export class Agent {
|
|
|
1142
1147
|
: undefined,
|
|
1143
1148
|
onAssistantMessageEvent: this.#onAssistantMessageEvent,
|
|
1144
1149
|
onHarmonyLeak: this.#onHarmonyLeak,
|
|
1145
|
-
onTurnEnd: (messages, signal) => this.#onTurnEnd?.(messages, signal),
|
|
1150
|
+
onTurnEnd: (messages, signal, context) => this.#onTurnEnd?.(messages, signal, context),
|
|
1146
1151
|
getToolChoice,
|
|
1147
1152
|
getReasoning: () => this.#state.thinkingLevel,
|
|
1148
1153
|
getDisableReasoning: () => this.#state.disableReasoning,
|
|
@@ -44,6 +44,7 @@ import compactionSummaryPrompt from "./prompts/compaction-summary.md" with { typ
|
|
|
44
44
|
import compactionTurnPrefixPrompt from "./prompts/compaction-turn-prefix.md" with { type: "text" };
|
|
45
45
|
import compactionUpdateSummaryPrompt from "./prompts/compaction-update-summary.md" with { type: "text" };
|
|
46
46
|
import handoffDocumentPrompt from "./prompts/handoff-document.md" with { type: "text" };
|
|
47
|
+
import snapcompactArchiveContextPrompt from "./prompts/snapcompact-archive-context.md" with { type: "text" };
|
|
47
48
|
|
|
48
49
|
import {
|
|
49
50
|
computeFileLists,
|
|
@@ -148,6 +149,7 @@ export interface CompactionSettings {
|
|
|
148
149
|
strategy?: "context-full" | "handoff" | "shake" | "snapcompact" | "off";
|
|
149
150
|
thresholdPercent?: number;
|
|
150
151
|
thresholdTokens?: number;
|
|
152
|
+
midTurnEnabled?: boolean;
|
|
151
153
|
reserveTokens: number;
|
|
152
154
|
keepRecentTokens: number;
|
|
153
155
|
autoContinue?: boolean;
|
|
@@ -160,6 +162,7 @@ export const DEFAULT_COMPACTION_SETTINGS: CompactionSettings = {
|
|
|
160
162
|
strategy: "context-full",
|
|
161
163
|
thresholdPercent: -1,
|
|
162
164
|
thresholdTokens: -1,
|
|
165
|
+
midTurnEnabled: true,
|
|
163
166
|
reserveTokens: 16384,
|
|
164
167
|
keepRecentTokens: 20000,
|
|
165
168
|
autoContinue: true,
|
|
@@ -658,6 +661,27 @@ export interface SummaryOptions {
|
|
|
658
661
|
fetch?: FetchImpl;
|
|
659
662
|
}
|
|
660
663
|
|
|
664
|
+
function formatPreviousSnapcompactArchive(archiveText: string): string {
|
|
665
|
+
return prompt.render(snapcompactArchiveContextPrompt, { archiveText });
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function mergePreviousSummaryWithSnapcompactArchive(
|
|
669
|
+
previousSummary: string | undefined,
|
|
670
|
+
archiveText: string | undefined,
|
|
671
|
+
): string | undefined {
|
|
672
|
+
if (!archiveText) return previousSummary;
|
|
673
|
+
const archiveSummary = formatPreviousSnapcompactArchive(archiveText);
|
|
674
|
+
return previousSummary ? `${previousSummary}\n\n${archiveSummary}` : archiveSummary;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function createSnapcompactArchiveMigrationMessage(archiveText: string): Message {
|
|
678
|
+
return {
|
|
679
|
+
role: "user",
|
|
680
|
+
content: [{ type: "text", text: formatPreviousSnapcompactArchive(archiveText) }],
|
|
681
|
+
timestamp: Date.now(),
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
661
685
|
export async function generateSummary(
|
|
662
686
|
currentMessages: AgentMessage[],
|
|
663
687
|
model: Model,
|
|
@@ -1101,10 +1125,27 @@ export async function compact(
|
|
|
1101
1125
|
fetch: options?.fetch,
|
|
1102
1126
|
};
|
|
1103
1127
|
|
|
1128
|
+
const previousSnapcompactArchive = snapcompact.getPreservedArchive(previousPreserveData);
|
|
1129
|
+
const previousSnapcompactArchiveText = previousSnapcompactArchive
|
|
1130
|
+
? snapcompact.archiveSourceText(previousSnapcompactArchive)
|
|
1131
|
+
: undefined;
|
|
1132
|
+
const previousSummaryForCompaction = mergePreviousSummaryWithSnapcompactArchive(
|
|
1133
|
+
previousSummary,
|
|
1134
|
+
previousSnapcompactArchiveText,
|
|
1135
|
+
);
|
|
1136
|
+
const snapcompactArchiveMigrationMessage = previousSnapcompactArchiveText
|
|
1137
|
+
? createSnapcompactArchiveMigrationMessage(previousSnapcompactArchiveText)
|
|
1138
|
+
: undefined;
|
|
1139
|
+
|
|
1104
1140
|
let preserveData = withOpenAiRemoteCompactionPreserveData(previousPreserveData, undefined);
|
|
1105
1141
|
if (settings.remoteEnabled !== false && shouldUseOpenAiRemoteCompaction(model)) {
|
|
1106
1142
|
const previousRemoteCompaction = getPreservedOpenAiRemoteCompactionData(previousPreserveData);
|
|
1107
|
-
const remoteMessages = [
|
|
1143
|
+
const remoteMessages: AgentMessage[] = [
|
|
1144
|
+
...(snapcompactArchiveMigrationMessage ? [snapcompactArchiveMigrationMessage] : []),
|
|
1145
|
+
...messagesToSummarize,
|
|
1146
|
+
...turnPrefixMessages,
|
|
1147
|
+
...recentMessages,
|
|
1148
|
+
];
|
|
1108
1149
|
const previousReplacementHistory =
|
|
1109
1150
|
previousRemoteCompaction?.provider === model.provider
|
|
1110
1151
|
? previousRemoteCompaction.replacementHistory
|
|
@@ -1150,7 +1191,7 @@ export async function compact(
|
|
|
1150
1191
|
if (isSplitTurn && turnPrefixMessages.length > 0) {
|
|
1151
1192
|
// Generate both summaries in parallel
|
|
1152
1193
|
const [historyResult, turnPrefixResult] = await Promise.all([
|
|
1153
|
-
messagesToSummarize.length > 0
|
|
1194
|
+
messagesToSummarize.length > 0 || previousSummaryForCompaction
|
|
1154
1195
|
? generateSummary(
|
|
1155
1196
|
messagesToSummarize,
|
|
1156
1197
|
model,
|
|
@@ -1158,7 +1199,7 @@ export async function compact(
|
|
|
1158
1199
|
apiKey,
|
|
1159
1200
|
signal,
|
|
1160
1201
|
customInstructions,
|
|
1161
|
-
|
|
1202
|
+
previousSummaryForCompaction,
|
|
1162
1203
|
summaryOptions,
|
|
1163
1204
|
)
|
|
1164
1205
|
: Promise.resolve("No prior history."),
|
|
@@ -1175,12 +1216,12 @@ export async function compact(
|
|
|
1175
1216
|
apiKey,
|
|
1176
1217
|
signal,
|
|
1177
1218
|
customInstructions,
|
|
1178
|
-
|
|
1219
|
+
previousSummaryForCompaction,
|
|
1179
1220
|
summaryOptions,
|
|
1180
1221
|
);
|
|
1181
|
-
} else if (
|
|
1222
|
+
} else if (previousSummaryForCompaction) {
|
|
1182
1223
|
// No new messages to summarize, preserve previous summary
|
|
1183
|
-
summary =
|
|
1224
|
+
summary = previousSummaryForCompaction;
|
|
1184
1225
|
} else {
|
|
1185
1226
|
// No messages and no previous summary
|
|
1186
1227
|
summary = "No prior history.";
|
|
@@ -1214,13 +1255,21 @@ export async function compact(
|
|
|
1214
1255
|
throw new Error("First kept entry has no ID - session may need migration");
|
|
1215
1256
|
}
|
|
1216
1257
|
|
|
1258
|
+
// This LLM-summary path migrated any prior snapcompact frames into the summary
|
|
1259
|
+
// text above; strip the now-stale frame archive from preserveData so it cannot
|
|
1260
|
+
// re-attach to the rebuilt context. Only the legacy-frame case needs stripping —
|
|
1261
|
+
// when there was no previous archive, preserveData carries no frames to drop.
|
|
1262
|
+
const finalPreserveData = previousSnapcompactArchive
|
|
1263
|
+
? snapcompact.stripPreservedArchive(preserveData)
|
|
1264
|
+
: preserveData;
|
|
1265
|
+
|
|
1217
1266
|
return {
|
|
1218
1267
|
summary,
|
|
1219
1268
|
shortSummary,
|
|
1220
1269
|
firstKeptEntryId,
|
|
1221
1270
|
tokensBefore,
|
|
1222
1271
|
details: { readFiles, modifiedFiles } as CompactionDetails,
|
|
1223
|
-
preserveData,
|
|
1272
|
+
preserveData: finalPreserveData,
|
|
1224
1273
|
};
|
|
1225
1274
|
}
|
|
1226
1275
|
|
package/src/types.ts
CHANGED
|
@@ -37,6 +37,15 @@ export type StreamFn = (
|
|
|
37
37
|
*/
|
|
38
38
|
export type AsideMessage = AgentMessage | (() => AgentMessage | null);
|
|
39
39
|
|
|
40
|
+
export interface AgentTurnEndContext {
|
|
41
|
+
/** Assistant/user message that just completed this turn boundary. */
|
|
42
|
+
message: AgentMessage;
|
|
43
|
+
/** Tool results produced by this turn, already paired with `message` in the live context. */
|
|
44
|
+
toolResults: ToolResultMessage[];
|
|
45
|
+
/** True when the current tool-loop batch is continuing without yielding to post-turn steering. */
|
|
46
|
+
willContinue: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
/**
|
|
41
50
|
* A soft tool requirement: the host wants `toolName` called before the loop
|
|
42
51
|
* runs other tools or yields, but WITHOUT paying the forced-`toolChoice` cost
|
|
@@ -346,11 +355,11 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
346
355
|
) => Promise<BeforeToolCallResult | undefined> | BeforeToolCallResult | undefined;
|
|
347
356
|
/**
|
|
348
357
|
* Called after a turn ends and before the loop polls steering/asides for the
|
|
349
|
-
* next iteration.
|
|
350
|
-
*
|
|
351
|
-
*
|
|
358
|
+
* next iteration. `context` carries the just-finished turn; `context.willContinue`
|
|
359
|
+
* is true when the current tool-loop batch is continuing without yielding to
|
|
360
|
+
* post-turn steering.
|
|
352
361
|
*/
|
|
353
|
-
onTurnEnd?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<void> | void;
|
|
362
|
+
onTurnEnd?: (messages: AgentMessage[], signal?: AbortSignal, context?: AgentTurnEndContext) => Promise<void> | void;
|
|
354
363
|
|
|
355
364
|
/**
|
|
356
365
|
* Called once an assistant message is finalized from the model stream, before
|