acp-kernel 0.0.34 → 0.0.36

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.
@@ -0,0 +1,5 @@
1
+ /** Compact token formatting for user-facing panels: lowercase k/M with the
2
+ * same thresholds as the hosts' footers (<1000 → raw, <10000 → one decimal
3
+ * k, <1e6 → rounded k, <1e7 → one decimal M). */
4
+ export declare function formatCompactTokens(count: number): string;
5
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/panel/format.ts"],"names":[],"mappings":"AAAA;;kDAEkD;AAClD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMzD"}
@@ -0,0 +1,5 @@
1
+ export { topicFallback } from "./topic.js";
2
+ export { formatCompactTokens } from "./format.js";
3
+ export { buildStatusPanel } from "./panel.js";
4
+ export type { StatusPanelInput } from "./panel.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/panel/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,116 @@
1
+ import {
2
+ defaultCountTokens,
3
+ formatRanges,
4
+ viableRanges
5
+ } from "../chunk-MWXUJVMN.js";
6
+
7
+ // src/panel/topic.ts
8
+ function topicFallback(summary) {
9
+ const first = summary.split(/[.\n]/)[0] ?? "";
10
+ const t = first.trim().replace(/^["'`]+/, "").trim();
11
+ return t.length <= 30 ? t : `${t.slice(0, 30).trimEnd()}\u2026`;
12
+ }
13
+
14
+ // src/panel/format.ts
15
+ function formatCompactTokens(count) {
16
+ if (count < 1e3) return count.toString();
17
+ if (count < 1e4) return `${(count / 1e3).toFixed(1)}k`;
18
+ if (count < 1e6) return `${Math.round(count / 1e3)}k`;
19
+ if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
20
+ return `${Math.round(count / 1e6)}M`;
21
+ }
22
+
23
+ // src/panel/panel.ts
24
+ function bar(value, total, width = 20) {
25
+ if (total === 0) return "";
26
+ const filled = Math.max(0, Math.min(width, Math.round(value / total * width)));
27
+ return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
28
+ }
29
+ function buildStatusPanel(input) {
30
+ const { tokenCount, state, nudge, modelContextLimit } = input;
31
+ const fmt = input.fmtTokens ?? formatCompactTokens;
32
+ const bd = nudge?.contextBreakdown;
33
+ const limit = modelContextLimit;
34
+ const classified = bd ? bd.system + bd.tool + bd.summaries + bd.code + bd.text : 0;
35
+ const systemPromptTokens = input.systemPromptTokens;
36
+ const sentTotal = classified + systemPromptTokens;
37
+ const sessionOnly = input.unprunedTokens !== void 0 ? Math.max(0, input.unprunedTokens - sentTotal) : 0;
38
+ const displayTotal = tokenCount;
39
+ const displayPct = limit > 0 ? Math.round(displayTotal / limit * 100) : 0;
40
+ const sentPct = limit > 0 ? Math.round(sentTotal / limit * 100) : 0;
41
+ const activeBlocksList = state.blocks.filter((b) => b.active);
42
+ const totalBlocksList = state.blocks;
43
+ const lines = [];
44
+ lines.push("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
45
+ lines.push("\u2502 ACP Context Analysis \u2502");
46
+ lines.push("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
47
+ if (input.version) lines.push(input.version);
48
+ lines.push("");
49
+ lines.push(`Context (session accounting, host footer scale): ${displayPct}% (${fmt(displayTotal)} / ${fmt(limit)}) \u2014 never shrinks; includes compressed originals`);
50
+ if (nudge && bd) {
51
+ const growth = bd.growth;
52
+ if (growth > 0 && displayTotal > 0) {
53
+ lines.push(`Growth: +${fmt(growth)} since last nudge`);
54
+ }
55
+ lines.push("");
56
+ lines.push(`Sent to LLM (after compression, est.): ${fmt(sentTotal)}${limit > 0 ? ` (${sentPct}% of limit)` : ""}`);
57
+ if (input.unprunedTokens !== void 0 && sessionOnly > 0) {
58
+ lines.push(`Session-only (compressed originals, est.): ${fmt(sessionOnly)} \u2014 pruned from every request; the footer/nudge still count them`);
59
+ }
60
+ lines.push("");
61
+ lines.push("Token Breakdown (sent view):");
62
+ const categories = [
63
+ { label: "Tool", value: bd.tool },
64
+ { label: "SysPrompt", value: systemPromptTokens },
65
+ { label: "Text", value: bd.text },
66
+ { label: "Code", value: bd.code },
67
+ { label: "Summaries", value: bd.summaries }
68
+ ];
69
+ for (const cat of categories) {
70
+ if (cat.value <= 0) continue;
71
+ const pct = sentTotal > 0 ? Math.round(cat.value / sentTotal * 100) : 0;
72
+ const b = bar(cat.value, sentTotal);
73
+ lines.push(` ${cat.label.padEnd(10)} ${b} ${String(pct).padStart(3)}% ${fmt(cat.value)}`);
74
+ }
75
+ }
76
+ lines.push("");
77
+ if (nudge) {
78
+ if (nudge.shouldInject) {
79
+ const tierInfo = nudge.tier ? ` [T${nudge.tier} distillation]` : "";
80
+ lines.push(`Nudge: ACTIVE${tierInfo} \u2014 ${nudge.reason}`);
81
+ } else {
82
+ lines.push(`Nudge: idle \u2014 ${nudge.reason}`);
83
+ }
84
+ }
85
+ const ranges = viableRanges(nudge?.compressibleRanges ?? []);
86
+ const protectedRanges = nudge?.protectedRanges ?? [];
87
+ if (ranges.length > 0 || protectedRanges.length > 0) {
88
+ lines.push("");
89
+ lines.push(formatRanges(ranges, protectedRanges));
90
+ }
91
+ if (activeBlocksList.length > 0) {
92
+ lines.push("");
93
+ lines.push(`Blocks: ${activeBlocksList.length} active / ${totalBlocksList.length} total (${fmt(state.stats.tokensCompressed)} tokens compressed)`);
94
+ for (const b of activeBlocksList) {
95
+ const topic = b.topic ? `: ${b.topic}` : `: ${topicFallback(b.summary || "")}`;
96
+ const summaryTok = defaultCountTokens(b.summary || "");
97
+ const origTok = b.compressedTokens > 0 ? b.compressedTokens : summaryTok;
98
+ lines.push(` [${b.blockId}] T${b.tier} ${fmt(origTok)}\u2192${fmt(summaryTok)}${topic}`);
99
+ }
100
+ } else if (totalBlocksList.length > 0) {
101
+ lines.push("");
102
+ lines.push(`Blocks: 0 active / ${totalBlocksList.length} total (${fmt(state.stats.tokensCompressed)} tokens compressed)`);
103
+ } else {
104
+ lines.push("");
105
+ lines.push("Blocks: none (nothing compressed yet)");
106
+ }
107
+ lines.push("");
108
+ lines.push("Tag visibility: tags injected to LLM only (deep copy), not persisted in session, not shown in terminal.");
109
+ return lines.join("\n");
110
+ }
111
+ export {
112
+ buildStatusPanel,
113
+ formatCompactTokens,
114
+ topicFallback
115
+ };
116
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/panel/topic.ts","../../src/panel/format.ts","../../src/panel/panel.ts"],"sourcesContent":["/** Short topic for a block when the model did not provide one: first sentence\n * segment, leading quotes stripped, truncated to 30 chars with an ellipsis.\n * Used wherever block lists are rendered (panels, search results). */\nexport function topicFallback(summary: string): string {\n const first = summary.split(/[.\\n]/)[0] ?? \"\";\n const t = first.trim().replace(/^[\"'`]+/, \"\").trim();\n return t.length <= 30 ? t : `${t.slice(0, 30).trimEnd()}…`;\n}\n","/** Compact token formatting for user-facing panels: lowercase k/M with the\n * same thresholds as the hosts' footers (<1000 → raw, <10000 → one decimal\n * k, <1e6 → rounded k, <1e7 → one decimal M). */\nexport function formatCompactTokens(count: number): string {\n if (count < 1000) return count.toString();\n if (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n if (count < 1000000) return `${Math.round(count / 1000)}k`;\n if (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n return `${Math.round(count / 1000000)}M`;\n}\n","import { defaultCountTokens } from \"../tokenize.js\";\nimport { formatRanges } from \"../nudge-text.js\";\nimport type { CompressionState, NudgeDecision } from \"../types.js\";\nimport { formatCompactTokens } from \"./format.js\";\nimport { topicFallback } from \"./topic.js\";\nimport { viableRanges } from \"../viable.js\";\n\nexport interface StatusPanelInput {\n /** Adapter identifier for the header, e.g. \"billion-context-omp@0.1.6\".\n * Omit to hide the version line. */\n version?: string;\n /** Host session accounting — the SAME number the host footer displays.\n * It is the append-only session tree including compressed originals; it\n * never shrinks when compression prunes the per-request view. */\n tokenCount: number;\n /** Measured token count of the host system prompt (host-specific to\n * obtain; the kernel breakdown does not see it). */\n systemPromptTokens: number;\n /** Kernel state (blocks drive the Blocks section). */\n state: CompressionState;\n /** The nudge decision from core.processTurn for this turn, if any. The\n * panel applies the viability filter to compressibleRanges itself. */\n nudge: NudgeDecision | undefined;\n /** Configured model context window, in tokens. */\n modelContextLimit: number;\n /** chars/4 estimate of the FULL (unpruned) core-message projection — the\n * same estimation scale as the kernel breakdown. When provided, the\n * panel derives `Session-only` on that scale (unpruned − sent). Without\n * it the line is omitted: subtracting the host's provider-scale number\n * from an estimate-scale number invents a third, meaningless scale\n * (issue #18 \"看板统计的和拆分的有差异\"). */\n unprunedTokens?: number;\n /** Token formatter override (defaults to formatCompactTokens). */\n fmtTokens?: (n: number) => string;\n}\n\nfunction bar(value: number, total: number, width: number = 20): string {\n if (total === 0) return \"\";\n const filled = Math.max(0, Math.min(width, Math.round((value / total) * width)));\n return \"█\".repeat(filled) + \"░\".repeat(width - filled);\n}\n\n/** Render the /acp status panel. Three token numbers, each labeled with\n * its own scale, never mixed in arithmetic:\n * - Session accounting (host footer scale): the append-only session tree\n * INCLUDING compressed originals. It never shrinks — adapter pruning is\n * a per-request transform view the host cannot see.\n * - Sent view (chars/4 est.): what actually reaches the LLM after\n * compression (kernel's classification over the pruned projection +\n * measured system prompt). This is the number compression controls.\n * - Session-only (chars/4 est.): unpruned projection − sent view; the\n * compressed originals pruned from every request.\n * Subtracting the host number from an estimate produced numbers that\n * reconciled with neither scale (\"Framework 390K\", \"session-only 29k vs\n * 112k compressed\") — that is what issue #18 reported. */\nexport function buildStatusPanel(input: StatusPanelInput): string {\n const { tokenCount, state, nudge, modelContextLimit } = input;\n const fmt = input.fmtTokens ?? formatCompactTokens;\n const bd = nudge?.contextBreakdown;\n const limit = modelContextLimit;\n const classified = bd ? bd.system + bd.tool + bd.summaries + bd.code + bd.text : 0;\n const systemPromptTokens = input.systemPromptTokens;\n const sentTotal = classified + systemPromptTokens;\n // Same-scale derivation only: both sides are chars/4 estimates. The host\n // footer's tokenCount (provider-anchored, session-tree) is displayed as\n // its own line and never fed into an arithmetic difference with these.\n const sessionOnly = input.unprunedTokens !== undefined ? Math.max(0, input.unprunedTokens - sentTotal) : 0;\n const displayTotal = tokenCount;\n const displayPct = limit > 0 ? Math.round((displayTotal / limit) * 100) : 0;\n const sentPct = limit > 0 ? Math.round((sentTotal / limit) * 100) : 0;\n const activeBlocksList = state.blocks.filter((b) => b.active);\n const totalBlocksList = state.blocks;\n\n const lines: string[] = [];\n\n lines.push(\"╭─────────────────────────────────────────────╮\");\n lines.push(\"│ ACP Context Analysis │\");\n lines.push(\"╰─────────────────────────────────────────────╯\");\n if (input.version) lines.push(input.version);\n lines.push(\"\");\n lines.push(`Context (session accounting, host footer scale): ${displayPct}% (${fmt(displayTotal)} / ${fmt(limit)}) — never shrinks; includes compressed originals`);\n\n if (nudge && bd) {\n const growth = bd.growth;\n if (growth > 0 && displayTotal > 0) {\n lines.push(`Growth: +${fmt(growth)} since last nudge`);\n }\n lines.push(\"\");\n lines.push(`Sent to LLM (after compression, est.): ${fmt(sentTotal)}${limit > 0 ? ` (${sentPct}% of limit)` : \"\"}`);\n if (input.unprunedTokens !== undefined && sessionOnly > 0) {\n lines.push(`Session-only (compressed originals, est.): ${fmt(sessionOnly)} — pruned from every request; the footer/nudge still count them`);\n }\n lines.push(\"\");\n lines.push(\"Token Breakdown (sent view):\");\n\n const categories: Array<{ label: string; value: number }> = [\n { label: \"Tool\", value: bd.tool },\n { label: \"SysPrompt\", value: systemPromptTokens },\n { label: \"Text\", value: bd.text },\n { label: \"Code\", value: bd.code },\n { label: \"Summaries\", value: bd.summaries },\n ];\n\n for (const cat of categories) {\n if (cat.value <= 0) continue;\n const pct = sentTotal > 0 ? Math.round((cat.value / sentTotal) * 100) : 0;\n const b = bar(cat.value, sentTotal);\n lines.push(` ${cat.label.padEnd(10)} ${b} ${String(pct).padStart(3)}% ${fmt(cat.value)}`);\n }\n }\n\n lines.push(\"\");\n\n if (nudge) {\n if (nudge.shouldInject) {\n const tierInfo = nudge.tier ? ` [T${nudge.tier} distillation]` : \"\";\n lines.push(`Nudge: ACTIVE${tierInfo} — ${nudge.reason}`);\n } else {\n lines.push(`Nudge: idle — ${nudge.reason}`);\n }\n }\n\n const ranges = viableRanges(nudge?.compressibleRanges ?? []);\n const protectedRanges = nudge?.protectedRanges ?? [];\n if (ranges.length > 0 || protectedRanges.length > 0) {\n lines.push(\"\");\n lines.push(formatRanges(ranges, protectedRanges));\n }\n\n if (activeBlocksList.length > 0) {\n lines.push(\"\");\n lines.push(`Blocks: ${activeBlocksList.length} active / ${totalBlocksList.length} total (${fmt(state.stats.tokensCompressed)} tokens compressed)`);\n for (const b of activeBlocksList) {\n const topic = b.topic ? `: ${b.topic}` : `: ${topicFallback(b.summary || \"\")}`;\n const summaryTok = defaultCountTokens(b.summary || \"\");\n const origTok = b.compressedTokens > 0 ? b.compressedTokens : summaryTok;\n lines.push(` [${b.blockId}] T${b.tier} ${fmt(origTok)}→${fmt(summaryTok)}${topic}`);\n }\n } else if (totalBlocksList.length > 0) {\n lines.push(\"\");\n lines.push(`Blocks: 0 active / ${totalBlocksList.length} total (${fmt(state.stats.tokensCompressed)} tokens compressed)`);\n } else {\n lines.push(\"\");\n lines.push(\"Blocks: none (nothing compressed yet)\");\n }\n\n lines.push(\"\");\n lines.push(\"Tag visibility: tags injected to LLM only (deep copy), not persisted in session, not shown in terminal.\");\n\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,44 @@
1
+ import type { CompressionState, NudgeDecision } from "../types.js";
2
+ export interface StatusPanelInput {
3
+ /** Adapter identifier for the header, e.g. "billion-context-omp@0.1.6".
4
+ * Omit to hide the version line. */
5
+ version?: string;
6
+ /** Host session accounting — the SAME number the host footer displays.
7
+ * It is the append-only session tree including compressed originals; it
8
+ * never shrinks when compression prunes the per-request view. */
9
+ tokenCount: number;
10
+ /** Measured token count of the host system prompt (host-specific to
11
+ * obtain; the kernel breakdown does not see it). */
12
+ systemPromptTokens: number;
13
+ /** Kernel state (blocks drive the Blocks section). */
14
+ state: CompressionState;
15
+ /** The nudge decision from core.processTurn for this turn, if any. The
16
+ * panel applies the viability filter to compressibleRanges itself. */
17
+ nudge: NudgeDecision | undefined;
18
+ /** Configured model context window, in tokens. */
19
+ modelContextLimit: number;
20
+ /** chars/4 estimate of the FULL (unpruned) core-message projection — the
21
+ * same estimation scale as the kernel breakdown. When provided, the
22
+ * panel derives `Session-only` on that scale (unpruned − sent). Without
23
+ * it the line is omitted: subtracting the host's provider-scale number
24
+ * from an estimate-scale number invents a third, meaningless scale
25
+ * (issue #18 "看板统计的和拆分的有差异"). */
26
+ unprunedTokens?: number;
27
+ /** Token formatter override (defaults to formatCompactTokens). */
28
+ fmtTokens?: (n: number) => string;
29
+ }
30
+ /** Render the /acp status panel. Three token numbers, each labeled with
31
+ * its own scale, never mixed in arithmetic:
32
+ * - Session accounting (host footer scale): the append-only session tree
33
+ * INCLUDING compressed originals. It never shrinks — adapter pruning is
34
+ * a per-request transform view the host cannot see.
35
+ * - Sent view (chars/4 est.): what actually reaches the LLM after
36
+ * compression (kernel's classification over the pruned projection +
37
+ * measured system prompt). This is the number compression controls.
38
+ * - Session-only (chars/4 est.): unpruned projection − sent view; the
39
+ * compressed originals pruned from every request.
40
+ * Subtracting the host number from an estimate produced numbers that
41
+ * reconciled with neither scale ("Framework 390K", "session-only 29k vs
42
+ * 112k compressed") — that is what issue #18 reported. */
43
+ export declare function buildStatusPanel(input: StatusPanelInput): string;
44
+ //# sourceMappingURL=panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/panel/panel.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKnE,MAAM,WAAW,gBAAgB;IAC/B;yCACqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;sEAEkE;IAClE,UAAU,EAAE,MAAM,CAAC;IACnB;yDACqD;IACrD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sDAAsD;IACtD,KAAK,EAAE,gBAAgB,CAAC;IACxB;2EACuE;IACvE,KAAK,EAAE,aAAa,GAAG,SAAS,CAAC;IACjC,kDAAkD;IAClD,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;;;sCAKkC;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kEAAkE;IAClE,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;CACnC;AAQD;;;;;;;;;;;;2DAY2D;AAC3D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CA+FhE"}
@@ -0,0 +1,5 @@
1
+ /** Short topic for a block when the model did not provide one: first sentence
2
+ * segment, leading quotes stripped, truncated to 30 chars with an ellipsis.
3
+ * Used wherever block lists are rendered (panels, search results). */
4
+ export declare function topicFallback(summary: string): string;
5
+ //# sourceMappingURL=topic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topic.d.ts","sourceRoot":"","sources":["../../src/panel/topic.ts"],"names":[],"mappings":"AAAA;;uEAEuE;AACvE,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIrD"}
@@ -0,0 +1,13 @@
1
+ /** Minimum size for a compressible range to be worth recommending. Ranges
2
+ * below this are fragmented leftovers (a 16-token ack, a one-line tool
3
+ * result): the model cannot write a meaningful >=50-char summary for them,
4
+ * and a batched compress call that includes one gets atomically rejected
5
+ * (the kernel validates the whole batch). Observed in the wild: a 14-range
6
+ * recommendation list containing a 16-token range → every batch attempt
7
+ * failed with "Summary too short". Apply on every surface that recommends
8
+ * ranges: the injected nudge, acp_status, and the /acp panel. */
9
+ export declare const VIABLE_RANGE_MIN_TOKENS = 200;
10
+ export declare function viableRanges<T extends {
11
+ tokens: number;
12
+ }>(ranges: T[]): T[];
13
+ //# sourceMappingURL=viable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viable.d.ts","sourceRoot":"","sources":["../src/viable.ts"],"names":[],"mappings":"AAAA;;;;;;;kEAOkE;AAClE,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAE3C,wBAAgB,YAAY,CAAC,CAAC,SAAS;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAE3E"}
@@ -5,5 +5,6 @@ export * from "./formats.js";
5
5
  export * from "./bili-message.js";
6
6
  export * from "./message-id.js";
7
7
  export * from "./demoted-thinking.js";
8
+ export * from "./mirror.js";
8
9
  export * from "./util.js";
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/wire/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/wire/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC"}
@@ -775,6 +775,145 @@ function detectWireFormat(payload) {
775
775
  }
776
776
  return "openai";
777
777
  }
778
+
779
+ // src/wire/mirror.ts
780
+ function textBlocks(blocks) {
781
+ const out = [];
782
+ for (const b of blocks ?? []) if (b.type === "text") out.push(b.text);
783
+ return out;
784
+ }
785
+ function joinText(blocks) {
786
+ return textBlocks(blocks).join("\n");
787
+ }
788
+ function thinkingText(blocks) {
789
+ return blocks?.filter((b) => b.type === "thinking" && b.thinking.trim().length > 0).map((b) => b.thinking).join("\n") ?? "";
790
+ }
791
+ function mirrorOpenaiMessages(view, systemText) {
792
+ const messages = [{ role: "system", content: systemText }];
793
+ for (const message of view) {
794
+ if (message.role === "user") {
795
+ const text = joinText(message.blocks);
796
+ if (text) messages.push({ role: "user", content: text });
797
+ } else if (message.role === "assistant") {
798
+ const calls = (message.blocks ?? []).filter((b) => b.type === "toolCall");
799
+ const reasoning = thinkingText(message.blocks);
800
+ const text = joinText(message.blocks);
801
+ if (calls.length > 0) {
802
+ messages.push({
803
+ role: "assistant",
804
+ content: text,
805
+ ...reasoning ? { reasoning_content: reasoning } : {},
806
+ tool_calls: calls.map((c) => ({
807
+ id: c.id,
808
+ type: "function",
809
+ function: { name: c.name ?? "", arguments: JSON.stringify(c.arguments ?? {}) }
810
+ }))
811
+ });
812
+ } else if (text || reasoning) {
813
+ messages.push({ role: "assistant", content: text, ...reasoning ? { reasoning_content: reasoning } : {} });
814
+ }
815
+ } else if (message.role === "toolResult") {
816
+ messages.push({ role: "tool", tool_call_id: message.toolCallId ?? "", content: joinText(message.blocks) });
817
+ } else {
818
+ const text = message.text ?? "";
819
+ if (text) messages.push({ role: "developer", content: text });
820
+ }
821
+ }
822
+ return messages;
823
+ }
824
+ function mirrorAnthropicMessages(view) {
825
+ const messages = [];
826
+ for (const message of view) {
827
+ if (message.role === "user") {
828
+ const text = joinText(message.blocks);
829
+ if (text) messages.push({ role: "user", content: [{ type: "text", text }] });
830
+ } else if (message.role === "assistant") {
831
+ const content = [];
832
+ for (const b of message.blocks ?? []) {
833
+ if (b.type === "thinking" && b.thinking.trim().length > 0) {
834
+ content.push({
835
+ type: "thinking",
836
+ thinking: b.thinking,
837
+ ...typeof b.signature === "string" && b.signature ? { signature: b.signature } : {}
838
+ });
839
+ } else if (b.type === "text" && b.text.trim().length > 0) {
840
+ content.push({ type: "text", text: b.text });
841
+ } else if (b.type === "toolCall") {
842
+ let input = {};
843
+ try {
844
+ input = b.arguments && typeof b.arguments === "object" ? b.arguments : JSON.parse(JSON.stringify(b.arguments ?? {}));
845
+ } catch {
846
+ input = {};
847
+ }
848
+ content.push({ type: "tool_use", id: b.id, name: b.name ?? "", input });
849
+ }
850
+ }
851
+ if (content.length > 0) messages.push({ role: "assistant", content });
852
+ } else if (message.role === "toolResult") {
853
+ messages.push({
854
+ role: "user",
855
+ content: [{ type: "tool_result", tool_use_id: message.toolCallId ?? "", content: joinText(message.blocks) }]
856
+ });
857
+ } else {
858
+ const text = message.text ?? "";
859
+ if (text) messages.push({ role: "user", content: [{ type: "text", text }] });
860
+ }
861
+ }
862
+ return messages;
863
+ }
864
+ function mirrorResponsesInput(view) {
865
+ const input = [];
866
+ for (const message of view) {
867
+ if (message.role === "user") {
868
+ const text = joinText(message.blocks);
869
+ if (text) input.push({ type: "message", role: "user", content: [{ type: "input_text", text }] });
870
+ } else if (message.role === "assistant") {
871
+ for (const b of message.blocks ?? []) {
872
+ if (b.type === "thinking" && b.thinking.trim().length > 0) {
873
+ input.push({ type: "reasoning", summary: [{ type: "summary_text", text: b.thinking }] });
874
+ } else if (b.type === "text" && b.text.trim().length > 0) {
875
+ input.push({ type: "message", role: "assistant", content: [{ type: "output_text", text: b.text }] });
876
+ } else if (b.type === "toolCall") {
877
+ let args = "{}";
878
+ try {
879
+ args = JSON.stringify(b.arguments ?? {});
880
+ } catch {
881
+ args = "{}";
882
+ }
883
+ input.push({ type: "function_call", call_id: b.id ?? "", name: b.name ?? "", arguments: args });
884
+ }
885
+ }
886
+ } else if (message.role === "toolResult") {
887
+ input.push({ type: "function_call_output", call_id: message.toolCallId ?? "", output: joinText(message.blocks) });
888
+ } else {
889
+ const text = message.text ?? "";
890
+ if (text) input.push({ type: "message", role: "user", content: [{ type: "input_text", text }] });
891
+ }
892
+ }
893
+ return input;
894
+ }
895
+ function mirrorOpenaiToCore(view, systemText) {
896
+ const { msgs } = openaiToCore({
897
+ model: "prime-fold",
898
+ messages: mirrorOpenaiMessages(view, systemText)
899
+ });
900
+ return msgs;
901
+ }
902
+ function mirrorAnthropicToCore(view) {
903
+ const { msgs } = anthropicToCore({
904
+ model: "prime-fold",
905
+ messages: mirrorAnthropicMessages(view)
906
+ });
907
+ return msgs;
908
+ }
909
+ function mirrorResponsesToCore(view, systemText) {
910
+ const { msgs } = responsesToCore({
911
+ model: "prime-fold",
912
+ instructions: systemText,
913
+ input: mirrorResponsesInput(view)
914
+ });
915
+ return msgs;
916
+ }
778
917
  export {
779
918
  ClusterCounter,
780
919
  WIRE_FORMATS,
@@ -795,6 +934,12 @@ export {
795
934
  injectOpenaiSystem,
796
935
  injectResponsesDeveloperMessage,
797
936
  isWireFormat,
937
+ mirrorAnthropicMessages,
938
+ mirrorAnthropicToCore,
939
+ mirrorOpenaiMessages,
940
+ mirrorOpenaiToCore,
941
+ mirrorResponsesInput,
942
+ mirrorResponsesToCore,
798
943
  openaiToCore,
799
944
  parseDataUrl,
800
945
  patchResponsesInput,