@linxiraos/pi-agent-core 1.1.27 → 1.2.0

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.
@@ -64,10 +64,9 @@ import type { CompactionEntry, SessionEntry } from "./entries";
64
64
  import { NativeCompactionError } from "./errors";
65
65
  import {
66
66
  type ConvertToLlm,
67
- createBranchSummaryMessage,
68
67
  createCompactionSummaryMessage,
69
- createCustomMessage,
70
68
  defaultConvertToLlm,
69
+ getMessageFromEntry,
71
70
  } from "./messages";
72
71
  import {
73
72
  assertRemoteCompactionInputFits,
@@ -142,34 +141,6 @@ function extractFileOperations(
142
141
  return fileOps;
143
142
  }
144
143
 
145
- // ============================================================================
146
- // Message Extraction
147
- // ============================================================================
148
-
149
- /**
150
- * Extract AgentMessage from an entry if it produces one.
151
- * Returns undefined for entries that don't contribute to LLM context.
152
- */
153
- function getMessageFromEntry(entry: SessionEntry): AgentMessage | undefined {
154
- if (entry.type === "message") {
155
- return entry.message;
156
- }
157
- if (entry.type === "custom_message") {
158
- return createCustomMessage(
159
- entry.customType,
160
- entry.content,
161
- entry.display,
162
- entry.details,
163
- entry.timestamp,
164
- entry.attribution,
165
- );
166
- }
167
- if (entry.type === "branch_summary") {
168
- return createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp);
169
- }
170
- return undefined;
171
- }
172
-
173
144
  /** Result from compact() - SessionManager adds uuid/parentUuid when saving */
174
145
  export interface CompactionResult<T = unknown> {
175
146
  summary: string;
@@ -192,6 +163,14 @@ export interface CompactionSettings {
192
163
  strategy?: "context-full" | "handoff" | "shake" | "snapcompact" | "off";
193
164
  thresholdPercent?: number;
194
165
  thresholdTokens?: number;
166
+ /**
167
+ * Stands in for the context window when the percentage or reserve-based
168
+ * threshold is computed (when `> 0` and smaller than the window); a positive
169
+ * `thresholdTokens` still wins and is checked against the real window (at
170
+ * or past it, the window less its reserve applies). Set by per-model
171
+ * compaction limits; request and overflow budgets keep the real window.
172
+ */
173
+ baseWindowTokens?: number;
195
174
  midTurnEnabled?: boolean;
196
175
  /**
197
176
  * Tokens reserved below the context window for the next prompt + response.
@@ -387,27 +366,42 @@ export function compactionContextTokens(providerContextTokens: number, storedCon
387
366
  return Math.max(Math.max(0, providerContextTokens), Math.max(0, storedConversationEstimate));
388
367
  }
389
368
 
369
+ /**
370
+ * The prompt budget below `contextWindow`: the window less
371
+ * {@link resolveBudgetReserveTokens}, capped at `contextWindow - 1` so it never
372
+ * reaches the whole window even when the reserve resolves to 0.
373
+ */
374
+ function promptBudgetTokens(contextWindow: number, settings: CompactionSettings): number {
375
+ return Math.max(0, Math.min(contextWindow - 1, contextWindow - resolveBudgetReserveTokens(contextWindow, settings)));
376
+ }
377
+
390
378
  export function resolveThresholdTokens(contextWindow: number, settings: CompactionSettings): number {
391
- // Fixed token limit takes priority over percentage
379
+ // Fixed token limit takes priority over percentage, and is checked against
380
+ // the real window: `baseWindowTokens` only rescales the policies below.
392
381
  const thresholdTokens = settings.thresholdTokens;
393
382
  if (typeof thresholdTokens === "number" && Number.isFinite(thresholdTokens) && thresholdTokens > 0) {
394
- // Clamp to [1, contextWindow - 1] so there's always room
383
+ // A trigger at or past the window can never fire before the request
384
+ // overflows (a provider may cap the window below the configured point);
385
+ // fall back to the window's prompt budget, as the reserve policy does.
386
+ // A trigger below the window is exact, as the `f` model entries promise,
387
+ // even one inside the reserve: the user chose that headroom. The step at
388
+ // the window is inherent; a monotone fallback would sit at window - 1.
389
+ if (thresholdTokens >= contextWindow) return promptBudgetTokens(contextWindow, settings);
395
390
  return Math.min(contextWindow - 1, Math.max(1, thresholdTokens));
396
391
  }
392
+ const baseWindowTokens = settings.baseWindowTokens;
393
+ if (typeof baseWindowTokens === "number" && Number.isFinite(baseWindowTokens) && baseWindowTokens > 0) {
394
+ contextWindow = Math.min(contextWindow, baseWindowTokens);
395
+ }
397
396
 
398
397
  // Percentage-based threshold. The default absolute reserve can exceed bundled
399
398
  // small-context windows, or nearly consume a 16k-class window; in those
400
399
  // known-impossible default configurations, fall back to the proportional
401
400
  // reserve so threshold/recovery-band checks stay usable. Explicit valid
402
- // configured reserves still define the usable prompt budget. Cap at
403
- // contextWindow - 1 (matching the fixed-token clamp above) so the threshold
404
- // never reaches the whole window even when the reserve resolves to 0.
401
+ // configured reserves still define the usable prompt budget.
405
402
  const thresholdPercent = settings.thresholdPercent;
406
403
  if (typeof thresholdPercent !== "number" || !Number.isFinite(thresholdPercent) || thresholdPercent <= 0) {
407
- return Math.max(
408
- 0,
409
- Math.min(contextWindow - 1, contextWindow - resolveBudgetReserveTokens(contextWindow, settings)),
410
- );
404
+ return promptBudgetTokens(contextWindow, settings);
411
405
  }
412
406
  const clampedThresholdPercent = Math.min(99, Math.max(1, thresholdPercent));
413
407
  return Math.floor(contextWindow * (clampedThresholdPercent / 100));
@@ -698,7 +692,7 @@ export interface SummaryOptions {
698
692
  /**
699
693
  * Optional telemetry handle. When provided, every LLM call emitted during
700
694
  * compaction is wrapped in an OTEL chat span tagged with
701
- * `omp.gen_ai.oneshot.kind` (`compaction_summary`, `compaction_short_summary`,
695
+ * `zeta.gen_ai.oneshot.kind` (`compaction_summary`, `compaction_short_summary`,
702
696
  * or `compaction_turn_prefix`). `undefined` keeps the call paths zero-cost.
703
697
  */
704
698
  telemetry?: AgentTelemetry;
@@ -1061,7 +1055,7 @@ export interface HandoffOptions {
1061
1055
  metadata?: Record<string, unknown>;
1062
1056
  /**
1063
1057
  * Optional telemetry handle. When provided, the handoff LLM call is
1064
- * wrapped in an OTEL chat span tagged with `omp.gen_ai.oneshot.kind = "handoff"`.
1058
+ * wrapped in an OTEL chat span tagged with `zeta.gen_ai.oneshot.kind = "handoff"`.
1065
1059
  */
1066
1060
  telemetry?: AgentTelemetry;
1067
1061
  /**
@@ -1874,7 +1868,7 @@ export async function compact(
1874
1868
  ),
1875
1869
  { signal },
1876
1870
  );
1877
- preserveData = withOpenAiRemoteCompactionPreserveData(previousPreserveData, remote);
1871
+ preserveData = withOpenAiRemoteCompactionPreserveData(preserveData, remote);
1878
1872
  usedRemoteCompaction = true;
1879
1873
  } catch (err) {
1880
1874
  // A user/session abort is a cancellation, not a remote failure —
@@ -48,7 +48,7 @@ export function registerMessageCacheInvalidator(invalidate: (message: AgentMessa
48
48
  * the tag onto derived clones — harmless, because estimate memos key on message
49
49
  * *identity* and a fresh clone starts with no memo entries anywhere.
50
50
  */
51
- const kEstimateVersion = Symbol("omp.messageEstimateVersion");
51
+ const kEstimateVersion = Symbol("zeta.messageEstimateVersion");
52
52
 
53
53
  interface VersionedMessage {
54
54
  [kEstimateVersion]?: number;
@@ -8,6 +8,7 @@ import type {
8
8
  } from "@linxiraos/pi-ai";
9
9
  import { prompt } from "@linxiraos/pi-utils";
10
10
  import type { AgentMessage } from "../types";
11
+ import type { SessionEntry } from "./entries";
11
12
  import branchSummaryContextPrompt from "./prompts/branch-summary-context.md" with { type: "text" };
12
13
  import compactionSummaryContextPrompt from "./prompts/compaction-summary-context.md" with { type: "text" };
13
14
  import handoffSummaryContextPrompt from "./prompts/handoff-summary-context.md" with { type: "text" };
@@ -291,3 +292,27 @@ export function convertMessageToLlm(message: AgentMessage): Message | undefined
291
292
  export function defaultConvertToLlm(messages: AgentMessage[]): Message[] {
292
293
  return messages.map(convertMessageToLlm).filter(message => message !== undefined);
293
294
  }
295
+
296
+ /**
297
+ * The context message a session entry contributes, or `undefined` for entries
298
+ * that don't reach the LLM (compaction markers, labels, model changes, ...).
299
+ */
300
+ export function getMessageFromEntry(entry: SessionEntry): AgentMessage | undefined {
301
+ if (entry.type === "message") {
302
+ return entry.message;
303
+ }
304
+ if (entry.type === "custom_message") {
305
+ return createCustomMessage(
306
+ entry.customType,
307
+ entry.content,
308
+ entry.display,
309
+ entry.details,
310
+ entry.timestamp,
311
+ entry.attribution,
312
+ );
313
+ }
314
+ if (entry.type === "branch_summary") {
315
+ return createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp);
316
+ }
317
+ return undefined;
318
+ }
@@ -20,6 +20,7 @@ import { getCodexAttestationHeader } from "@linxiraos/pi-ai/providers/openai-cod
20
20
  import { createOpenAICodexCompactionRequestContext } from "@linxiraos/pi-ai/providers/openai-codex-compaction";
21
21
  import { applyCodexResponsesLiteShape } from "@linxiraos/pi-ai/providers/openai-codex/request-transformer";
22
22
  import { createOpenAICodexCompatibilityMetadata } from "@linxiraos/pi-ai/providers/openai-codex-responses";
23
+ import { applySessionHeader } from "@linxiraos/pi-ai/providers/inference-headers";
23
24
  import {
24
25
  encodeResponsesToolResultOutput,
25
26
  hoistInterleavedResponsesToolBatchMessages,
@@ -862,6 +863,7 @@ export async function requestOpenAiRemoteCompaction(
862
863
  Authorization: `Bearer ${apiKey}`,
863
864
  ...model.headers,
864
865
  };
866
+ applySessionHeader(headers, model.compat, opts?.sessionId);
865
867
 
866
868
  // Codex endpoints require additional auth headers
867
869
  if (isCodexResponses) {