@gajae-code/agent-core 0.10.0 → 0.10.2

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 CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.10.2] - 2026-07-14
6
+
7
+ ### Fixed
8
+
9
+ - Extended the gpt-5.6 `Request blocked (code=invalid_prompt)` fix to the compaction paths that bypass the streaming transport. Remote OpenAI compaction (`/responses/compact`, `compaction.remoteEnabled` default on — the "remote compact task" in openai/codex#32028) built its native `input` from reasoning signatures, verbatim history items, and message/tool text without neutralizing leaked Harmony control-token markers (e.g. `<|channel|>analysis`), so gpt-5.6 rejected the compaction request and, on retry, could escalate to account-level blocking. `requestOpenAiRemoteCompaction` now neutralizes reserved control tokens across the whole outgoing `input`, and the generic `requestRemoteCompaction` prompt/systemPrompt are neutralized too. Local summarization was already covered by the streaming-transport request-boundary fix.
10
+
5
11
  ## [0.10.0] - 2026-07-12
6
12
 
7
13
  ### Fixed
@@ -298,6 +298,7 @@ export declare class Agent {
298
298
  get streamMaxRetries(): number | undefined;
299
299
  set streamMaxRetries(value: number | undefined);
300
300
  get state(): AgentState;
301
+ get contextRevision(): number;
301
302
  get appendOnlyContext(): AppendOnlyContextManager | undefined;
302
303
  setAppendOnlyContext(manager?: AppendOnlyContextManager): void;
303
304
  subscribe(fn: (e: AgentEvent) => void): () => void;
@@ -309,7 +310,7 @@ export declare class Agent {
309
310
  emitExternalEvent(event: AgentEvent): void;
310
311
  createExternalEventEmitterForCurrentRun(): ((event: AgentEvent) => void) | undefined;
311
312
  setSystemPrompt(v: string[]): void;
312
- setModel(m: Model): void;
313
+ setModel(m: Model | undefined): void;
313
314
  setThinkingLevel(l: Effort | undefined): void;
314
315
  setSteeringMode(mode: "all" | "one-at-a-time"): void;
315
316
  getSteeringMode(): "all" | "one-at-a-time";
@@ -321,6 +322,11 @@ export declare class Agent {
321
322
  replaceMessages(ms: AgentMessage[]): void;
322
323
  appendMessage(m: AgentMessage): void;
323
324
  popMessage(): AgentMessage | undefined;
325
+ /**
326
+ * For callers that mutate committed messages or the system prompt in place
327
+ * outside Agent-owned mutators.
328
+ */
329
+ touchContext(): void;
324
330
  /**
325
331
  * Queue a steering message to interrupt the agent mid-run.
326
332
  * Delivered after current tool execution, skips remaining tools.
@@ -308,7 +308,7 @@ export type AgentMessage = Message | CustomAgentMessages[keyof CustomAgentMessag
308
308
  */
309
309
  export interface AgentState {
310
310
  systemPrompt: string[];
311
- model: Model;
311
+ model: Model | undefined;
312
312
  thinkingLevel?: Effort;
313
313
  tools: AgentTool<any>[];
314
314
  messages: AgentMessage[];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/agent-core",
4
- "version": "0.10.0",
4
+ "version": "0.10.2",
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.10.0",
36
- "@gajae-code/natives": "0.10.0",
37
- "@gajae-code/utils": "0.10.0",
35
+ "@gajae-code/ai": "0.10.2",
36
+ "@gajae-code/natives": "0.10.2",
37
+ "@gajae-code/utils": "0.10.2",
38
38
  "@opentelemetry/api": "^1.9.0"
39
39
  },
40
40
  "devDependencies": {
package/src/agent.ts CHANGED
@@ -296,6 +296,7 @@ export class Agent {
296
296
  pendingToolCalls: new Set<string>(),
297
297
  error: undefined,
298
298
  };
299
+ #contextRevision = 0;
299
300
 
300
301
  #listeners = new Set<(e: AgentEvent) => void>();
301
302
  #abortController?: AbortController;
@@ -640,6 +641,10 @@ export class Agent {
640
641
  return this.#state;
641
642
  }
642
643
 
644
+ get contextRevision(): number {
645
+ return this.#contextRevision;
646
+ }
647
+
643
648
  get appendOnlyContext(): AppendOnlyContextManager | undefined {
644
649
  return this.#appendOnlyContext;
645
650
  }
@@ -822,10 +827,12 @@ export class Agent {
822
827
  // State mutators
823
828
  setSystemPrompt(v: string[]) {
824
829
  this.#state.systemPrompt = v;
830
+ this.#contextRevision++;
825
831
  }
826
832
 
827
- setModel(m: Model) {
833
+ setModel(m: Model | undefined) {
828
834
  this.#state.model = m;
835
+ this.#contextRevision++;
829
836
  }
830
837
 
831
838
  setThinkingLevel(l: Effort | undefined) {
@@ -858,10 +865,12 @@ export class Agent {
858
865
 
859
866
  setTools(t: AgentTool<any>[]) {
860
867
  this.#state.tools = t;
868
+ this.#contextRevision++;
861
869
  }
862
870
 
863
871
  replaceMessages(ms: AgentMessage[]) {
864
872
  this.#state.messages = ms.slice();
873
+ this.#contextRevision++;
865
874
  }
866
875
 
867
876
  appendMessage(m: AgentMessage) {
@@ -869,12 +878,14 @@ export class Agent {
869
878
  // N is O(N+M), not O(M*N). Consumers read state.messages fresh; run() snapshots
870
879
  // via slice() at the API boundary, so no caller relies on per-append array identity.
871
880
  this.#state.messages.push(m);
881
+ this.#contextRevision++;
872
882
  }
873
883
 
874
884
  popMessage(): AgentMessage | undefined {
875
885
  const messages = this.#state.messages.slice(0, -1);
876
886
  const removed = this.#state.messages.at(-1);
877
887
  this.#state.messages = messages;
888
+ this.#contextRevision++;
878
889
 
879
890
  if (removed && this.#state.streamMessage === removed) {
880
891
  this.#state.streamMessage = null;
@@ -883,6 +894,14 @@ export class Agent {
883
894
  return removed;
884
895
  }
885
896
 
897
+ /**
898
+ * For callers that mutate committed messages or the system prompt in place
899
+ * outside Agent-owned mutators.
900
+ */
901
+ touchContext(): void {
902
+ this.#contextRevision++;
903
+ }
904
+
886
905
  /**
887
906
  * Queue a steering message to interrupt the agent mid-run.
888
907
  * Delivered after current tool execution, skips remaining tools.
@@ -1056,6 +1075,7 @@ export class Agent {
1056
1075
 
1057
1076
  clearMessages() {
1058
1077
  this.#state.messages = [];
1078
+ this.#contextRevision++;
1059
1079
  }
1060
1080
 
1061
1081
  abort() {
@@ -1094,6 +1114,7 @@ export class Agent {
1094
1114
 
1095
1115
  reset() {
1096
1116
  this.#state.messages = [];
1117
+ this.#contextRevision++;
1097
1118
  this.#state.isStreaming = false;
1098
1119
  this.#state.streamMessage = null;
1099
1120
  this.#state.pendingToolCalls = new Set<string>();
@@ -24,6 +24,8 @@ import type { AssistantMessage, Message, Model } from "@gajae-code/ai/types";
24
24
  import {
25
25
  getOpenAIResponsesHistoryItems,
26
26
  getOpenAIResponsesHistoryPayload,
27
+ neutralizeReservedControlTokens,
28
+ neutralizeResponsesInputControlTokens,
27
29
  normalizeResponsesToolCallId,
28
30
  } from "@gajae-code/ai/utils";
29
31
  import { $env, logger } from "@gajae-code/utils";
@@ -479,10 +481,12 @@ export async function requestOpenAiRemoteCompaction(
479
481
  const endpoint = resolveOpenAiCompactEndpoint(model, options?.authCredentialType);
480
482
  const request: OpenAiRemoteCompactionRequest = {
481
483
  model: model.id,
482
- input: trimOpenAiCompactInput(
483
- compactInput,
484
- resolveOpenAiCompactInputBudget(model.contextWindow, model.maxTokens),
485
- instructions,
484
+ input: neutralizeResponsesInputControlTokens(
485
+ trimOpenAiCompactInput(
486
+ compactInput,
487
+ resolveOpenAiCompactInputBudget(model.contextWindow, model.maxTokens),
488
+ instructions,
489
+ ),
486
490
  ),
487
491
  instructions,
488
492
  };
@@ -553,10 +557,17 @@ export async function requestRemoteCompaction(
553
557
  request: RemoteCompactionRequest,
554
558
  signal?: AbortSignal,
555
559
  ): Promise<RemoteCompactionResponse> {
560
+ // The prompt embeds the serialized transcript, which can carry leaked Harmony
561
+ // control-token markers (e.g. `<|channel|>analysis`) from model output; a
562
+ // gpt-5.6-backed summarization endpoint rejects those with `Request blocked`.
563
+ const sanitizedRequest: RemoteCompactionRequest = {
564
+ systemPrompt: neutralizeReservedControlTokens(request.systemPrompt),
565
+ prompt: neutralizeReservedControlTokens(request.prompt),
566
+ };
556
567
  const response = await fetch(endpoint, {
557
568
  method: "POST",
558
569
  headers: { "content-type": "application/json" },
559
- body: JSON.stringify(request),
570
+ body: JSON.stringify(sanitizedRequest),
560
571
  signal,
561
572
  });
562
573
 
package/src/types.ts CHANGED
@@ -357,7 +357,7 @@ export type AgentMessage = Message | CustomAgentMessages[keyof CustomAgentMessag
357
357
  */
358
358
  export interface AgentState {
359
359
  systemPrompt: string[];
360
- model: Model;
360
+ model: Model | undefined;
361
361
  thinkingLevel?: Effort;
362
362
  tools: AgentTool<any>[];
363
363
  messages: AgentMessage[]; // Can include attachments + custom message types