@linxiraos/pi-agent-core 1.1.26 → 1.1.27

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,9 +2,30 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
- ## [1.1.26] - 2026-10-03
5
+ ## [1.1.27] - 2026-10-07
6
6
 
7
- - 版本线推进至 1.1.26;本版无独立用户可见变化。
7
+ - 版本线推进至 1.1.27;本版无独立用户可见变化。
8
+
9
+ ### Breaking Changes
10
+
11
+ - `Agent.withdrawUndeliveredQueuedMessages()` replaces `withdrawLiveSteering()` and returns `{ steering, followUp }`: it also takes back queued input already dequeued for the next model call, which the aborted run then neither records nor reports in `agent_end` ([#14179](https://github.com/can1357/oh-my-pi/pull/14179) by [@andrebrait](https://github.com/andrebrait))
12
+
13
+ ### Added
14
+
15
+ - Added `Agent.setOnModelCallSystemPrompt`, called with the exact system prompt each model call is built from ([#14338](https://github.com/can1357/oh-my-pi/pull/14338) by [@will-bogusz](https://github.com/will-bogusz))
16
+
17
+ ### Changed
18
+
19
+ - `SessionInitEntry.systemPrompt` holds the system prompt blocks as sent; session files written earlier keep one joined string ([#14338](https://github.com/can1357/oh-my-pi/pull/14338) by [@will-bogusz](https://github.com/will-bogusz))
20
+ - Image token estimates now use the catalog's `imageTokens()` formula with its OpenAI Responses wire rule; values are unchanged ([#14286](https://github.com/can1357/oh-my-pi/pull/14286) by [@will-bogusz](https://github.com/will-bogusz)).
21
+
22
+ ### Fixed
23
+
24
+ - Fixed the context count and auto-compaction trigger pricing every snapcompact archive frame at Opus's high-res rate, which overcounted Codex and Gemini sessions and could re-trigger compaction right after compacting ([#14291](https://github.com/can1357/oh-my-pi/pull/14291) by [@will-bogusz](https://github.com/will-bogusz))
25
+ - Fixed native (OpenAI) compaction being refused as over the context window in sessions with many screenshots. Images were counted as about 1,200 tokens when deciding to compact but 12,000 when checking whether the compaction request fits; both checks now estimate images from their actual dimensions, and a request is no longer refused when only the image estimate pushes it over the window ([#14260](https://github.com/can1357/oh-my-pi/pull/14260) by [@H4vC](https://github.com/H4vC)).
26
+ - Fixed OpenAI and Codex Remote Compaction V2 dropping your recent messages instead of keeping them next to the compaction summary ([#14247](https://github.com/can1357/oh-my-pi/pull/14247) by [@will-bogusz](https://github.com/will-bogusz))
27
+ - Fixed the failed V2 remote compaction warning claiming a V1 fallback on Codex, where V1 does not run ([#14245](https://github.com/can1357/oh-my-pi/pull/14245) by [@will-bogusz](https://github.com/will-bogusz))
28
+ - Fixed Anthropic native compaction being rejected with `Invalid signature in thinking block` (or silently dropping the summarized thinking) on models with preserved thinking ([#14251](https://github.com/can1357/oh-my-pi/pull/14251) by [@will-bogusz](https://github.com/will-bogusz))
8
29
 
9
30
  ## [14.9.5] - 2026-05-12
10
31
 
@@ -390,8 +390,8 @@ export declare class Agent {
390
390
  get state(): AgentState;
391
391
  /**
392
392
  * Tokenizer for the active model. The instance is replaced whenever the
393
- * active model's encoding changes (see {@link setModel}), so callers must
394
- * not cache it across model switches.
393
+ * active model's encoding or snapcompact frame pricing changes (see
394
+ * {@link setModel}), so callers must not cache it across model switches.
395
395
  */
396
396
  get tokenizer(): Tokenizer;
397
397
  get appendOnlyContext(): AppendOnlyContextManager | undefined;
@@ -422,10 +422,15 @@ export declare class Agent {
422
422
  /** Register an independently removable hook that runs immediately before each model call. */
423
423
  addBeforeModelCallHook(hook: (signal?: AbortSignal) => Promise<void> | void): () => void;
424
424
  /**
425
- * Take back live-steered messages ahead of an abort (Esc restores them to the editor):
426
- * the aborted run then neither records nor requeues them.
425
+ * Take back dequeued input the transcript has not recorded yet, ahead of an abort (Esc restores
426
+ * it to the editor): steering live steering took for the in-flight response, then each batch
427
+ * dequeued for the next model call, oldest first. The aborted run then neither records it (or
428
+ * the context prepared for it) nor requeues it.
427
429
  */
428
- withdrawLiveSteering(): AgentMessage[];
430
+ withdrawUndeliveredQueuedMessages(): {
431
+ steering: AgentMessage[];
432
+ followUp: AgentMessage[];
433
+ };
429
434
  /** Steering live steering took for the streaming response; the transcript records it once
430
435
  * that response (or its tool batch) ends, which is when the model switches to it. */
431
436
  peekLiveSteeredMessages(): AgentMessage[];
@@ -434,6 +439,8 @@ export declare class Agent {
434
439
  setAssistantMessageEventInterceptor(fn: ((message: AssistantMessage, event: AssistantMessageEvent) => void) | undefined): void;
435
440
  setOnBeforeYield(fn: (() => Promise<void> | void) | undefined): void;
436
441
  setOnTurnEnd(fn: ((messages: AgentMessage[], signal?: AbortSignal, context?: AgentTurnEndContext) => Promise<void> | void) | undefined): void;
442
+ /** Called with the exact system prompt each model call is built from, after before-model-call hooks. */
443
+ setOnModelCallSystemPrompt(fn: ((systemPrompt: string[]) => void) | undefined): void;
437
444
  /**
438
445
  * Install or replace the host pre-model-call gate; pass `undefined` to
439
446
  * remove it. Gates are sampled when a run starts: installing the first
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * Anthropic on-demand compaction (`compact-2026-09-04` beta).
3
3
  *
4
- * The request sends only the prefix to summarize, with the live conversation's
5
- * system prompt, tools and thinking settings. The returned signed block
6
- * replaces that prefix; the retained tail is replayed from session entries.
4
+ * The request sends only the prefix to summarize, as the live conversation
5
+ * sent it: same system prompt, tools, history bytes and thinking settings.
6
+ * The returned signed block replaces that prefix; the retained tail is
7
+ * replayed from session entries.
7
8
  */
8
- import type { AnthropicCompactionPayload, ApiKey, Effort, Message, Model, SimpleStreamOptions, Tool, Usage } from "@linxiraos/pi-ai";
9
+ import type { AnthropicCompactionFiles, AnthropicCompactionPayload, ApiKey, Context, Effort, Model, SimpleStreamOptions, Usage } from "@linxiraos/pi-ai";
9
10
  import { type InstrumentedChatSpanOptions } from "../telemetry.js";
10
11
  import type { AgentMessage } from "../types.js";
11
12
  export declare const ANTHROPIC_COMPACTION_PRESERVE_KEY = "anthropicCompaction";
@@ -17,8 +18,12 @@ export interface AnthropicCompactionPreserveData {
17
18
  signature?: string;
18
19
  /** Legacy threshold block state; replay-only. */
19
20
  encryptedContent?: string;
20
- /** Harness file metadata (`<files>` section) replayed after the native block. */
21
+ /** Harness file metadata (`<files>` section) replayed after the retained tail. */
21
22
  filesText?: string;
23
+ /** Earlier summaries' file metadata replayed inside the retained tail. */
24
+ retainedFiles?: AnthropicCompactionFiles[];
25
+ /** The retained tail replays unchanged (see `AnthropicCompactionPayload.exactTail`). */
26
+ exactTail?: true;
22
27
  /** Model that wrote the summary. */
23
28
  model?: string;
24
29
  /** Prompt tokens the compaction request processed, for display. */
@@ -51,10 +56,11 @@ export declare function findAnthropicCompactionCut(messages: readonly (AgentMess
51
56
  /** Instructions replace the API default; the request contains only summarized messages. */
52
57
  export declare function buildAnthropicCompactionInstructions(basePrompt: string, customInstructions: string | undefined, extraContext: string | undefined): string;
53
58
  export interface AnthropicNativeCompactionRequest {
54
- systemPrompt: string[];
55
- messages: Message[];
56
- tools?: Tool[];
59
+ /** The summarized prefix as the live turn would send it. */
60
+ context: Context;
57
61
  instructions: string;
62
+ /** Replayed file metadata due before this time ends the request (see `AnthropicCompactionRequest.filesDueBefore`). */
63
+ filesDueBefore?: number;
58
64
  maxTokens: number;
59
65
  reasoning?: Effort;
60
66
  }
@@ -26,6 +26,8 @@ export interface CompactionV2Usage {
26
26
  export interface CompactionV2Request {
27
27
  body: OpenAICodexCompactionBody;
28
28
  input: unknown[];
29
+ /** Serialized user-written turns to keep next to the compaction item; defaults to `input`. */
30
+ retainedUserItems: unknown[];
29
31
  retainedMessageBudget: number;
30
32
  sessionId?: string;
31
33
  promptCacheKey?: string;
@@ -56,12 +58,14 @@ export declare function buildCompactionV2Request(model: Model, input: unknown[],
56
58
  sessionId?: string;
57
59
  promptCacheKey?: string;
58
60
  retainedMessageBudget?: number;
61
+ retainedUserItems?: unknown[];
59
62
  }): CompactionV2Request;
60
63
  /** Wrap a body built by the normal Codex serializer for V2 compaction transport. */
61
64
  export declare function buildCompactionV2RequestFromBody(model: Model, body: OpenAICodexCompactionBody, options?: {
62
65
  sessionId?: string;
63
66
  promptCacheKey?: string;
64
67
  retainedMessageBudget?: number;
68
+ retainedUserItems?: unknown[];
65
69
  }): CompactionV2Request;
66
70
  /** Request V2 compaction over the normal OpenAI Responses streaming endpoint. */
67
71
  export declare function requestCompactionV2Streaming(model: Model, apiKey: string, request: CompactionV2Request, signal?: AbortSignal, options?: {
@@ -72,7 +76,7 @@ export declare function requestCompactionV2Streaming(model: Model, apiKey: strin
72
76
  codexCompaction?: CodexCompactionContext;
73
77
  preferWebsockets?: boolean;
74
78
  }): Promise<CompactionV2Response>;
75
- /** Build Codex-style V2 replacement history from prompt input plus compaction output. */
79
+ /** Build Codex-style V2 replacement history from retention candidates plus compaction output. */
76
80
  export declare function buildCompactionV2ReplacementHistory(input: unknown[], compactionItem: Record<string, unknown>, retainedMessageBudget?: number): {
77
81
  replacementHistory: Array<Record<string, unknown>>;
78
82
  retainedImageCount: number;
@@ -177,6 +177,20 @@ export interface SummaryOptions {
177
177
  initiatorOverride?: MessageAttribution;
178
178
  metadata?: Record<string, unknown>;
179
179
  convertToLlm?: ConvertToLlm;
180
+ /**
181
+ * The provider context a live turn sends for `summarized` + `retained`, cut to
182
+ * `summarized`. Anthropic on-demand compaction sends it so retained signed
183
+ * thinking keeps its prefix; without it the request uses `remoteSystemPrompt`,
184
+ * `tools` and `convertToLlm`.
185
+ */
186
+ buildProviderContext?: (summarized: AgentMessage[], retained: AgentMessage[], signal?: AbortSignal) => Promise<Context>;
187
+ /**
188
+ * Whether a message is a turn the user wrote. Remote Compaction V2 keeps these
189
+ * next to the compaction item. Defaults to `role === "user"`; hosts whose
190
+ * user-initiated turns also arrive as custom messages (e.g. skill invocations)
191
+ * widen it.
192
+ */
193
+ isUserAuthored?: (message: AgentMessage) => boolean;
180
194
  /**
181
195
  * Optional telemetry handle. When provided, every LLM call emitted during
182
196
  * compaction is wrapped in an OTEL chat span tagged with
@@ -88,8 +88,8 @@ export interface TtsrInjectionEntry extends SessionEntryBase {
88
88
  }
89
89
  export interface SessionInitEntry extends SessionEntryBase {
90
90
  type: "session_init";
91
- /** Full system prompt sent to the model */
92
- systemPrompt: string;
91
+ /** System prompt blocks exactly as sent to the model; files written before blocks were kept store one joined string. */
92
+ systemPrompt: string[] | string;
93
93
  /** Initial task/user message */
94
94
  task: string;
95
95
  /** Tools available to the agent */
@@ -28,19 +28,31 @@ export declare const OPENAI_REMOTE_COMPACTION_PRESERVE_KEY = "openaiRemoteCompac
28
28
  */
29
29
  export declare const REMOTE_COMPACTION_TIMEOUT_MS = 300000;
30
30
  export declare const CONTEXT_WINDOW_TRUNCATED_OUTPUT_MESSAGE: string;
31
+ /**
32
+ * Tokens of stored Responses replacement history. Retained `input_image` parts
33
+ * count at their image estimate rather than as base64 text; everything else,
34
+ * opaque compaction state included, counts as serialized.
35
+ */
36
+ export declare function countResponsesHistoryTokens(items: unknown[], tokenizer: Tokenizer): number;
31
37
  export interface TrimRemoteCompactionInputResult {
32
38
  input: Array<Record<string, unknown>>;
33
39
  rewrittenOutputs: number;
34
40
  estimatedTokensBefore: number;
35
41
  estimatedTokensAfter: number;
36
- /** Whether `input` fits the model window; false means it must not be sent. */
42
+ /**
43
+ * Whether `input` may be sent; false means it must not be. True when the
44
+ * full estimate fits, or when only the image estimate pushes it over (image
45
+ * pricing is a guess; the provider's own accounting decides).
46
+ */
37
47
  fits: boolean;
38
48
  }
39
49
  /**
40
50
  * Preserve the full native transcript unless trailing tool outputs alone push a
41
51
  * remote compaction request beyond the model window. Replacing only those
42
52
  * outputs keeps call/result pairing and all earlier assistant/reasoning history,
43
- * matching Codex's recovery path for oversized tool turns.
53
+ * matching Codex's recovery path for oversized tool turns. A request whose text
54
+ * fits but whose image estimate does not is still sendable: refusing it on an
55
+ * image guess would skip native compaction the provider would have accepted.
44
56
  */
45
57
  export declare function trimRemoteCompactionInputToContextWindow(input: Array<Record<string, unknown>>, tokenizer: Tokenizer, contextWindow: number | null | undefined, instructions: string, tools?: unknown[]): TrimRemoteCompactionInputResult;
46
58
  /**
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Tool output pruning utilities for compaction.
3
3
  */
4
+ import type { ToolResultMessage } from "@linxiraos/pi-ai";
4
5
  import type { Tokenizer } from "../tokenizer.js";
5
6
  import type { SessionEntry } from "./entries.js";
6
7
  import { type ProtectedToolMatcher } from "./tool-protection.js";
@@ -18,6 +19,8 @@ export interface PruneConfig {
18
19
  * unchanged.
19
20
  */
20
21
  supersedeKey?: SupersedeKeyFn;
22
+ /** Whether a keyed result shows its whole target (see {@link SupersedePruneConfig.supersedeComplete}). */
23
+ supersedeComplete?: SupersedeCompleteFn;
21
24
  /** Useless-flagged results bypass the protect window (see {@link USELESS_NOTICE}). Default true. */
22
25
  pruneUseless?: boolean;
23
26
  /**
@@ -56,15 +59,35 @@ export declare const SUPERSEDED_NOTICE = "[Superseded by a newer read of this fi
56
59
  export declare const USELESS_NOTICE = "[Uneventful result elided]";
57
60
  /**
58
61
  * Maps a tool call to a supersede key. Results sharing a key form a group in
59
- * which every result except the newest is a supersede candidate. A key `K`
60
- * additionally supersedes keys with prefix `K + "\u0000"` (selector-free read
61
- * supersedes selector-carrying reads of the same base path). Return
62
- * `undefined` to exempt a call from supersede grouping.
62
+ * which a newer successful result supersedes every older one. A key `K` also
63
+ * covers keys with prefix `K + "\u0000"`: they are superseded only when the
64
+ * newest successful result for `K` is complete (see
65
+ * {@link SupersedeCompleteFn}). Return `undefined` to exempt a call from
66
+ * supersede grouping.
63
67
  */
64
68
  export type SupersedeKeyFn = (toolName: string, args: Record<string, unknown>) => string | undefined;
69
+ /**
70
+ * Whether a successful keyed result shows its whole target, so a result for a
71
+ * parent key (e.g. a selector-free read) may supersede child-key results (e.g.
72
+ * range reads). A summary, truncated page, or notice is not complete.
73
+ */
74
+ export type SupersedeCompleteFn = (message: ToolResultMessage) => boolean;
65
75
  export interface SupersedePruneConfig {
66
- /** Supersede key function; results sharing a key supersede older ones. */
76
+ /** Supersede key function; a newer successful result with the same key supersedes older ones (see {@link SupersedeKeyFn}). */
67
77
  supersedeKey?: SupersedeKeyFn;
78
+ /**
79
+ * Whether a successful keyed result is complete. Absent, every successful
80
+ * result is complete. Child-key results are superseded only when the parent
81
+ * key's newest successful result is complete; same-key results follow
82
+ * newest-wins, since a same-key re-read that shows a different view means
83
+ * the target changed. A failed result supersedes only older failed results.
84
+ *
85
+ * Trade-off, by design: after `read foo.ts:50-200` → edit → a bare
86
+ * `read foo.ts` that returns a summary, the pre-edit range stays in context
87
+ * until a complete bare read or a same-range re-read. Keeping possibly stale
88
+ * detail is preferred over losing detail the summary does not show.
89
+ */
90
+ supersedeComplete?: SupersedeCompleteFn;
68
91
  /** Also prune results flagged useless by their tool. Default false. */
69
92
  pruneUseless?: boolean;
70
93
  /** Prune a candidate now when all messages after it total at most this many estimated tokens. Default 8 000. */
@@ -113,9 +136,10 @@ export declare function pruneToolOutputs(entries: SessionEntry[], tokenizer: Tok
113
136
  * selector stripped (the read tool's own splitter grammar via
114
137
  * {@link splitReadSelector}, e.g. `src/foo.ts:50-200`, `:2-4:raw`).
115
138
  * Internal/URL-scheme paths (`skill://…`, `https://…`) are exempt.
116
- * Selector-free reads key on the bare path; selector-carrying reads key on
117
- * `path + "\u0000" + selector`, so two reads collide only when the newer is
118
- * selector-free or the selectors are identical (the pass's prefix rule lets a
119
- * bare-path read supersede selector-carrying reads of the same file).
139
+ * Selector-free reads key on the bare path; line-range reads key on
140
+ * `path + "\u0000" + selector`, so a complete bare-path read can supersede them.
141
+ * `raw` and `conflicts` reads show a different form than a plain read (e.g. raw
142
+ * notebook JSON vs converted notebook text), so they key on
143
+ * `path + "\u0001" + selector` and are superseded only by the same selector.
120
144
  */
121
145
  export declare function readToolSupersedeKey(toolName: string, args: Record<string, unknown>): string | undefined;
@@ -26,6 +26,11 @@ export declare function splitReadSelector(path: string): {
26
26
  * and matches its write/edit path when computing Read/Write/RW markers.
27
27
  */
28
28
  export declare function stripReadSelector(path: string): string;
29
+ /**
30
+ * Whether a selector from {@link splitReadSelector} shows the file in a form other
31
+ * than its plain lines: verbatim `raw` bytes or the merge `conflicts` view.
32
+ */
33
+ export declare function isAlternateFormReadSelector(sel: string): boolean;
29
34
  /**
30
35
  * Whether `path` references a `scheme://` URL (internal URI or web URL) rather
31
36
  * than a filesystem path that belongs in the compaction `<files>` summary.
@@ -0,0 +1,29 @@
1
+ import type { ImageContent } from "@linxiraos/pi-ai";
2
+ import { type ImageSize } from "@linxiraos/pi-catalog/compat/image-tokenization";
3
+ /**
4
+ * Dimension-based image token estimates shared by the local context counter
5
+ * ({@link Tokenizer.countMessage}) and the native remote-compaction fit probe.
6
+ * Both sides must price the same image the same way: when they disagreed
7
+ * (1.2k flat locally vs a 12k flat worst case remotely) an image-heavy session
8
+ * stayed under the local compaction trigger yet was refused as over-window by
9
+ * the remote probe.
10
+ *
11
+ * Follows the catalog's image rule for the OpenAI Responses wire (GPT-5.5's
12
+ * 32px patches, a per-detail pixel limit and patch budget, x1.2 multiplier),
13
+ * which is also a close upper estimate for other providers once omp has
14
+ * downscaled the image (≤1568px by default).
15
+ */
16
+ /** OpenAI image `detail` values; `undefined` means the provider default (`auto`). */
17
+ export type ImageDetail = ImageContent["detail"];
18
+ export type { ImageSize };
19
+ /**
20
+ * Estimated input tokens for one image. Unknown dimensions (undecodable data,
21
+ * remote URLs, provider file ids) charge the detail level's full patch budget.
22
+ */
23
+ export declare function estimateImageTokens(size: ImageSize | null | undefined, detail?: ImageDetail): number;
24
+ /** Pixel size read from the header of base64-encoded image data, or null. */
25
+ export declare function base64ImageSize(base64: string): ImageSize | null;
26
+ /** Pixel size of a `data:<mime>;base64,` URL, or null for any other URL. */
27
+ export declare function dataUrlImageSize(url: string): ImageSize | null;
28
+ /** {@link estimateImageTokens} for an in-memory image block. */
29
+ export declare function estimateImageContentTokens(image: Pick<ImageContent, "data" | "detail">): number;
@@ -145,10 +145,18 @@ export interface ChatUsageSnapshot {
145
145
  }
146
146
  /** Context passed to the cost estimator. */
147
147
  export interface CostEstimatorContext {
148
+ /** OTel provider label (`gen_ai.provider.name`). */
148
149
  readonly provider: string;
150
+ /** Provider id before OTel normalization (e.g. `openai-codex` for the `openai` label). */
151
+ readonly providerId: string | undefined;
152
+ /** Model id reported on the response; a provider may report a served model instead of the requested one. */
149
153
  readonly model: string;
154
+ /** Requested model id, when the caller knows it. */
155
+ readonly modelId: string | undefined;
150
156
  readonly serviceTier: ServiceTier | undefined;
151
157
  readonly usage: ChatUsageSnapshot;
158
+ /** Cost the provider already computed for this request; the value persisted with the message. */
159
+ readonly usageCost: Usage["cost"];
152
160
  }
153
161
  /**
154
162
  * Cost estimator result.
@@ -390,6 +398,8 @@ export interface ChatRequestSnapshot {
390
398
  */
391
399
  export declare function finishChatSpan(telemetry: AgentTelemetry | undefined, span: Span | undefined, message: AssistantMessage, options: {
392
400
  readonly stepNumber: number;
401
+ /** Requested model id; `message.model` may name a served fallback model instead. */
402
+ readonly modelId?: string;
393
403
  readonly serviceTier?: ServiceTier;
394
404
  readonly responseHeaders?: Readonly<Record<string, string>>;
395
405
  readonly baseUrl?: string;
@@ -1,5 +1,6 @@
1
1
  import type { Model } from "@linxiraos/pi-ai";
2
2
  import * as natives from "@linxiraos/pi-natives";
3
+ import * as snapcompact from "@linxiraos/pi-snapcompact";
3
4
  import type { AgentMessage } from "./types.js";
4
5
  /** Maps the catalog-resolved tokenizer family to its native implementation. */
5
6
  export declare function tokenizerEncodingForModel(model: Pick<Model, "tokenizer"> | null | undefined): natives.Encoding | null;
@@ -42,14 +43,15 @@ export interface TokenBudgetCheck {
42
43
  * Model-aware local token counter. Immutable: the catalog-resolved encoding
43
44
  * is fixed at construction, so a cached count can never straddle two
44
45
  * encodings. An `Agent` owns one for its active model (swapping the instance
45
- * when the model's encoding changes); one-shot flows construct their own for
46
- * the model that will be billed. Known tokenizer families use exact native
47
- * counts; unknown models keep the fast byte estimate (or o200k when
48
- * `PI_TOKENIZER_ACCURATE=1`).
46
+ * when the model's encoding or snapcompact frame pricing changes); one-shot
47
+ * flows construct their own for the model that will be billed. Known
48
+ * tokenizer families use exact native counts; unknown models keep the fast
49
+ * byte estimate (or o200k when `PI_TOKENIZER_ACCURATE=1`).
49
50
  */
50
51
  export declare class Tokenizer {
51
52
  #private;
52
- constructor(model?: Pick<Model, "tokenizer"> | null);
53
+ readonly frameBillingKey: string;
54
+ constructor(model?: (Pick<Model, "tokenizer"> & snapcompact.ShapeTarget) | null);
53
55
  get encoding(): natives.Encoding | null;
54
56
  countTokens(text: string | string[], mode?: TokenCountMode): number;
55
57
  /**
@@ -70,7 +72,8 @@ export declare class Tokenizer {
70
72
  * Settled historical messages are counted once and reused until an owner
71
73
  * (prune/shake/strip-images) calls `invalidateMessageCache`; streaming
72
74
  * assistants bypass the memo entirely (see the message-cache settle-gate
73
- * invariant). Image blocks charge a fixed per-image estimate.
75
+ * invariant). Image blocks charge {@link estimateImageContentTokens}, the
76
+ * same dimension-based estimate the native remote-compaction probe applies.
74
77
  */
75
78
  countMessage(message: AgentMessage, options?: MessageCountOptions): number;
76
79
  /** Sum of {@link countMessage} over `messages`. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@linxiraos/pi-agent-core",
4
- "version": "1.1.26",
4
+ "version": "1.1.27",
5
5
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
6
6
  "homepage": "https://linxira-os.github.io/zeta/",
7
7
  "author": "Linxira-OS",
@@ -35,16 +35,16 @@
35
35
  "fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
36
36
  },
37
37
  "dependencies": {
38
- "@linxiraos/pi-ai": "1.1.26",
39
- "@linxiraos/pi-catalog": "1.1.26",
40
- "@linxiraos/pi-natives": "1.1.26",
41
- "@linxiraos/pi-utils": "1.1.26",
42
- "@linxiraos/pi-wire": "1.1.26",
43
- "@linxiraos/pi-snapcompact": "1.1.26",
38
+ "@linxiraos/pi-ai": "1.1.27",
39
+ "@linxiraos/pi-catalog": "1.1.27",
40
+ "@linxiraos/pi-natives": "1.1.27",
41
+ "@linxiraos/pi-utils": "1.1.27",
42
+ "@linxiraos/pi-wire": "1.1.27",
43
+ "@linxiraos/pi-snapcompact": "1.1.27",
44
44
  "@opentelemetry/api": "^1.9.1"
45
45
  },
46
46
  "devDependencies": {
47
- "@linxiraos/pi-omptype": "1.1.26",
47
+ "@linxiraos/pi-omptype": "1.1.27",
48
48
  "@opentelemetry/context-async-hooks": "^2.9.0",
49
49
  "@opentelemetry/sdk-trace-base": "^2.9.0",
50
50
  "@types/bun": "^1.3.14"