@gajae-code/agent-core 0.5.3 → 0.5.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 +6 -0
- package/dist/types/agent.d.ts +6 -0
- package/dist/types/compaction/branch-summarization.d.ts +10 -1
- package/dist/types/compaction/compaction.d.ts +20 -1
- package/package.json +4 -4
- package/src/agent.ts +9 -0
- package/src/compaction/branch-summarization.ts +22 -3
- package/src/compaction/compaction.ts +66 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.5.4] - 2026-06-17
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Maintenance one-shot LLM calls now preserve active provider session state and the configured WebSocket transport preference. `SummaryOptions`, `HandoffOptions`, and `GenerateBranchSummaryOptions` accept `sessionId`, `providerSessionState`, and `preferWebsockets`, and `generateSummary`, `generateShortSummary`, `generateTurnPrefixSummary`, `generateHandoff`, `generateBranchSummary`, and `compact()` forward them through to `completeSimple` — previously these fields were dropped, so Codex/OpenAI-compatible compaction summaries, handoff generation, and branch summaries fell back to HTTP/SSE and lost `session_id` affinity even with `providers.openaiWebsockets: "on"`. Split-turn compaction now runs its history and turn-prefix summaries sequentially when they share a single provider WebSocket session, avoiding `websocket request already in progress`; non-WebSocket sessions still run them in parallel. `Agent` exposes a `preferWebsockets` getter so callers can forward the live transport preference (#736).
|
|
10
|
+
|
|
5
11
|
## [0.5.3] - 2026-06-16
|
|
6
12
|
|
|
7
13
|
### Fixed
|
package/dist/types/agent.d.ts
CHANGED
|
@@ -193,6 +193,12 @@ export declare class Agent {
|
|
|
193
193
|
set sessionId(value: string | undefined);
|
|
194
194
|
get providerSessionId(): string | undefined;
|
|
195
195
|
set providerSessionId(value: string | undefined);
|
|
196
|
+
/**
|
|
197
|
+
* Whether websocket transport is preferred when the provider implementation
|
|
198
|
+
* supports it. Read by maintenance one-shot calls (compaction, handoff,
|
|
199
|
+
* branch summary) so they forward the same transport preference as live turns.
|
|
200
|
+
*/
|
|
201
|
+
get preferWebsockets(): boolean | undefined;
|
|
196
202
|
/**
|
|
197
203
|
* Static metadata forwarded to every API request when no resolver is installed
|
|
198
204
|
* (e.g. `metadata.user_id` for Anthropic session attribution). Setting this
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* When navigating to a different point in the session tree, this generates
|
|
5
5
|
* a summary of the branch being left so context isn't lost.
|
|
6
6
|
*/
|
|
7
|
-
import type { Model } from "@gajae-code/ai";
|
|
7
|
+
import type { Model, ProviderSessionState } from "@gajae-code/ai";
|
|
8
8
|
import { type AgentTelemetry } from "../telemetry";
|
|
9
9
|
import type { AgentMessage } from "../types";
|
|
10
10
|
import type { ReadonlySessionManager, SessionEntry } from "./entries";
|
|
@@ -57,6 +57,15 @@ export interface GenerateBranchSummaryOptions {
|
|
|
57
57
|
* wrapped in an OTEL chat span tagged with `pi.gen_ai.oneshot.kind = "branch_summary"`.
|
|
58
58
|
*/
|
|
59
59
|
telemetry?: AgentTelemetry;
|
|
60
|
+
/**
|
|
61
|
+
* Provider session affinity id forwarded to the branch summary LLM call so it
|
|
62
|
+
* reuses the live turn's provider/WebSocket session.
|
|
63
|
+
*/
|
|
64
|
+
sessionId?: string;
|
|
65
|
+
/** Shared provider state map so the branch summary call reuses session-scoped transport/session caches. */
|
|
66
|
+
providerSessionState?: Map<string, ProviderSessionState>;
|
|
67
|
+
/** Hint that websocket transport should be preferred when supported by the provider implementation. */
|
|
68
|
+
preferWebsockets?: boolean;
|
|
60
69
|
}
|
|
61
70
|
/**
|
|
62
71
|
* Collect entries that should be summarized when navigating from one position to another.
|
|
@@ -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 MessageAttribution, type Model, type Usage } from "@gajae-code/ai";
|
|
7
|
+
import { type MessageAttribution, type Model, type ProviderSessionState, type Usage } from "@gajae-code/ai";
|
|
8
8
|
import { type AgentTelemetry } from "../telemetry";
|
|
9
9
|
import type { AgentMessage, AgentTool } from "../types";
|
|
10
10
|
import type { SessionEntry } from "./entries";
|
|
@@ -182,6 +182,16 @@ export interface SummaryOptions {
|
|
|
182
182
|
*/
|
|
183
183
|
telemetry?: AgentTelemetry;
|
|
184
184
|
authCredentialType?: "api_key" | "oauth";
|
|
185
|
+
/**
|
|
186
|
+
* Provider session affinity id forwarded to the maintenance LLM call so it
|
|
187
|
+
* reuses the live turn's provider/WebSocket session (matches the
|
|
188
|
+
* `providerSessionId ?? sessionId` the agent loop sends for normal turns).
|
|
189
|
+
*/
|
|
190
|
+
sessionId?: string;
|
|
191
|
+
/** Shared provider state map so maintenance calls reuse session-scoped transport/session caches. */
|
|
192
|
+
providerSessionState?: Map<string, ProviderSessionState>;
|
|
193
|
+
/** Hint that websocket transport should be preferred when supported by the provider implementation. */
|
|
194
|
+
preferWebsockets?: boolean;
|
|
185
195
|
}
|
|
186
196
|
export declare function generateSummary(currentMessages: AgentMessage[], model: Model, reserveTokens: number, apiKey: string, signal?: AbortSignal, customInstructions?: string, previousSummary?: string, options?: SummaryOptions): Promise<string>;
|
|
187
197
|
export interface HandoffOptions {
|
|
@@ -199,6 +209,15 @@ export interface HandoffOptions {
|
|
|
199
209
|
*/
|
|
200
210
|
telemetry?: AgentTelemetry;
|
|
201
211
|
authCredentialType?: "api_key" | "oauth";
|
|
212
|
+
/**
|
|
213
|
+
* Provider session affinity id forwarded to the handoff LLM call so it
|
|
214
|
+
* reuses the live turn's provider/WebSocket session.
|
|
215
|
+
*/
|
|
216
|
+
sessionId?: string;
|
|
217
|
+
/** Shared provider state map so the handoff call reuses session-scoped transport/session caches. */
|
|
218
|
+
providerSessionState?: Map<string, ProviderSessionState>;
|
|
219
|
+
/** Hint that websocket transport should be preferred when supported by the provider implementation. */
|
|
220
|
+
preferWebsockets?: boolean;
|
|
202
221
|
}
|
|
203
222
|
export declare function renderHandoffPrompt(customInstructions?: string): string;
|
|
204
223
|
export declare function generateHandoff(messages: AgentMessage[], model: Model, apiKey: string, options: HandoffOptions, signal?: AbortSignal): Promise<string>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/agent-core",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.4",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://gaebal-gajae.dev",
|
|
7
7
|
"author": "Yeachan-Heo",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"fmt": "biome format --write ."
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@gajae-code/ai": "0.5.
|
|
39
|
-
"@gajae-code/natives": "0.5.
|
|
40
|
-
"@gajae-code/utils": "0.5.
|
|
38
|
+
"@gajae-code/ai": "0.5.4",
|
|
39
|
+
"@gajae-code/natives": "0.5.4",
|
|
40
|
+
"@gajae-code/utils": "0.5.4",
|
|
41
41
|
"@opentelemetry/api": "^1.9.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
package/src/agent.ts
CHANGED
|
@@ -408,6 +408,15 @@ export class Agent {
|
|
|
408
408
|
this.#providerSessionId = value;
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
+
/**
|
|
412
|
+
* Whether websocket transport is preferred when the provider implementation
|
|
413
|
+
* supports it. Read by maintenance one-shot calls (compaction, handoff,
|
|
414
|
+
* branch summary) so they forward the same transport preference as live turns.
|
|
415
|
+
*/
|
|
416
|
+
get preferWebsockets(): boolean | undefined {
|
|
417
|
+
return this.#preferWebsockets;
|
|
418
|
+
}
|
|
419
|
+
|
|
411
420
|
/**
|
|
412
421
|
* Static metadata forwarded to every API request when no resolver is installed
|
|
413
422
|
* (e.g. `metadata.user_id` for Anthropic session attribution). Setting this
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* a summary of the branch being left so context isn't lost.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { Model } from "@gajae-code/ai";
|
|
8
|
+
import type { Model, ProviderSessionState } from "@gajae-code/ai";
|
|
9
9
|
import { prompt } from "@gajae-code/utils";
|
|
10
10
|
import { type AgentTelemetry, instrumentedCompleteSimple } from "../telemetry";
|
|
11
11
|
import type { AgentMessage } from "../types";
|
|
@@ -86,6 +86,15 @@ export interface GenerateBranchSummaryOptions {
|
|
|
86
86
|
* wrapped in an OTEL chat span tagged with `pi.gen_ai.oneshot.kind = "branch_summary"`.
|
|
87
87
|
*/
|
|
88
88
|
telemetry?: AgentTelemetry;
|
|
89
|
+
/**
|
|
90
|
+
* Provider session affinity id forwarded to the branch summary LLM call so it
|
|
91
|
+
* reuses the live turn's provider/WebSocket session.
|
|
92
|
+
*/
|
|
93
|
+
sessionId?: string;
|
|
94
|
+
/** Shared provider state map so the branch summary call reuses session-scoped transport/session caches. */
|
|
95
|
+
providerSessionState?: Map<string, ProviderSessionState>;
|
|
96
|
+
/** Hint that websocket transport should be preferred when supported by the provider implementation. */
|
|
97
|
+
preferWebsockets?: boolean;
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
// ============================================================================
|
|
@@ -274,7 +283,17 @@ export async function generateBranchSummary(
|
|
|
274
283
|
entries: SessionEntry[],
|
|
275
284
|
options: GenerateBranchSummaryOptions,
|
|
276
285
|
): Promise<BranchSummaryResult> {
|
|
277
|
-
const {
|
|
286
|
+
const {
|
|
287
|
+
model,
|
|
288
|
+
apiKey,
|
|
289
|
+
signal,
|
|
290
|
+
customInstructions,
|
|
291
|
+
reserveTokens = 16384,
|
|
292
|
+
metadata,
|
|
293
|
+
sessionId,
|
|
294
|
+
providerSessionState,
|
|
295
|
+
preferWebsockets,
|
|
296
|
+
} = options;
|
|
278
297
|
|
|
279
298
|
// Token budget = context window minus reserved space for prompt + response
|
|
280
299
|
const contextWindow = model.contextWindow || 128000;
|
|
@@ -307,7 +326,7 @@ export async function generateBranchSummary(
|
|
|
307
326
|
const response = await instrumentedCompleteSimple(
|
|
308
327
|
model,
|
|
309
328
|
{ systemPrompt: [SUMMARIZATION_SYSTEM_PROMPT], messages: summarizationMessages },
|
|
310
|
-
{ apiKey, signal, maxTokens: 2048, metadata },
|
|
329
|
+
{ apiKey, signal, maxTokens: 2048, metadata, sessionId, providerSessionState, preferWebsockets },
|
|
311
330
|
{ telemetry: options.telemetry, oneshotKind: "branch_summary" },
|
|
312
331
|
);
|
|
313
332
|
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type Message,
|
|
13
13
|
type MessageAttribution,
|
|
14
14
|
type Model,
|
|
15
|
+
type ProviderSessionState,
|
|
15
16
|
type Usage,
|
|
16
17
|
} from "@gajae-code/ai";
|
|
17
18
|
import { isCompiledBinary, logger, prompt } from "@gajae-code/utils";
|
|
@@ -735,6 +736,16 @@ export interface SummaryOptions {
|
|
|
735
736
|
*/
|
|
736
737
|
telemetry?: AgentTelemetry;
|
|
737
738
|
authCredentialType?: "api_key" | "oauth";
|
|
739
|
+
/**
|
|
740
|
+
* Provider session affinity id forwarded to the maintenance LLM call so it
|
|
741
|
+
* reuses the live turn's provider/WebSocket session (matches the
|
|
742
|
+
* `providerSessionId ?? sessionId` the agent loop sends for normal turns).
|
|
743
|
+
*/
|
|
744
|
+
sessionId?: string;
|
|
745
|
+
/** Shared provider state map so maintenance calls reuse session-scoped transport/session caches. */
|
|
746
|
+
providerSessionState?: Map<string, ProviderSessionState>;
|
|
747
|
+
/** Hint that websocket transport should be preferred when supported by the provider implementation. */
|
|
748
|
+
preferWebsockets?: boolean;
|
|
738
749
|
}
|
|
739
750
|
|
|
740
751
|
export async function generateSummary(
|
|
@@ -801,6 +812,9 @@ export async function generateSummary(
|
|
|
801
812
|
reasoning: Effort.High,
|
|
802
813
|
initiatorOverride: options?.initiatorOverride,
|
|
803
814
|
metadata: options?.metadata,
|
|
815
|
+
sessionId: options?.sessionId,
|
|
816
|
+
providerSessionState: options?.providerSessionState,
|
|
817
|
+
preferWebsockets: options?.preferWebsockets,
|
|
804
818
|
},
|
|
805
819
|
{ telemetry: options?.telemetry, oneshotKind: "compaction_summary" },
|
|
806
820
|
);
|
|
@@ -836,6 +850,15 @@ export interface HandoffOptions {
|
|
|
836
850
|
*/
|
|
837
851
|
telemetry?: AgentTelemetry;
|
|
838
852
|
authCredentialType?: "api_key" | "oauth";
|
|
853
|
+
/**
|
|
854
|
+
* Provider session affinity id forwarded to the handoff LLM call so it
|
|
855
|
+
* reuses the live turn's provider/WebSocket session.
|
|
856
|
+
*/
|
|
857
|
+
sessionId?: string;
|
|
858
|
+
/** Shared provider state map so the handoff call reuses session-scoped transport/session caches. */
|
|
859
|
+
providerSessionState?: Map<string, ProviderSessionState>;
|
|
860
|
+
/** Hint that websocket transport should be preferred when supported by the provider implementation. */
|
|
861
|
+
preferWebsockets?: boolean;
|
|
839
862
|
}
|
|
840
863
|
|
|
841
864
|
export function renderHandoffPrompt(customInstructions?: string): string {
|
|
@@ -877,6 +900,9 @@ export async function generateHandoff(
|
|
|
877
900
|
toolChoice: "none",
|
|
878
901
|
initiatorOverride: options.initiatorOverride,
|
|
879
902
|
metadata: options.metadata,
|
|
903
|
+
sessionId: options.sessionId,
|
|
904
|
+
providerSessionState: options.providerSessionState,
|
|
905
|
+
preferWebsockets: options.preferWebsockets,
|
|
880
906
|
},
|
|
881
907
|
{ telemetry: options.telemetry, oneshotKind: "handoff" },
|
|
882
908
|
);
|
|
@@ -936,6 +962,9 @@ async function generateShortSummary(
|
|
|
936
962
|
reasoning: Effort.High,
|
|
937
963
|
initiatorOverride: options?.initiatorOverride,
|
|
938
964
|
metadata: options?.metadata,
|
|
965
|
+
sessionId: options?.sessionId,
|
|
966
|
+
providerSessionState: options?.providerSessionState,
|
|
967
|
+
preferWebsockets: options?.preferWebsockets,
|
|
939
968
|
},
|
|
940
969
|
{ telemetry: options?.telemetry, oneshotKind: "compaction_short_summary" },
|
|
941
970
|
);
|
|
@@ -1120,6 +1149,9 @@ export async function compact(
|
|
|
1120
1149
|
metadata: options?.metadata,
|
|
1121
1150
|
convertToLlm: options?.convertToLlm,
|
|
1122
1151
|
telemetry: options?.telemetry,
|
|
1152
|
+
sessionId: options?.sessionId,
|
|
1153
|
+
providerSessionState: options?.providerSessionState,
|
|
1154
|
+
preferWebsockets: options?.preferWebsockets,
|
|
1123
1155
|
};
|
|
1124
1156
|
|
|
1125
1157
|
let preserveData = withOpenAiRemoteCompactionPreserveData(previousPreserveData, undefined);
|
|
@@ -1159,9 +1191,22 @@ export async function compact(
|
|
|
1159
1191
|
// Generate summaries (can be parallel if both needed) and merge into one
|
|
1160
1192
|
let summary: string;
|
|
1161
1193
|
|
|
1194
|
+
// A single active Codex WebSocket session cannot service two concurrent
|
|
1195
|
+
// requests ("websocket request already in progress"). When the maintenance
|
|
1196
|
+
// calls use the Codex Responses provider, share one provider session, and
|
|
1197
|
+
// websocket transport is not explicitly disabled, run the split-turn history
|
|
1198
|
+
// and turn-prefix summaries sequentially. This covers websocket activation
|
|
1199
|
+
// from config/env/model defaults too: the provider can select websockets even
|
|
1200
|
+
// when `preferWebsockets` is undefined, while non-Codex providers keep the
|
|
1201
|
+
// previous parallel behavior.
|
|
1202
|
+
const summariesMayShareWebSocketSession = Boolean(
|
|
1203
|
+
model.api === "openai-codex-responses" &&
|
|
1204
|
+
summaryOptions.providerSessionState &&
|
|
1205
|
+
summaryOptions.preferWebsockets !== false,
|
|
1206
|
+
);
|
|
1207
|
+
|
|
1162
1208
|
if (isSplitTurn && turnPrefixMessages.length > 0) {
|
|
1163
|
-
|
|
1164
|
-
const [historyResult, turnPrefixResult] = await Promise.all([
|
|
1209
|
+
const runHistorySummary = () =>
|
|
1165
1210
|
messagesToSummarize.length > 0
|
|
1166
1211
|
? generateSummary(
|
|
1167
1212
|
messagesToSummarize,
|
|
@@ -1173,9 +1218,19 @@ export async function compact(
|
|
|
1173
1218
|
previousSummary,
|
|
1174
1219
|
summaryOptions,
|
|
1175
1220
|
)
|
|
1176
|
-
: Promise.resolve("No prior history.")
|
|
1177
|
-
|
|
1178
|
-
|
|
1221
|
+
: Promise.resolve("No prior history.");
|
|
1222
|
+
const runTurnPrefixSummary = () =>
|
|
1223
|
+
generateTurnPrefixSummary(turnPrefixMessages, model, settings.reserveTokens, apiKey, signal, summaryOptions);
|
|
1224
|
+
|
|
1225
|
+
let historyResult: string;
|
|
1226
|
+
let turnPrefixResult: string;
|
|
1227
|
+
if (summariesMayShareWebSocketSession) {
|
|
1228
|
+
// Sequential: avoids concurrent requests on the same provider session.
|
|
1229
|
+
historyResult = await runHistorySummary();
|
|
1230
|
+
turnPrefixResult = await runTurnPrefixSummary();
|
|
1231
|
+
} else {
|
|
1232
|
+
[historyResult, turnPrefixResult] = await Promise.all([runHistorySummary(), runTurnPrefixSummary()]);
|
|
1233
|
+
}
|
|
1179
1234
|
// Merge into single summary
|
|
1180
1235
|
summary = `${historyResult}\n\n---\n\n**Turn Context (split turn):**\n\n${turnPrefixResult}`;
|
|
1181
1236
|
} else if (messagesToSummarize.length > 0) {
|
|
@@ -1211,6 +1266,9 @@ export async function compact(
|
|
|
1211
1266
|
initiatorOverride: summaryOptions.initiatorOverride,
|
|
1212
1267
|
metadata: summaryOptions.metadata,
|
|
1213
1268
|
telemetry: summaryOptions.telemetry,
|
|
1269
|
+
sessionId: summaryOptions.sessionId,
|
|
1270
|
+
providerSessionState: summaryOptions.providerSessionState,
|
|
1271
|
+
preferWebsockets: summaryOptions.preferWebsockets,
|
|
1214
1272
|
},
|
|
1215
1273
|
);
|
|
1216
1274
|
|
|
@@ -1266,6 +1324,9 @@ async function generateTurnPrefixSummary(
|
|
|
1266
1324
|
reasoning: Effort.High,
|
|
1267
1325
|
initiatorOverride: options?.initiatorOverride,
|
|
1268
1326
|
metadata: options?.metadata,
|
|
1327
|
+
sessionId: options?.sessionId,
|
|
1328
|
+
providerSessionState: options?.providerSessionState,
|
|
1329
|
+
preferWebsockets: options?.preferWebsockets,
|
|
1269
1330
|
},
|
|
1270
1331
|
{ telemetry: options?.telemetry, oneshotKind: "compaction_turn_prefix" },
|
|
1271
1332
|
);
|