acp-kernel 0.0.35 → 0.0.37

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"}
@@ -227,11 +227,16 @@ function parseDataUrl(url) {
227
227
  // src/wire/openai.ts
228
228
  function openaiToCore(body) {
229
229
  const msgs = [];
230
+ const systemParts = [];
230
231
  const clusters = new ClusterCounter();
231
232
  for (const m of body.messages) {
232
233
  switch (m.role) {
233
234
  case "system":
234
235
  case "developer": {
236
+ if (msgs.length === 0) {
237
+ systemParts.push(stringContent(m.content));
238
+ break;
239
+ }
235
240
  const base = deriveMessageId(m.role, "text", stringContent(m.content));
236
241
  msgs.push({ id: clusters.next(base), role: "system", contentType: "text", text: stringContent(m.content), originalRole: m.role });
237
242
  break;
@@ -310,7 +315,7 @@ function openaiToCore(body) {
310
315
  }
311
316
  }
312
317
  }
313
- return { msgs };
318
+ return { msgs, systemText: systemParts.join("\n\n") };
314
319
  }
315
320
  function coreToOpenai(messages) {
316
321
  const out = [];