@gajae-code/agent-core 0.16.3 → 0.16.6
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/compaction/branch-summarization.d.ts +2 -0
- package/dist/types/compaction/compaction.d.ts +4 -0
- package/dist/types/types.d.ts +7 -5
- package/package.json +4 -4
- package/src/agent-loop.ts +16 -5
- package/src/agent.ts +8 -5
- package/src/compaction/branch-summarization.ts +13 -1
- package/src/compaction/compaction.ts +28 -3
- package/src/types.ts +10 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.16.6] - 2026-09-07
|
|
6
|
+
|
|
7
|
+
## [0.16.5] - 2026-09-07
|
|
8
|
+
|
|
9
|
+
- Compaction summary, turn-prefix summary, and handoff generation now clamp their reasoning effort to the model they are about to call instead of hard-coding `high`. A reasoning-capable model on a transport without reasoning control (the registry strips `thinking` when `openai-codex` or `anthropic` is routed through a non-audited proxy `baseUrl`) rejected the raw effort inside the provider mapper with "Model <provider>/<id> does not support thinking"; since the compaction fallback chain then reaches for the same-provider largest-context model, every candidate died on that throw and auto-compaction reported only the last one. The agent turn already clamps through `clampThinkingLevelForModel`; the maintenance calls were the one path still sending an unclamped effort.
|
|
10
|
+
|
|
11
|
+
## [0.16.4] - 2026-09-05
|
|
12
|
+
|
|
13
|
+
- Provider calls now carry the agent-owned opaque provider conversation identity separately from generic session/cache affinity, including compaction, handoff, turn-prefix summary, and branch-summary maintenance calls. This lets provider-specific conversation headers remain stable without treating prompt-derived gateway cache keys as conversation authority (#5295).
|
|
14
|
+
|
|
15
|
+
- Failed cooperative context maintenance now terminalizes the active run with its concrete maintenance error instead of advertising a continuation over unchanged context. Only committed prune, compaction, or promotion outcomes remain resumable.
|
|
16
|
+
|
|
5
17
|
## [0.16.3] - 2026-09-04
|
|
6
18
|
|
|
7
19
|
## [0.16.2] - 2026-09-04
|
|
@@ -62,6 +62,8 @@ export interface GenerateBranchSummaryOptions {
|
|
|
62
62
|
* reuses the live turn's provider/WebSocket session.
|
|
63
63
|
*/
|
|
64
64
|
sessionId?: string;
|
|
65
|
+
/** Opaque provider conversation identity; never derived from branch-summary content. */
|
|
66
|
+
providerSessionId?: string;
|
|
65
67
|
/** Shared provider state map so the branch summary call reuses session-scoped transport/session caches. */
|
|
66
68
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
67
69
|
/** Hint that websocket transport should be preferred when supported by the provider implementation. */
|
|
@@ -209,6 +209,8 @@ export interface SummaryOptions {
|
|
|
209
209
|
* `providerSessionId ?? sessionId` the agent loop sends for normal turns).
|
|
210
210
|
*/
|
|
211
211
|
sessionId?: string;
|
|
212
|
+
/** Opaque provider conversation identity; never derived from compaction content. */
|
|
213
|
+
providerSessionId?: string;
|
|
212
214
|
/** Shared provider state map so maintenance calls reuse session-scoped transport/session caches. */
|
|
213
215
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
214
216
|
/** Hint that websocket transport should be preferred when supported by the provider implementation. */
|
|
@@ -262,6 +264,8 @@ export interface HandoffOptions {
|
|
|
262
264
|
* reuses the live turn's provider/WebSocket session.
|
|
263
265
|
*/
|
|
264
266
|
sessionId?: string;
|
|
267
|
+
/** Opaque provider conversation identity; never derived from handoff content. */
|
|
268
|
+
providerSessionId?: string;
|
|
265
269
|
/** Shared provider state map so the handoff call reuses session-scoped transport/session caches. */
|
|
266
270
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
267
271
|
/** Hint that websocket transport should be preferred when supported by the provider implementation. */
|
package/dist/types/types.d.ts
CHANGED
|
@@ -195,14 +195,16 @@ export type ManagedAttemptOutcomeHandler = (outcome: ManagedAttemptOutcome) => M
|
|
|
195
195
|
* Outcome of a cooperative mid-run context-maintenance checkpoint (see
|
|
196
196
|
* {@link AgentLoopConfig.maintainContext}). Any value other than "not-needed"
|
|
197
197
|
* means the checkpoint mutated (or attempted to mutate) durable context, so the
|
|
198
|
-
* loop ends the current run without
|
|
199
|
-
*
|
|
198
|
+
* loop ends the current run without lossy finalization. Committed maintenance
|
|
199
|
+
* resumes on rewritten context; failed or aborted maintenance terminalizes.
|
|
200
200
|
*/
|
|
201
201
|
export type MidRunMaintenanceOutcome = "not-needed" | "pruned" | "compacted" | "promoted" | "failed" | "aborted";
|
|
202
202
|
export interface ContextMaintenanceResult {
|
|
203
203
|
outcome: MidRunMaintenanceOutcome;
|
|
204
204
|
releaseCurrentContext?: boolean;
|
|
205
|
+
errorMessage?: string;
|
|
205
206
|
}
|
|
207
|
+
export declare function isContinuingMidRunMaintenanceOutcome(outcome: unknown): boolean;
|
|
206
208
|
/**
|
|
207
209
|
* Configuration for the agent loop.
|
|
208
210
|
*/
|
|
@@ -407,9 +409,9 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
407
409
|
* non-optional loop signal, and `awaitEventDrain(invocationSignal)` waits for
|
|
408
410
|
* prior event consumer bodies with loop and invocation cancellation composed.
|
|
409
411
|
* Any outcome other than "not-needed" ends the current run with
|
|
410
|
-
* `agent_end.stopReason === "maintenance"
|
|
411
|
-
*
|
|
412
|
-
*
|
|
412
|
+
* `agent_end.stopReason === "maintenance"`. Successful maintenance resumes
|
|
413
|
+
* on rewritten context; `failed` and `aborted` are terminal and never resend
|
|
414
|
+
* the unchanged context.
|
|
413
415
|
*/
|
|
414
416
|
maintainContext?: (context: AgentContext, lifecycle: {
|
|
415
417
|
signal: AbortSignal;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/agent-core",
|
|
4
|
-
"version": "0.16.
|
|
4
|
+
"version": "0.16.6",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://gajae-code.com",
|
|
7
7
|
"author": "Yeachan-Heo and Gajae Code Contributors",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"fmt": "biome format --write ."
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@gajae-code/ai": "0.16.
|
|
36
|
-
"@gajae-code/natives": "0.16.
|
|
37
|
-
"@gajae-code/utils": "0.16.
|
|
35
|
+
"@gajae-code/ai": "0.16.6",
|
|
36
|
+
"@gajae-code/natives": "0.16.6",
|
|
37
|
+
"@gajae-code/utils": "0.16.6",
|
|
38
38
|
"@opentelemetry/api": "^1.9.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
package/src/agent-loop.ts
CHANGED
|
@@ -89,6 +89,7 @@ import {
|
|
|
89
89
|
type AgentTool,
|
|
90
90
|
type AgentToolContext,
|
|
91
91
|
type AgentToolResult,
|
|
92
|
+
isContinuingMidRunMaintenanceOutcome,
|
|
92
93
|
type ManagedAttemptOutcome,
|
|
93
94
|
type StandaloneRunOwnership,
|
|
94
95
|
type StreamFn,
|
|
@@ -1260,13 +1261,12 @@ function publishAgentEnd(
|
|
|
1260
1261
|
event: Extract<AgentEvent, { type: "agent_end" }>,
|
|
1261
1262
|
scope?: AttemptScope,
|
|
1262
1263
|
): void {
|
|
1263
|
-
//
|
|
1264
|
-
//
|
|
1265
|
-
// stopReason so AgentSession can still report the aborted maintenance
|
|
1266
|
-
// settlement to its consumers.
|
|
1264
|
+
// Only maintenance that committed a context mutation can continue. Failed and
|
|
1265
|
+
// aborted maintenance are terminal so the unchanged context is never resent.
|
|
1267
1266
|
const publishedEvent = scope ? { ...event, scope } : event;
|
|
1268
1267
|
const maintenanceContinues =
|
|
1269
|
-
publishedEvent.stopReason === "maintenance" &&
|
|
1268
|
+
publishedEvent.stopReason === "maintenance" &&
|
|
1269
|
+
isContinuingMidRunMaintenanceOutcome(publishedEvent.maintenanceOutcome);
|
|
1270
1270
|
stream.push(publishedEvent);
|
|
1271
1271
|
const standalone = config.standaloneRunOwnership
|
|
1272
1272
|
? standaloneOwnershipStates.get(config.standaloneRunOwnership)
|
|
@@ -3739,6 +3739,16 @@ async function runLoopBody(
|
|
|
3739
3739
|
}
|
|
3740
3740
|
|
|
3741
3741
|
if (outcome !== "not-needed") {
|
|
3742
|
+
if (outcome === "failed") {
|
|
3743
|
+
const errorMessage =
|
|
3744
|
+
maintenance.errorMessage ??
|
|
3745
|
+
"Context maintenance failed before the next model request; the unchanged context was not resubmitted.";
|
|
3746
|
+
const message = managedFailureMessage(new Error(errorMessage), config);
|
|
3747
|
+
stream.push({ type: "message_start", message, scope: attemptScope });
|
|
3748
|
+
stream.push({ type: "message_end", message, scope: attemptScope });
|
|
3749
|
+
currentContext.messages.push(message);
|
|
3750
|
+
newMessages.push(message);
|
|
3751
|
+
}
|
|
3742
3752
|
publishAgentEnd(
|
|
3743
3753
|
stream,
|
|
3744
3754
|
config,
|
|
@@ -4639,6 +4649,7 @@ async function streamAssistantResponse(
|
|
|
4639
4649
|
authCredentialType,
|
|
4640
4650
|
metadata: resolvedMetadata,
|
|
4641
4651
|
sessionId: config.providerSessionId ?? config.sessionId,
|
|
4652
|
+
providerSessionId: config.providerSessionId,
|
|
4642
4653
|
toolChoice: effectiveToolChoice,
|
|
4643
4654
|
reasoning: effectiveReasoning,
|
|
4644
4655
|
temperature: effectiveTemperature,
|
package/src/agent.ts
CHANGED
|
@@ -54,7 +54,7 @@ import type {
|
|
|
54
54
|
StreamFn,
|
|
55
55
|
ToolCallContext,
|
|
56
56
|
} from "./types";
|
|
57
|
-
import { setAgentTerminalOwnerContext } from "./types";
|
|
57
|
+
import { isContinuingMidRunMaintenanceOutcome, setAgentTerminalOwnerContext } from "./types";
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
60
|
* Closed runtime allowlist of failure-classifier codes. The public diagnostic
|
|
@@ -118,7 +118,7 @@ function sanitizeAgentFailure(error: unknown, runtimeClassifiedCode?: string): {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
/** Only runtime-authenticated built-in constructors may contribute a name. */
|
|
121
|
-
const TRUSTED_ERROR_CONSTRUCTORS = new Map<
|
|
121
|
+
const TRUSTED_ERROR_CONSTRUCTORS = new Map<object, string>([
|
|
122
122
|
[Error, "Error"],
|
|
123
123
|
[TypeError, "TypeError"],
|
|
124
124
|
[RangeError, "RangeError"],
|
|
@@ -2147,12 +2147,15 @@ export class Agent {
|
|
|
2147
2147
|
}
|
|
2148
2148
|
this.#state.isStreaming = false;
|
|
2149
2149
|
this.#state.streamMessage = null;
|
|
2150
|
-
// A maintenance checkpoint is only non-terminal while a
|
|
2151
|
-
//
|
|
2150
|
+
// A maintenance checkpoint is only non-terminal while a committed rewrite
|
|
2151
|
+
// will continue. Failed or aborted maintenance yields none. Because the loop runs with
|
|
2152
2152
|
// `resourceSealOwner: "caller"` it deliberately leaves sealing to us, so
|
|
2153
2153
|
// treating it as a checkpoint here would leave the run open forever and make
|
|
2154
2154
|
// every cancel report `run_not_sealed`.
|
|
2155
|
-
if (
|
|
2155
|
+
if (
|
|
2156
|
+
event.stopReason === "maintenance" &&
|
|
2157
|
+
isContinuingMidRunMaintenanceOutcome(event.maintenanceOutcome)
|
|
2158
|
+
) {
|
|
2156
2159
|
this.#managedLogicalRunOwner ??= managedLogicalRunOwner ?? runId;
|
|
2157
2160
|
maintenanceInterrupted = true;
|
|
2158
2161
|
this.#emit(event);
|
|
@@ -91,6 +91,8 @@ export interface GenerateBranchSummaryOptions {
|
|
|
91
91
|
* reuses the live turn's provider/WebSocket session.
|
|
92
92
|
*/
|
|
93
93
|
sessionId?: string;
|
|
94
|
+
/** Opaque provider conversation identity; never derived from branch-summary content. */
|
|
95
|
+
providerSessionId?: string;
|
|
94
96
|
/** Shared provider state map so the branch summary call reuses session-scoped transport/session caches. */
|
|
95
97
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
96
98
|
/** Hint that websocket transport should be preferred when supported by the provider implementation. */
|
|
@@ -291,6 +293,7 @@ export async function generateBranchSummary(
|
|
|
291
293
|
reserveTokens = 16384,
|
|
292
294
|
metadata,
|
|
293
295
|
sessionId,
|
|
296
|
+
providerSessionId,
|
|
294
297
|
providerSessionState,
|
|
295
298
|
preferWebsockets,
|
|
296
299
|
} = options;
|
|
@@ -326,7 +329,16 @@ export async function generateBranchSummary(
|
|
|
326
329
|
const response = await instrumentedCompleteSimple(
|
|
327
330
|
model,
|
|
328
331
|
{ systemPrompt: [SUMMARIZATION_SYSTEM_PROMPT], messages: summarizationMessages },
|
|
329
|
-
{
|
|
332
|
+
{
|
|
333
|
+
apiKey,
|
|
334
|
+
signal,
|
|
335
|
+
maxTokens: 2048,
|
|
336
|
+
metadata,
|
|
337
|
+
sessionId,
|
|
338
|
+
providerSessionId,
|
|
339
|
+
providerSessionState,
|
|
340
|
+
preferWebsockets,
|
|
341
|
+
},
|
|
330
342
|
{ telemetry: options.telemetry, oneshotKind: "branch_summary" },
|
|
331
343
|
);
|
|
332
344
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import * as os from "node:os";
|
|
9
9
|
import {
|
|
10
10
|
type AssistantMessage,
|
|
11
|
+
clampThinkingLevelForModel,
|
|
11
12
|
Effort,
|
|
12
13
|
type Message,
|
|
13
14
|
type MessageAttribution,
|
|
@@ -882,6 +883,8 @@ export interface SummaryOptions {
|
|
|
882
883
|
* `providerSessionId ?? sessionId` the agent loop sends for normal turns).
|
|
883
884
|
*/
|
|
884
885
|
sessionId?: string;
|
|
886
|
+
/** Opaque provider conversation identity; never derived from compaction content. */
|
|
887
|
+
providerSessionId?: string;
|
|
885
888
|
/** Shared provider state map so maintenance calls reuse session-scoped transport/session caches. */
|
|
886
889
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
887
890
|
/** Hint that websocket transport should be preferred when supported by the provider implementation. */
|
|
@@ -1021,10 +1024,11 @@ export async function generateSummary(
|
|
|
1021
1024
|
maxTokens,
|
|
1022
1025
|
signal,
|
|
1023
1026
|
apiKey,
|
|
1024
|
-
reasoning:
|
|
1027
|
+
reasoning: maintenanceReasoning(model),
|
|
1025
1028
|
initiatorOverride: options?.initiatorOverride,
|
|
1026
1029
|
metadata: options?.metadata,
|
|
1027
1030
|
sessionId: options?.sessionId,
|
|
1031
|
+
providerSessionId: options?.providerSessionId,
|
|
1028
1032
|
providerSessionState: options?.providerSessionState,
|
|
1029
1033
|
preferWebsockets: options?.preferWebsockets,
|
|
1030
1034
|
},
|
|
@@ -1073,6 +1077,8 @@ export interface HandoffOptions {
|
|
|
1073
1077
|
* reuses the live turn's provider/WebSocket session.
|
|
1074
1078
|
*/
|
|
1075
1079
|
sessionId?: string;
|
|
1080
|
+
/** Opaque provider conversation identity; never derived from handoff content. */
|
|
1081
|
+
providerSessionId?: string;
|
|
1076
1082
|
/** Shared provider state map so the handoff call reuses session-scoped transport/session caches. */
|
|
1077
1083
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
1078
1084
|
/** Hint that websocket transport should be preferred when supported by the provider implementation. */
|
|
@@ -1115,11 +1121,12 @@ export async function generateHandoff(
|
|
|
1115
1121
|
{
|
|
1116
1122
|
apiKey,
|
|
1117
1123
|
signal,
|
|
1118
|
-
reasoning:
|
|
1124
|
+
reasoning: maintenanceReasoning(model),
|
|
1119
1125
|
toolChoice: "none",
|
|
1120
1126
|
initiatorOverride: options.initiatorOverride,
|
|
1121
1127
|
metadata: options.metadata,
|
|
1122
1128
|
sessionId: options.sessionId,
|
|
1129
|
+
providerSessionId: options.providerSessionId,
|
|
1123
1130
|
providerSessionState: options.providerSessionState,
|
|
1124
1131
|
preferWebsockets: options.preferWebsockets,
|
|
1125
1132
|
},
|
|
@@ -1340,6 +1347,22 @@ export function prepareCompaction(
|
|
|
1340
1347
|
|
|
1341
1348
|
const TURN_PREFIX_SUMMARIZATION_PROMPT = prompt.render(compactionTurnPrefixPrompt);
|
|
1342
1349
|
|
|
1350
|
+
/**
|
|
1351
|
+
* Reasoning effort for a maintenance one-shot call (summary, turn-prefix
|
|
1352
|
+
* summary, handoff), sized against the model that will actually run it.
|
|
1353
|
+
*
|
|
1354
|
+
* These calls want `high`, but they must never *demand* it: the fallback
|
|
1355
|
+
* chain hands them whatever same-provider model has the most context, and a
|
|
1356
|
+
* reasoning-capable model on a transport without reasoning control (the
|
|
1357
|
+
* registry strips `thinking` for a proxied `openai-codex` baseUrl) rejects any
|
|
1358
|
+
* explicit effort inside the provider mapper. The agent turn already clamps
|
|
1359
|
+
* through this helper; the maintenance calls must not be the one path that
|
|
1360
|
+
* still sends a raw effort.
|
|
1361
|
+
*/
|
|
1362
|
+
function maintenanceReasoning(model: Model): Effort | undefined {
|
|
1363
|
+
return clampThinkingLevelForModel(model, Effort.High);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1343
1366
|
/**
|
|
1344
1367
|
* Generate summaries for compaction using prepared data.
|
|
1345
1368
|
* Returns CompactionResult - SessionManager adds id/parentId when saving.
|
|
@@ -1378,6 +1401,7 @@ export async function compact(
|
|
|
1378
1401
|
convertToLlm: options?.convertToLlm,
|
|
1379
1402
|
telemetry: options?.telemetry,
|
|
1380
1403
|
sessionId: options?.sessionId,
|
|
1404
|
+
providerSessionId: options?.providerSessionId,
|
|
1381
1405
|
providerSessionState: options?.providerSessionState,
|
|
1382
1406
|
preferWebsockets: options?.preferWebsockets,
|
|
1383
1407
|
remoteCompactionFallbackHealth: options?.remoteCompactionFallbackHealth,
|
|
@@ -1548,10 +1572,11 @@ async function generateTurnPrefixSummary(
|
|
|
1548
1572
|
maxTokens,
|
|
1549
1573
|
signal,
|
|
1550
1574
|
apiKey,
|
|
1551
|
-
reasoning:
|
|
1575
|
+
reasoning: maintenanceReasoning(model),
|
|
1552
1576
|
initiatorOverride: options?.initiatorOverride,
|
|
1553
1577
|
metadata: options?.metadata,
|
|
1554
1578
|
sessionId: options?.sessionId,
|
|
1579
|
+
providerSessionId: options?.providerSessionId,
|
|
1555
1580
|
providerSessionState: options?.providerSessionState,
|
|
1556
1581
|
preferWebsockets: options?.preferWebsockets,
|
|
1557
1582
|
},
|
package/src/types.ts
CHANGED
|
@@ -214,14 +214,19 @@ export type ManagedAttemptOutcomeHandler = (
|
|
|
214
214
|
* Outcome of a cooperative mid-run context-maintenance checkpoint (see
|
|
215
215
|
* {@link AgentLoopConfig.maintainContext}). Any value other than "not-needed"
|
|
216
216
|
* means the checkpoint mutated (or attempted to mutate) durable context, so the
|
|
217
|
-
* loop ends the current run without
|
|
218
|
-
*
|
|
217
|
+
* loop ends the current run without lossy finalization. Committed maintenance
|
|
218
|
+
* resumes on rewritten context; failed or aborted maintenance terminalizes.
|
|
219
219
|
*/
|
|
220
220
|
export type MidRunMaintenanceOutcome = "not-needed" | "pruned" | "compacted" | "promoted" | "failed" | "aborted";
|
|
221
221
|
|
|
222
222
|
export interface ContextMaintenanceResult {
|
|
223
223
|
outcome: MidRunMaintenanceOutcome;
|
|
224
224
|
releaseCurrentContext?: boolean;
|
|
225
|
+
errorMessage?: string;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function isContinuingMidRunMaintenanceOutcome(outcome: unknown): boolean {
|
|
229
|
+
return outcome === "pruned" || outcome === "compacted" || outcome === "promoted";
|
|
225
230
|
}
|
|
226
231
|
|
|
227
232
|
/**
|
|
@@ -438,9 +443,9 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
438
443
|
* non-optional loop signal, and `awaitEventDrain(invocationSignal)` waits for
|
|
439
444
|
* prior event consumer bodies with loop and invocation cancellation composed.
|
|
440
445
|
* Any outcome other than "not-needed" ends the current run with
|
|
441
|
-
* `agent_end.stopReason === "maintenance"
|
|
442
|
-
*
|
|
443
|
-
*
|
|
446
|
+
* `agent_end.stopReason === "maintenance"`. Successful maintenance resumes
|
|
447
|
+
* on rewritten context; `failed` and `aborted` are terminal and never resend
|
|
448
|
+
* the unchanged context.
|
|
444
449
|
*/
|
|
445
450
|
maintainContext?: (
|
|
446
451
|
context: AgentContext,
|