@gajae-code/agent-core 0.17.1 → 0.17.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 +10 -0
- package/dist/types/types.d.ts +2 -0
- package/package.json +4 -4
- package/src/agent-loop.ts +24 -9
- package/src/append-only-context.ts +9 -8
- package/src/compaction/compaction.ts +1 -2
- package/src/telemetry.ts +2 -1
- package/src/types.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.17.4] - 2026-09-23
|
|
6
|
+
|
|
7
|
+
## [0.17.3] - 2026-09-22
|
|
8
|
+
|
|
9
|
+
### Performance
|
|
10
|
+
|
|
11
|
+
- Bound stream abort-race retention to the current read and reuse history hashes when checking append-only context rewrites.
|
|
12
|
+
|
|
13
|
+
## [0.17.2] - 2026-09-18
|
|
14
|
+
|
|
5
15
|
## [0.17.1] - 2026-09-17
|
|
6
16
|
|
|
7
17
|
## [0.17.0] - 2026-09-17
|
package/dist/types/types.d.ts
CHANGED
|
@@ -234,6 +234,8 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
234
234
|
* next tool/turn boundary either way.
|
|
235
235
|
*/
|
|
236
236
|
toolInterruptPolicy?: "abort_tools" | "finish_tools";
|
|
237
|
+
/** Test-only diagnostic for bounding pending per-read abort-race reactions. */
|
|
238
|
+
onAbortRaceReactionChange?: (delta: 1 | -1) => void;
|
|
237
239
|
/**
|
|
238
240
|
* Optional session identifier forwarded to LLM providers.
|
|
239
241
|
* Used by providers that support session-based caching (e.g., OpenAI code provider).
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/agent-core",
|
|
4
|
-
"version": "0.17.
|
|
4
|
+
"version": "0.17.4",
|
|
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.17.
|
|
36
|
-
"@gajae-code/natives": "0.17.
|
|
37
|
-
"@gajae-code/utils": "0.17.
|
|
35
|
+
"@gajae-code/ai": "0.17.4",
|
|
36
|
+
"@gajae-code/natives": "0.17.4",
|
|
37
|
+
"@gajae-code/utils": "0.17.4",
|
|
38
38
|
"@opentelemetry/api": "^1.9.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
package/src/agent-loop.ts
CHANGED
|
@@ -2030,6 +2030,8 @@ function losslessDetachedClone<T>(value: T): T {
|
|
|
2030
2030
|
"kind",
|
|
2031
2031
|
"status",
|
|
2032
2032
|
"code",
|
|
2033
|
+
"http2RstCode",
|
|
2034
|
+
"nativeErrorCode",
|
|
2033
2035
|
"providerCode",
|
|
2034
2036
|
"openaiErrorCode",
|
|
2035
2037
|
"anthropicErrorType",
|
|
@@ -4808,10 +4810,9 @@ async function streamAssistantResponse(
|
|
|
4808
4810
|
return getResponseResult();
|
|
4809
4811
|
};
|
|
4810
4812
|
|
|
4811
|
-
//
|
|
4812
|
-
//
|
|
4813
|
-
|
|
4814
|
-
let abortRacePromise: Promise<typeof ABORTED> | undefined;
|
|
4813
|
+
// Keep one listener, but race a fresh promise per read so pending abort
|
|
4814
|
+
// reactions do not retain every event until the request ends.
|
|
4815
|
+
let settleReadAbort: (() => void) | undefined;
|
|
4815
4816
|
let detachAbortListener: (() => void) | undefined;
|
|
4816
4817
|
if (requestSignal) {
|
|
4817
4818
|
if (requestSignal.aborted) {
|
|
@@ -4827,18 +4828,32 @@ async function streamAssistantResponse(
|
|
|
4827
4828
|
await finishChat(aborted);
|
|
4828
4829
|
return aborted;
|
|
4829
4830
|
}
|
|
4830
|
-
const
|
|
4831
|
-
const onAbort = () => resolve(ABORTED);
|
|
4831
|
+
const onAbort = () => settleReadAbort?.();
|
|
4832
4832
|
requestSignal.addEventListener("abort", onAbort, { once: true });
|
|
4833
|
-
abortRacePromise = promise;
|
|
4834
4833
|
detachAbortListener = () => requestSignal.removeEventListener("abort", onAbort);
|
|
4835
4834
|
}
|
|
4836
4835
|
|
|
4837
4836
|
try {
|
|
4838
4837
|
while (true) {
|
|
4839
4838
|
let next: IteratorResult<AssistantMessageEvent>;
|
|
4840
|
-
if (
|
|
4841
|
-
const
|
|
4839
|
+
if (requestSignal) {
|
|
4840
|
+
const { promise, resolve } = Promise.withResolvers<typeof ABORTED>();
|
|
4841
|
+
let settled = false;
|
|
4842
|
+
const settleAbort = (): void => {
|
|
4843
|
+
if (settled) return;
|
|
4844
|
+
settled = true;
|
|
4845
|
+
resolve(ABORTED);
|
|
4846
|
+
config.onAbortRaceReactionChange?.(-1);
|
|
4847
|
+
};
|
|
4848
|
+
config.onAbortRaceReactionChange?.(1);
|
|
4849
|
+
settleReadAbort = settleAbort;
|
|
4850
|
+
let result: IteratorResult<AssistantMessageEvent> | typeof ABORTED;
|
|
4851
|
+
try {
|
|
4852
|
+
result = requestSignal.aborted ? ABORTED : await Promise.race([responseIterator.next(), promise]);
|
|
4853
|
+
} finally {
|
|
4854
|
+
settleAbort();
|
|
4855
|
+
settleReadAbort = undefined;
|
|
4856
|
+
}
|
|
4842
4857
|
if (result === ABORTED) {
|
|
4843
4858
|
closeIterator();
|
|
4844
4859
|
const aborted = emitAbortedAssistantMessage(
|
|
@@ -272,18 +272,19 @@ export class AppendOnlyContextManager {
|
|
|
272
272
|
seededPrefixLength > 0 && !includesSeedPrefix
|
|
273
273
|
? [...this.log.entries().slice(0, seededPrefixLength), ...normalizedMessages]
|
|
274
274
|
: normalizedMessages;
|
|
275
|
+
const hashes = this.#hashRange(messagesToSync, 0, messagesToSync.length);
|
|
275
276
|
|
|
276
277
|
// Detect in-place rewrites of already-synced messages via per-message content
|
|
277
278
|
// hashes (no retained full serialized-history string; F5).
|
|
278
279
|
if (
|
|
279
280
|
this.#lastSyncCount > 0 &&
|
|
280
281
|
this.#lastSyncCount <= messagesToSync.length &&
|
|
281
|
-
this.#prefixChanged(
|
|
282
|
+
this.#prefixChanged(hashes, this.#lastSyncCount)
|
|
282
283
|
) {
|
|
283
284
|
if (this.#seededPrefixCount > 0) {
|
|
284
285
|
// F9: a seeded fork whose inherited prefix changed (e.g. after compaction)
|
|
285
286
|
// rebases onto the new provider context instead of throwing.
|
|
286
|
-
this.#rebaseToBaseline(messagesToSync, seededPrefixLength);
|
|
287
|
+
this.#rebaseToBaseline(messagesToSync, hashes, seededPrefixLength);
|
|
287
288
|
return;
|
|
288
289
|
}
|
|
289
290
|
this.log.clear();
|
|
@@ -296,7 +297,7 @@ export class AppendOnlyContextManager {
|
|
|
296
297
|
// while a seed prefix is active; a genuine seeded compaction rebases (F9).
|
|
297
298
|
if (messagesToSync.length < this.#lastSyncCount) {
|
|
298
299
|
if (this.#seededPrefixCount > 0) {
|
|
299
|
-
this.#rebaseToBaseline(messagesToSync, seededPrefixLength);
|
|
300
|
+
this.#rebaseToBaseline(messagesToSync, hashes, seededPrefixLength);
|
|
300
301
|
return;
|
|
301
302
|
}
|
|
302
303
|
this.log.clear();
|
|
@@ -310,7 +311,7 @@ export class AppendOnlyContextManager {
|
|
|
310
311
|
}
|
|
311
312
|
|
|
312
313
|
this.#lastSyncCount = messagesToSync.length;
|
|
313
|
-
this.#syncedHashes =
|
|
314
|
+
this.#syncedHashes = hashes;
|
|
314
315
|
}
|
|
315
316
|
|
|
316
317
|
seedNormalizedMessages(messages: readonly Message[], options?: { reset?: boolean }): void {
|
|
@@ -393,21 +394,21 @@ export class AppendOnlyContextManager {
|
|
|
393
394
|
}
|
|
394
395
|
|
|
395
396
|
/** True when any of the first `count` already-synced messages changed content (in-place rewrite). */
|
|
396
|
-
#prefixChanged(
|
|
397
|
+
#prefixChanged(hashes: readonly (number | bigint)[], count: number): boolean {
|
|
397
398
|
if (count > this.#syncedHashes.length) return false;
|
|
398
399
|
for (let i = 0; i < count; i++) {
|
|
399
|
-
if (
|
|
400
|
+
if (hashes[i] !== this.#syncedHashes[i]) return true;
|
|
400
401
|
}
|
|
401
402
|
return false;
|
|
402
403
|
}
|
|
403
404
|
|
|
404
405
|
/** F9: reset the log to a new provider-visible baseline after seeded compaction/rebase. */
|
|
405
|
-
#rebaseToBaseline(messages: readonly unknown[],
|
|
406
|
+
#rebaseToBaseline(messages: readonly unknown[], hashes: (number | bigint)[], seededPrefixCount: number): void {
|
|
406
407
|
this.log.clear();
|
|
407
408
|
this.log.extend(messages.map(message => cloneJson(message)));
|
|
408
409
|
this.#lastSyncCount = messages.length;
|
|
409
410
|
this.#seededPrefixCount = seededPrefixCount;
|
|
410
|
-
this.#syncedHashes =
|
|
411
|
+
this.#syncedHashes = hashes;
|
|
411
412
|
}
|
|
412
413
|
}
|
|
413
414
|
|
|
@@ -1365,8 +1365,7 @@ const TURN_PREFIX_SUMMARIZATION_PROMPT = prompt.render(compactionTurnPrefixPromp
|
|
|
1365
1365
|
* Reasoning effort for a maintenance one-shot call (summary, turn-prefix
|
|
1366
1366
|
* summary, handoff), sized against the model that will actually run it.
|
|
1367
1367
|
*
|
|
1368
|
-
* These calls want `high`, but they must never *demand* it:
|
|
1369
|
-
* chain hands them whatever same-provider model has the most context, and a
|
|
1368
|
+
* These calls want `high`, but they must never *demand* it: a
|
|
1370
1369
|
* reasoning-capable model on a transport without reasoning control (the
|
|
1371
1370
|
* registry strips `thinking` for a proxied `openai-codex` baseUrl) rejects any
|
|
1372
1371
|
* explicit effort inside the provider mapper. The agent turn already clamps
|
package/src/telemetry.ts
CHANGED
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
type ToolChoice,
|
|
38
38
|
type Usage,
|
|
39
39
|
} from "@gajae-code/ai";
|
|
40
|
+
import { isDesignedError } from "@gajae-code/utils/error-classification";
|
|
40
41
|
import { recordHandledError } from "@gajae-code/utils/postmortem";
|
|
41
42
|
import {
|
|
42
43
|
type Attributes,
|
|
@@ -1878,7 +1879,7 @@ export function finishExecuteToolSpan(
|
|
|
1878
1879
|
? options.errorObject.name || "Error"
|
|
1879
1880
|
: STATUS_ERROR_TYPE[status];
|
|
1880
1881
|
}
|
|
1881
|
-
if (status === "error" && options.errorObject instanceof Error) {
|
|
1882
|
+
if (status === "error" && options.errorObject instanceof Error && !isDesignedError(options.errorObject)) {
|
|
1882
1883
|
try {
|
|
1883
1884
|
recordHandledError(`Tool ${options.toolName}`, options.errorObject);
|
|
1884
1885
|
} catch {}
|
package/src/types.ts
CHANGED
|
@@ -260,6 +260,8 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
260
260
|
* next tool/turn boundary either way.
|
|
261
261
|
*/
|
|
262
262
|
toolInterruptPolicy?: "abort_tools" | "finish_tools";
|
|
263
|
+
/** Test-only diagnostic for bounding pending per-read abort-race reactions. */
|
|
264
|
+
onAbortRaceReactionChange?: (delta: 1 | -1) => void;
|
|
263
265
|
|
|
264
266
|
/**
|
|
265
267
|
* Optional session identifier forwarded to LLM providers.
|