billion-context-omp 0.2.6 → 0.2.8

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.
@@ -20,7 +20,26 @@ export interface StreamCompressCall {
20
20
  }[];
21
21
  }
22
22
  export declare function findCompressCalls(message: AgentMessage): StreamCompressCall[];
23
+ /** Extract a compress tool's arguments from a stream toolCall. Two call
24
+ * shapes exist: (1) top-level — our tools are registered with
25
+ * loadMode:"essential" so omp's tools.xdev does NOT mount them as xd://
26
+ * devices; the stream shows name:"compress" directly. (2) legacy xd:// —
27
+ * sessions recorded before that change (or hosts with tools.xdev forcing
28
+ * discoverable mounting) invoked compress through the write tool with path
29
+ * "xd://compress" and the tool args JSON-encoded in the content field. Both
30
+ * shapes must replay from the stream. Returns normalized compress args
31
+ * (content array
32
+ * plus optional topic / summaryMaxChars from wherever they live). */
33
+ export declare function compressToolArgs(call: {
34
+ name: string;
35
+ arguments?: unknown;
36
+ }): {
37
+ content: unknown[];
38
+ topic?: unknown;
39
+ summaryMaxChars?: unknown;
40
+ } | null;
23
41
  export declare function extractText(content: unknown, stripTags?: boolean): string;
42
+ export declare function stripRefTag(text: string): string;
24
43
  export declare function messageIdentity(message: unknown): string;
25
44
  export declare function matchesStoredText(stored: string, visible: string): boolean;
26
45
  export declare function coreOutToAgentMessages(coreOut: CoreMessage[], originalById: Map<string, AgentMessage>): AgentMessage[];
package/dist/runtime.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { ExtensionContext } from "@oh-my-pi/pi-coding-agent";
2
2
  import { type CompressionCore, type CompressionState, type Config, type CoreMessage, type Prompts } from "acp-kernel";
3
3
  import { type AdapterConfig } from "./config.js";
4
+ import type { BiliMessage } from "acp-kernel/wire";
4
5
  import { type AgentMessage } from "./messages.js";
5
6
  export interface FoldResult {
6
7
  state: CompressionState;
@@ -8,6 +9,13 @@ export interface FoldResult {
8
9
  originalById: Map<string, AgentMessage>;
9
10
  streamLen: number;
10
11
  }
12
+ /** Core-space fold result (provider mode): no originalById — the output
13
+ * rebuilds straight onto the wire via the kernel codecs. */
14
+ export interface CoreFoldResult {
15
+ state: CompressionState;
16
+ coreMessages: BiliMessage[];
17
+ streamLen: number;
18
+ }
11
19
  export interface AcpRuntime {
12
20
  core: CompressionCore;
13
21
  adapter: AdapterConfig;
@@ -17,10 +25,17 @@ export interface AcpRuntime {
17
25
  liveContextLimit(ctx: ExtensionContext): number;
18
26
  configFor(ctx: ExtensionContext): Config;
19
27
  foldStream(ctx: ExtensionContext, stream: AgentMessage[]): FoldResult;
28
+ /** Core-space fold (provider mode): the wire payload already parsed to
29
+ * BiliMessage[] by the kernel codec; same incremental LCP + replay
30
+ * semantics as foldStream, content-hash id space (wire-fold.ts). */
31
+ foldStreamCore(ctx: ExtensionContext, stream: BiliMessage[]): CoreFoldResult;
20
32
  stateFor(ctx: ExtensionContext): Promise<{
21
33
  state: CompressionState;
22
34
  coreMessages: CoreMessage[];
23
35
  }>;
36
+ /** Commit the folded state to the slot space the session's CURRENT mode
37
+ * folds in (provider -> core slot, context -> context slot) — the mirror
38
+ * of stateFor: a commit must land where the next read goes (issue #90). */
24
39
  commitFoldState(ctx: ExtensionContext, state: CompressionState, toolCallId?: string): void;
25
40
  /** Record the identity sequence of the last rebuilt output so the next
26
41
  * foldStream can recognize omp re-feeding it (issue #52). */
@@ -32,7 +47,7 @@ export interface AcpRuntime {
32
47
  forgetSession(sid: string): void;
33
48
  /** Rebuild blocks from the persisted session at session_start so /acp and
34
49
  * acp_status show them BEFORE the first LLM call of a resumed session.
35
- * Provider mode folds the WIRE projection (viewToWireStream mirror — the
50
+ * Provider mode folds the WIRE projection (wire-fold.ts mirrors — the
36
51
  * authoritative fold runs on the wire-synthesized stream, which differs
37
52
  * from the raw session view; issue #64). The slot is marked preview and
38
53
  * always re-folded authoritatively at the first live event (the live
@@ -0,0 +1,5 @@
1
+ import type { AdapterConfig } from "./config.js";
2
+ export declare function hostVersionAtLeast(min: readonly [number, number, number], version?: string): boolean;
3
+ export declare function resolveTransformMode(adapter: Pick<AdapterConfig, "transformMode">, model: {
4
+ api?: string;
5
+ } | undefined, hostVersion?: string): "context" | "provider";
@@ -0,0 +1,140 @@
1
+ import { type BiliMessage } from "acp-kernel/wire";
2
+ import { type CompressionState, type Config, type CoreMessage } from "acp-kernel";
3
+ import type { AgentMessage, BlockLike, StreamCompressCall } from "./messages.js";
4
+ export type ProviderWireFormat = "anthropic" | "openai";
5
+ /** Kernel format detection narrowed to the formats the omp pipeline can
6
+ * rebuild onto the wire. null = fail-open (pass the payload through). */
7
+ export declare function detectProviderWireFormat(payload: unknown): ProviderWireFormat | null;
8
+ export declare function payloadToCore(payload: unknown, fmt: ProviderWireFormat): {
9
+ msgs: BiliMessage[];
10
+ cacheControls?: Map<string, unknown>;
11
+ };
12
+ export declare function coreToPayloadMessages(msgs: BiliMessage[], fmt: ProviderWireFormat, cacheControls?: Map<string, unknown>): unknown[];
13
+ export type Representability = {
14
+ ok: true;
15
+ } | {
16
+ ok: false;
17
+ reason: string;
18
+ };
19
+ /** Whether the payloadToCore → coreToPayloadMessages round-trip can rebuild
20
+ * this payload WITHOUT content loss. The sets above mirror the kernel
21
+ * codec switches; everything they do not parse is dropped or flattened on
22
+ * the rebuild. Unrepresentable payloads must fail the transform OPEN —
23
+ * pass through untouched rather than lose content (issue #3 review). */
24
+ export declare function payloadRepresentable(payload: unknown, fmt: ProviderWireFormat): Representability;
25
+ export type WireTagRenderScope = {
26
+ config: Config;
27
+ tokenCount: number;
28
+ };
29
+ /** omp's wire tag contract (issue #66) on top of the kernel's "text-only"
30
+ * render: the proxy keeps tool content pristine, but omp's nudge ranges
31
+ * target tool results — the model must be able to cite them by ref, so tag
32
+ * the tool-result pieces (kernel renderer, format single-sourced). The
33
+ * kernel's "text-only" also tags assistant text — strip it: the model
34
+ * echoes tags it sees on its own responses (the contract patchRefTag
35
+ * enforced in the AgentMessage bridge). Tool-call args stay clean (replay
36
+ * JSON-parses them).
37
+ *
38
+ * Rendering goes through the kernel's render-refs NODE so token counts in
39
+ * the tags come from the fold state's tokenSnapshot (written once per ref,
40
+ * reused forever) instead of being recomputed per call; the updated
41
+ * snapshot is written back into the fold state in place. Tool names are
42
+ * re-attached from the call pieces first — the codecs drop them on
43
+ * tool-result pieces and classifyType would render type="tool" where the
44
+ * context path (and the system-prompt contract) shows the real name. */
45
+ export declare function applyWireTagContract(msgs: BiliMessage[], state: CompressionState, scope: WireTagRenderScope): BiliMessage[];
46
+ /** Stable cross-turn identity for the core-space LCP fold. The text carries
47
+ * our own <acp> ref tags from the previous turn's output (the model sees
48
+ * them and they ride back in the next request) — stripped before hashing
49
+ * so re-folds of an unmutated prefix stay incremental. */
50
+ export declare function coreIdentity(msg: BiliMessage): string;
51
+ /** toolCallId → toolName from the stream's tool-call pieces. The kernel
52
+ * codecs do not carry tool names on tool-result pieces, so protection
53
+ * checks (compress results stay ref-BLOCKED) resolve the name here. */
54
+ export declare function toolCallNames(msgs: BiliMessage[]): Map<string, string>;
55
+ export declare function toolResultTextsCore(msgs: BiliMessage[]): Map<string, string>;
56
+ /** Compress calls carried by a core tool-call piece. Same two shapes as the
57
+ * AgentMessage stream (direct compress; legacy xd://compress via write),
58
+ * with the arguments JSON-encoded in the piece's text. */
59
+ export declare function findCompressCallsCore(msg: BiliMessage): StreamCompressCall[];
60
+ /** Span fingerprint in content-hash space: hash the content keys of the
61
+ * exact first/last covered pieces. Boundary ids are pre-resolved (byRef /
62
+ * block lookup) — unlike the pN-space spanFingerprint there is no
63
+ * position parsing, ids are unique per piece. */
64
+ export declare function spanFingerprintCore(coreMessages: CoreMessage[], startId: string, endId: string): string;
65
+ /** Index-based span fingerprint (issue #91 replay fallback): hash the content
66
+ * keys of the pieces AT the given stream positions. The stored fp still
67
+ * decides keep/drop — the position is only a recovery hint for a drifted
68
+ * boundary whose content-hash id no longer matches, so a benign tail drift
69
+ * (first-4096 intact) is kept while a real rewrite mismatches. */
70
+ export declare function spanFingerprintCoreIdx(coreMessages: CoreMessage[], startIdx: number, endIdx: number): string;
71
+ /** Resolve a range boundary to the exact id of the piece it names, in
72
+ * content-hash space. Message refs go through byRef; block refs resolve to
73
+ * the earliest (min) or latest (max) covered piece by STREAM ORDER
74
+ * (index in coreMessages — the hash ids carry no position). */
75
+ export declare function boundaryRawCore(ref: string, byRef: Record<string, string>, blocks: BlockLike[], coreMessages: CoreMessage[], pick: "min" | "max"): string;
76
+ /** Resolve a range boundary to its STREAM INDEX in content-hash space. byRef /
77
+ * block lookup first (the exact piece it names, by array order); on a missed
78
+ * id (a drift re-hashed the piece so its carried ref dangles) fall back to
79
+ * the compress-time recorded index — the position hint, issue #91. -1 =
80
+ * unresolvable. */
81
+ export declare function boundaryIndexCore(ref: string, byRef: Record<string, string>, blocks: BlockLike[], coreMessages: CoreMessage[], pick: "min" | "max", fallbackIdx?: number): number;
82
+ /** Structured replay-guard verdict (issue #91, rework): the position
83
+ * fallback recovers the STREAM INDEX of a drifted boundary, but the kernel
84
+ * resolves ranges by REF — so when a recorded m-ref dangles, the replay
85
+ * must re-apply that boundary under the CURRENT ref of the recovered piece.
86
+ * `remap` carries exactly that (only dangling m-refs are remapped; block
87
+ * refs resolve themselves inside the kernel and are never touched). */
88
+ export type ReplayRangeVerdict = {
89
+ /** Stale: the range must be dropped (master semantics, unchanged). */
90
+ reject?: string;
91
+ /** Dangling m-refs recovered by position, remapped to current refs. */
92
+ remap?: {
93
+ startRef?: string;
94
+ endRef?: string;
95
+ };
96
+ /** True when the result text carried a [pos=] pair for this range —
97
+ * with `reject` set it marks a RECOVERY FAILURE (always logged). */
98
+ hint?: boolean;
99
+ /** Diagnostics — always logged when a recovery happens. */
100
+ recovered?: {
101
+ pos: string;
102
+ startIdx: number;
103
+ endIdx: number;
104
+ };
105
+ };
106
+ /** Current m-ref of the piece at stream index idx (inverse byRef scan).
107
+ * "" when the piece has no ref (protected) — the replay must fail closed
108
+ * rather than hand the kernel a ref it does not know. Replay-time only
109
+ * (replayed compress calls), so the O(refs) scan stays off the hot path. */
110
+ export declare function refOfPieceCore(coreMessages: BiliMessage[], idx: number, byRef: Record<string, string>): string;
111
+ export declare function staleRangeCore(r: {
112
+ startRef: string;
113
+ endRef: string;
114
+ }, rangeIndex: number, resultText: string, coreMessages: BiliMessage[], callIndex: number, byRef: Record<string, string>, blocks: BlockLike[]): ReplayRangeVerdict;
115
+ /** One fingerprint per range for the replay guard, content-hash space
116
+ * (mirrors rangeFingerprints for the pN space). */
117
+ export declare function rangeFingerprintsCore(ranges: Array<{
118
+ startRef: string;
119
+ endRef: string;
120
+ }>, coreMessages: BiliMessage[], byRef: Record<string, string>, blocks: BlockLike[]): string[];
121
+ /** One boundary-index pair per range for the replay fallback (issue #91),
122
+ * aligned with rangeFingerprintsCore: the stream index of each range's exact
123
+ * first/last covered piece at record time ("-1" pair when a boundary can't
124
+ * be positioned), so the replay can recover a drifted boundary by position. */
125
+ export declare function rangePositionsCore(ranges: Array<{
126
+ startRef: string;
127
+ endRef: string;
128
+ }>, coreMessages: CoreMessage[], byRef: Record<string, string>, blocks: BlockLike[]): string[];
129
+ /** Rebuild the WIRE-SHAPE projection of the persisted session (the mirror
130
+ * of the host's convertToLlm for openai chat) and parse it with the kernel
131
+ * codec, so primeFold (provider mode) folds exactly the space the live
132
+ * provider requests fold: system prompt first (it takes m00001), one
133
+ * tool-result piece per tool result, thinking dropped (issue #64). */
134
+ export declare function viewToCoreStream(view: AgentMessage[], systemText: string): BiliMessage[];
135
+ /** Anthropic-flavoured wire mirror for primeFold: the live anthropic
136
+ * request carries the system prompt as the TOP-LEVEL `system` field (out
137
+ * of the fold space) and folds tool results into user messages — mirror
138
+ * exactly that, or the preview lands in a different ref space than the
139
+ * live request (issue #64). */
140
+ export declare function viewToAnthropicCore(view: AgentMessage[]): BiliMessage[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "billion-context-omp",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "One billion, not one million. Model-driven context management for the oh-my-pi (omp) coding agent.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -62,13 +62,13 @@
62
62
  "@oh-my-pi/omptype": ">=17.0.0"
63
63
  },
64
64
  "devDependencies": {
65
- "@oh-my-pi/omptype": "17.3.2",
66
- "@oh-my-pi/pi-agent-core": "17.3.2",
67
- "@oh-my-pi/pi-ai": "17.3.2",
68
- "@oh-my-pi/pi-coding-agent": "17.3.2",
69
- "@oh-my-pi/pi-utils": "17.3.2",
65
+ "@oh-my-pi/omptype": "17.3.8",
66
+ "@oh-my-pi/pi-agent-core": "17.3.8",
67
+ "@oh-my-pi/pi-ai": "17.3.8",
68
+ "@oh-my-pi/pi-coding-agent": "17.3.8",
69
+ "@oh-my-pi/pi-utils": "17.3.8",
70
70
  "@types/node": "^26.1.2",
71
- "acp-kernel": "0.0.25",
71
+ "acp-kernel": "0.0.28",
72
72
  "billion-context-kit": "0.2.0",
73
73
  "bun-types": "^1.3.14",
74
74
  "tsup": "^8.5.1",
@@ -1,71 +0,0 @@
1
- import type { AgentMessage } from "./messages.js";
2
- import type { AnthropicRequestBody } from "acp-kernel/wire";
3
- export type WireFormat = "anthropic" | "openai" | "unknown";
4
- interface AnthropicBlock {
5
- type?: string;
6
- text?: string;
7
- id?: string;
8
- name?: string;
9
- input?: unknown;
10
- tool_use_id?: string;
11
- content?: unknown;
12
- is_error?: boolean;
13
- cache_control?: unknown;
14
- [k: string]: unknown;
15
- }
16
- interface AnthropicWireMessage {
17
- role: string;
18
- content: string | AnthropicBlock[];
19
- [k: string]: unknown;
20
- }
21
- interface OpenAIWireMessage {
22
- role: string;
23
- content?: unknown;
24
- tool_calls?: Array<{
25
- id: string;
26
- function?: {
27
- name?: string;
28
- arguments?: string;
29
- };
30
- }>;
31
- tool_call_id?: string;
32
- [k: string]: unknown;
33
- }
34
- export interface SynthesisResult {
35
- stream: AgentMessage[];
36
- back: Array<{
37
- wi: number;
38
- kind: "text" | "toolCall" | "toolResult";
39
- }>;
40
- format: WireFormat;
41
- }
42
- export declare function detectWireFormat(payload: unknown): WireFormat;
43
- export declare function synthesizeStream(payload: unknown, format: WireFormat): SynthesisResult;
44
- /** Rebuild the wire payload from the transformed stream. Survivors reuse the
45
- * ORIGINAL wire message objects (patched text only) so every field we do not
46
- * understand — cache_control, citations, provider extras — passes through
47
- * byte-identical. Only synthesized messages (nudge) are built from scratch.
48
- * Returns the ORIGINAL payload object when nothing changed (cache-safe). */
49
- export declare function rebuildWirePayload(rebuilt: AgentMessage[], payload: unknown, synth: SynthesisResult): unknown;
50
- /** Rebuild the WIRE-SHAPE view of the persisted session (the mirror of the
51
- * host's convertToLlm) and run it through the SAME synthesizeStream the
52
- * live provider fold uses, so primeFold (provider mode) folds exactly the
53
- * authoritative projection. The raw session view is NOT equivalent: for
54
- * openai-style payloads the system prompt is a message in the wire body —
55
- * it becomes the first core piece and takes m00001 — and the openai
56
- * synthesis cannot recover tool names for `role:"tool"` entries (a
57
- * compress result gets toolName "" instead of "compress", so it is not
58
- * ref-BLOCKED there). Folding the raw view puts the fold in a different
59
- * ref/fingerprint space: stored span fingerprints mismatch, the guard
60
- * rejects every in-stream replay, and a resumed session shows
61
- * "Blocks: none" until the first provider request (issue #64).
62
- *
63
- * Message shapes mirror the captured live payload (openai chat):
64
- * system first; assistant text and/or tool_calls (content "" when only
65
- * calls); one `role:"tool"` message per tool result; thinking dropped
66
- * (the host does not carry it in this format). */
67
- export declare function viewToWireStream(view: AgentMessage[], systemText: string): AgentMessage[];
68
- /** True when the payload carries no messages we could fold (e.g. an empty
69
- * tools-only probe). The caller bypasses instead of transforming. */
70
- export declare function synthesisIsEmpty(synth: SynthesisResult): boolean;
71
- export type { AnthropicRequestBody, OpenAIWireMessage, AnthropicWireMessage };