qlogicagent 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.js +1 -0
- package/dist/cli.js +9 -0
- package/dist/contracts.js +1 -0
- package/dist/index.js +5 -15
- package/dist/orchestration.js +118 -0
- package/package.json +56 -42
- package/dist/agent/agent.js +0 -113
- package/dist/agent/tool-loop.js +0 -575
- package/dist/agent/types.js +0 -14
- package/dist/cli/main.js +0 -23
- package/dist/cli/stdio-server.js +0 -463
- package/dist/config/config.js +0 -21
- package/dist/contracts/hooks.js +0 -7
- package/dist/contracts/index.js +0 -10
- package/dist/contracts/planner.js +0 -2
- package/dist/contracts/skill-candidate.js +0 -195
- package/dist/contracts/todo.js +0 -9
- package/dist/llm/builtin-providers.js +0 -531
- package/dist/llm/index.js +0 -14
- package/dist/llm/llm-client.js +0 -67
- package/dist/llm/model-catalog.js +0 -191
- package/dist/llm/provider-def.js +0 -12
- package/dist/llm/provider-registry.js +0 -147
- package/dist/llm/transport.js +0 -27
- package/dist/llm/transports/anthropic-messages.js +0 -293
- package/dist/llm/transports/openai-chat.js +0 -165
- package/dist/orchestration/agent-registry.js +0 -116
- package/dist/orchestration/approval-aware-tool-plan.js +0 -87
- package/dist/orchestration/context-compression.js +0 -583
- package/dist/orchestration/conversation-repair.js +0 -429
- package/dist/orchestration/curator-scheduler.js +0 -135
- package/dist/orchestration/embedded-failover-policy.js +0 -168
- package/dist/orchestration/error-classification.js +0 -77
- package/dist/orchestration/failover-classification.js +0 -381
- package/dist/orchestration/failover-error.js +0 -198
- package/dist/orchestration/fork-subagent.js +0 -98
- package/dist/orchestration/index.js +0 -267
- package/dist/orchestration/memory-flush-policy.js +0 -85
- package/dist/orchestration/memory-provider.js +0 -2
- package/dist/orchestration/parallel-tool-calls.js +0 -59
- package/dist/orchestration/prompt-cache-strategy.js +0 -228
- package/dist/orchestration/reactive-compact.js +0 -78
- package/dist/orchestration/retry-loop.js +0 -24
- package/dist/orchestration/skill-candidate.js +0 -141
- package/dist/orchestration/skill-consolidation.js +0 -220
- package/dist/orchestration/skill-improvement.js +0 -66
- package/dist/orchestration/skill-similarity.js +0 -131
- package/dist/orchestration/streaming-tool-executor.js +0 -96
- package/dist/orchestration/team-orchestration.js +0 -369
- package/dist/orchestration/team-tool-loop-wiring.js +0 -147
- package/dist/orchestration/tool-choice-policy.js +0 -164
- package/dist/orchestration/tool-loop-state.js +0 -133
- package/dist/orchestration/tool-schema.js +0 -297
- package/dist/orchestration/transcript-repair.js +0 -426
- package/dist/orchestration/turn-loop-guard.js +0 -92
- package/dist/orchestration/web-browser-policy.js +0 -39
- package/dist/runtime/context-compression.js +0 -274
- package/dist/runtime/hook-registry.js +0 -53
- package/dist/runtime/memory-hooks.js +0 -65
- package/dist/runtime/tool-eligibility.js +0 -111
- package/dist/skills/index.js +0 -82
- package/dist/skills/memory-extractor.js +0 -173
- package/dist/skills/memory-query-tool.js +0 -127
- package/dist/skills/memory-store.js +0 -228
- package/dist/skills/memory-tool.js +0 -192
- package/dist/skills/portable-tool.js +0 -14
- package/dist/skills/qmemory-adapter.js +0 -165
- package/dist/skills/skill-frontmatter.js +0 -344
- package/dist/skills/skill-guard.js +0 -229
- package/dist/skills/skill-loader.js +0 -303
- package/dist/skills/skill-source.js +0 -126
- package/dist/skills/skill-types.js +0 -6
- package/dist/skills/think-tool.js +0 -59
- package/dist/skills/todo-tool.js +0 -114
- package/dist/skills/tools/agent-tool.js +0 -142
- package/dist/skills/tools/apply-patch-tool.js +0 -184
- package/dist/skills/tools/ask-user-tool.js +0 -121
- package/dist/skills/tools/brief-tool.js +0 -95
- package/dist/skills/tools/browser-tool.js +0 -155
- package/dist/skills/tools/checkpoint-tool.js +0 -102
- package/dist/skills/tools/config-tool.js +0 -143
- package/dist/skills/tools/cron-tool.js +0 -175
- package/dist/skills/tools/edit-tool.js +0 -70
- package/dist/skills/tools/exec-tool.js +0 -133
- package/dist/skills/tools/image-generate-tool.js +0 -67
- package/dist/skills/tools/instructions-tool.js +0 -187
- package/dist/skills/tools/lsp-tool.js +0 -227
- package/dist/skills/tools/mcp-client-types.js +0 -53
- package/dist/skills/tools/mcp-tool.js +0 -503
- package/dist/skills/tools/memory-tool.js +0 -88
- package/dist/skills/tools/monitor-tool.js +0 -131
- package/dist/skills/tools/music-generate-tool.js +0 -62
- package/dist/skills/tools/notify-tool.js +0 -62
- package/dist/skills/tools/patch-tool.js +0 -505
- package/dist/skills/tools/pdf-tool.js +0 -88
- package/dist/skills/tools/plan-mode-tool.js +0 -122
- package/dist/skills/tools/read-tool.js +0 -84
- package/dist/skills/tools/repl-tool.js +0 -69
- package/dist/skills/tools/search-tool.js +0 -225
- package/dist/skills/tools/send-message-tool.js +0 -76
- package/dist/skills/tools/skill-list-tool.js +0 -54
- package/dist/skills/tools/skill-manage-tool.js +0 -153
- package/dist/skills/tools/skill-view-tool.js +0 -72
- package/dist/skills/tools/sleep-tool.js +0 -81
- package/dist/skills/tools/structured-output-tool.js +0 -176
- package/dist/skills/tools/task-tool.js +0 -161
- package/dist/skills/tools/team-tool.js +0 -105
- package/dist/skills/tools/tool-search-tool.js +0 -110
- package/dist/skills/tools/tts-tool.js +0 -45
- package/dist/skills/tools/video-edit-tool.js +0 -74
- package/dist/skills/tools/video-generate-tool.js +0 -66
- package/dist/skills/tools/video-merge-tool.js +0 -92
- package/dist/skills/tools/video-upscale-tool.js +0 -52
- package/dist/skills/tools/web-fetch-tool.js +0 -92
- package/dist/skills/tools/web-search-tool.js +0 -86
- package/dist/skills/tools/worktree-tool.js +0 -147
- package/dist/skills/tools/write-tool.js +0 -81
- /package/dist/{agent → types/agent}/agent.d.ts +0 -0
- /package/dist/{agent → types/agent}/tool-loop.d.ts +0 -0
- /package/dist/{agent → types/agent}/types.d.ts +0 -0
- /package/dist/{cli → types/cli}/main.d.ts +0 -0
- /package/dist/{cli → types/cli}/stdio-server.d.ts +0 -0
- /package/dist/{config → types/config}/config.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/hooks.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/index.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/planner.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/skill-candidate.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/todo.d.ts +0 -0
- /package/dist/{index.d.ts → types/index.d.ts} +0 -0
- /package/dist/{llm → types/llm}/builtin-providers.d.ts +0 -0
- /package/dist/{llm → types/llm}/index.d.ts +0 -0
- /package/dist/{llm → types/llm}/llm-client.d.ts +0 -0
- /package/dist/{llm → types/llm}/model-catalog.d.ts +0 -0
- /package/dist/{llm → types/llm}/provider-def.d.ts +0 -0
- /package/dist/{llm → types/llm}/provider-registry.d.ts +0 -0
- /package/dist/{llm → types/llm}/transport.d.ts +0 -0
- /package/dist/{llm → types/llm}/transports/anthropic-messages.d.ts +0 -0
- /package/dist/{llm → types/llm}/transports/openai-chat.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/agent-registry.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/approval-aware-tool-plan.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/context-compression.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/conversation-repair.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/curator-scheduler.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/embedded-failover-policy.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/error-classification.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/failover-classification.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/failover-error.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/fork-subagent.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/index.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/memory-flush-policy.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/memory-provider.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/parallel-tool-calls.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/prompt-cache-strategy.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/reactive-compact.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/retry-loop.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/skill-candidate.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/skill-consolidation.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/skill-improvement.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/skill-similarity.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/streaming-tool-executor.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/team-orchestration.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/team-tool-loop-wiring.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/tool-choice-policy.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/tool-loop-state.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/tool-schema.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/transcript-repair.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/turn-loop-guard.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/web-browser-policy.d.ts +0 -0
- /package/dist/{runtime → types/runtime}/context-compression.d.ts +0 -0
- /package/dist/{runtime → types/runtime}/hook-registry.d.ts +0 -0
- /package/dist/{runtime → types/runtime}/memory-hooks.d.ts +0 -0
- /package/dist/{runtime → types/runtime}/tool-eligibility.d.ts +0 -0
- /package/dist/{skills → types/skills}/index.d.ts +0 -0
- /package/dist/{skills → types/skills}/memory-extractor.d.ts +0 -0
- /package/dist/{skills → types/skills}/memory-query-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/memory-store.d.ts +0 -0
- /package/dist/{skills → types/skills}/memory-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/portable-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/qmemory-adapter.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-frontmatter.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-guard.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-loader.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-source.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-types.d.ts +0 -0
- /package/dist/{skills → types/skills}/think-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/todo-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/agent-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/apply-patch-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/ask-user-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/brief-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/browser-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/checkpoint-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/config-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/cron-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/edit-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/exec-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/image-generate-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/instructions-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/lsp-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/mcp-client-types.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/mcp-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/memory-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/monitor-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/music-generate-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/notify-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/patch-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/pdf-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/plan-mode-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/read-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/repl-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/search-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/send-message-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/skill-list-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/skill-manage-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/skill-view-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/sleep-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/structured-output-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/task-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/team-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/tool-search-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/tts-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/video-edit-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/video-generate-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/video-merge-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/video-upscale-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/web-fetch-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/web-search-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/worktree-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/write-tool.d.ts +0 -0
|
@@ -1,583 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Context compression strategies — reduce message history
|
|
3
|
-
// to fit within a token budget.
|
|
4
|
-
//
|
|
5
|
-
// Phase 1: ToolResultTrim + SlidingWindow (sync, no LLM)
|
|
6
|
-
// Phase 2: Structured LLM summarization + head/tail protection
|
|
7
|
-
// Phase 3: Prompt cache awareness
|
|
8
|
-
// Phase 4: Adaptive threshold + metrics + engine plugin
|
|
9
|
-
// ============================================================
|
|
10
|
-
export function isAsyncCompressionStrategy(s) {
|
|
11
|
-
return typeof s.compressAsync === "function";
|
|
12
|
-
}
|
|
13
|
-
// ── Sliding Window ──────────────────────────────────────────
|
|
14
|
-
// Keep the system prompt(s) + last N messages that fit the budget.
|
|
15
|
-
export class SlidingWindowStrategy {
|
|
16
|
-
estimateTokens;
|
|
17
|
-
constructor(estimateTokens) {
|
|
18
|
-
this.estimateTokens = estimateTokens;
|
|
19
|
-
}
|
|
20
|
-
compress(messages, budget) {
|
|
21
|
-
const systemMessages = [];
|
|
22
|
-
const nonSystem = [];
|
|
23
|
-
for (const msg of messages) {
|
|
24
|
-
if (msg.role === "system") {
|
|
25
|
-
systemMessages.push(msg);
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
nonSystem.push(msg);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
let remaining = budget;
|
|
32
|
-
for (const sys of systemMessages) {
|
|
33
|
-
remaining -= this.estimateTokens(sys);
|
|
34
|
-
}
|
|
35
|
-
if (remaining <= 0) {
|
|
36
|
-
return { messages: systemMessages, droppedCount: nonSystem.length, strategy: "sliding-window" };
|
|
37
|
-
}
|
|
38
|
-
const kept = [];
|
|
39
|
-
for (let i = nonSystem.length - 1; i >= 0; i--) {
|
|
40
|
-
const cost = this.estimateTokens(nonSystem[i]);
|
|
41
|
-
if (remaining - cost < 0)
|
|
42
|
-
break;
|
|
43
|
-
remaining -= cost;
|
|
44
|
-
kept.unshift(nonSystem[i]);
|
|
45
|
-
}
|
|
46
|
-
return {
|
|
47
|
-
messages: [...systemMessages, ...kept],
|
|
48
|
-
droppedCount: nonSystem.length - kept.length,
|
|
49
|
-
strategy: "sliding-window",
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
// ── Summarize Old ───────────────────────────────────────────
|
|
54
|
-
// Keep last `recentCount` messages as-is, replace older ones
|
|
55
|
-
// with a single system-role summary placeholder.
|
|
56
|
-
export class SummarizeOldStrategy {
|
|
57
|
-
recentCount;
|
|
58
|
-
summarize;
|
|
59
|
-
constructor(recentCount, summarize) {
|
|
60
|
-
this.recentCount = recentCount;
|
|
61
|
-
this.summarize = summarize;
|
|
62
|
-
}
|
|
63
|
-
compress(messages, _budget) {
|
|
64
|
-
const systemMessages = messages.filter((m) => m.role === "system");
|
|
65
|
-
const nonSystem = messages.filter((m) => m.role !== "system");
|
|
66
|
-
if (nonSystem.length <= this.recentCount) {
|
|
67
|
-
return { messages, droppedCount: 0, strategy: "summarize-old" };
|
|
68
|
-
}
|
|
69
|
-
const oldMessages = nonSystem.slice(0, nonSystem.length - this.recentCount);
|
|
70
|
-
const recentMessages = nonSystem.slice(nonSystem.length - this.recentCount);
|
|
71
|
-
const summary = this.summarize(oldMessages);
|
|
72
|
-
return {
|
|
73
|
-
messages: [
|
|
74
|
-
...systemMessages,
|
|
75
|
-
{ role: "system", content: `[Conversation summary]\n${summary}` },
|
|
76
|
-
...recentMessages,
|
|
77
|
-
],
|
|
78
|
-
droppedCount: oldMessages.length,
|
|
79
|
-
strategy: "summarize-old",
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
// ── Tool Result Trim ────────────────────────────────────────
|
|
84
|
-
// Truncate oversized tool results to a max character length.
|
|
85
|
-
export class ToolResultTrimStrategy {
|
|
86
|
-
maxToolResultChars;
|
|
87
|
-
constructor(maxToolResultChars = 8000) {
|
|
88
|
-
this.maxToolResultChars = maxToolResultChars;
|
|
89
|
-
}
|
|
90
|
-
compress(messages, _budget) {
|
|
91
|
-
let trimmedCount = 0;
|
|
92
|
-
const result = messages.map((msg) => {
|
|
93
|
-
if (msg.role !== "tool" || typeof msg.content !== "string")
|
|
94
|
-
return msg;
|
|
95
|
-
if (msg.content.length <= this.maxToolResultChars)
|
|
96
|
-
return msg;
|
|
97
|
-
trimmedCount++;
|
|
98
|
-
return {
|
|
99
|
-
...msg,
|
|
100
|
-
content: msg.content.slice(0, this.maxToolResultChars) + "\n[...truncated]",
|
|
101
|
-
};
|
|
102
|
-
});
|
|
103
|
-
return {
|
|
104
|
-
messages: result,
|
|
105
|
-
droppedCount: trimmedCount,
|
|
106
|
-
strategy: "tool-result-trim",
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
// ── Composite ───────────────────────────────────────────────
|
|
111
|
-
// Apply multiple strategies in sequence.
|
|
112
|
-
export function composeStrategies(...strategies) {
|
|
113
|
-
return {
|
|
114
|
-
compress(messages, budget) {
|
|
115
|
-
let current = messages;
|
|
116
|
-
let totalDropped = 0;
|
|
117
|
-
const names = [];
|
|
118
|
-
for (const strategy of strategies) {
|
|
119
|
-
const result = strategy.compress(current, budget);
|
|
120
|
-
current = result.messages;
|
|
121
|
-
totalDropped += result.droppedCount;
|
|
122
|
-
if (result.droppedCount > 0) {
|
|
123
|
-
names.push(result.strategy);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return {
|
|
127
|
-
messages: current,
|
|
128
|
-
droppedCount: totalDropped,
|
|
129
|
-
strategy: names.length > 0 ? names.join("+") : "none",
|
|
130
|
-
};
|
|
131
|
-
},
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Compose strategies with async support — if any strategy is async,
|
|
136
|
-
* the pipeline becomes async.
|
|
137
|
-
*/
|
|
138
|
-
export function composeAsyncStrategies(...strategies) {
|
|
139
|
-
return {
|
|
140
|
-
compress(messages, budget) {
|
|
141
|
-
// Sync fallback: skip async strategies
|
|
142
|
-
let current = messages;
|
|
143
|
-
let totalDropped = 0;
|
|
144
|
-
const names = [];
|
|
145
|
-
for (const strategy of strategies) {
|
|
146
|
-
const result = strategy.compress(current, budget);
|
|
147
|
-
current = result.messages;
|
|
148
|
-
totalDropped += result.droppedCount;
|
|
149
|
-
if (result.droppedCount > 0)
|
|
150
|
-
names.push(result.strategy);
|
|
151
|
-
}
|
|
152
|
-
return {
|
|
153
|
-
messages: current,
|
|
154
|
-
droppedCount: totalDropped,
|
|
155
|
-
strategy: names.length > 0 ? names.join("+") : "none",
|
|
156
|
-
};
|
|
157
|
-
},
|
|
158
|
-
async compressAsync(messages, budget) {
|
|
159
|
-
let current = messages;
|
|
160
|
-
let totalDropped = 0;
|
|
161
|
-
const names = [];
|
|
162
|
-
let totalLatency = 0;
|
|
163
|
-
let anyLlm = false;
|
|
164
|
-
let anyCacheInvalidated = false;
|
|
165
|
-
for (const strategy of strategies) {
|
|
166
|
-
const result = isAsyncCompressionStrategy(strategy)
|
|
167
|
-
? await strategy.compressAsync(current, budget)
|
|
168
|
-
: strategy.compress(current, budget);
|
|
169
|
-
current = result.messages;
|
|
170
|
-
totalDropped += result.droppedCount;
|
|
171
|
-
if (result.droppedCount > 0)
|
|
172
|
-
names.push(result.strategy);
|
|
173
|
-
if (result.metrics) {
|
|
174
|
-
totalLatency += result.metrics.latencyMs;
|
|
175
|
-
anyLlm = anyLlm || result.metrics.usedLlm;
|
|
176
|
-
anyCacheInvalidated = anyCacheInvalidated || !!result.metrics.cacheInvalidated;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return {
|
|
180
|
-
messages: current,
|
|
181
|
-
droppedCount: totalDropped,
|
|
182
|
-
strategy: names.length > 0 ? names.join("+") : "none",
|
|
183
|
-
metrics: totalLatency > 0 || anyLlm
|
|
184
|
-
? { tokensBefore: 0, tokensAfter: 0, compressionRatio: 0, latencyMs: totalLatency, usedLlm: anyLlm, cacheInvalidated: anyCacheInvalidated }
|
|
185
|
-
: undefined,
|
|
186
|
-
};
|
|
187
|
-
},
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
// ════════════════════════════════════════════════════════════
|
|
191
|
-
// Phase 2: LLM Summarization Strategies
|
|
192
|
-
// ════════════════════════════════════════════════════════════
|
|
193
|
-
// ── 2.1 Structured Summary Prompt (Claude Code style) ──────
|
|
194
|
-
/**
|
|
195
|
-
* Build the structured 9-section summary instruction for the LLM.
|
|
196
|
-
* Based on Claude Code's Full Compact mode, adapted for Hub.
|
|
197
|
-
*/
|
|
198
|
-
export function buildStructuredSummaryPrompt(messagesToSummarize, opts) {
|
|
199
|
-
const userMsgs = messagesToSummarize.filter((m) => m.role === "user");
|
|
200
|
-
const toolCalls = messagesToSummarize.filter((m) => m.tool_calls != null);
|
|
201
|
-
const toolResults = messagesToSummarize.filter((m) => m.role === "tool");
|
|
202
|
-
const sections = [
|
|
203
|
-
"You are a conversation summarizer. Produce a structured summary of the conversation history below.",
|
|
204
|
-
"",
|
|
205
|
-
"## Instructions",
|
|
206
|
-
"Analyze the conversation and produce a summary with these sections:",
|
|
207
|
-
"",
|
|
208
|
-
"### 1. Primary Objective",
|
|
209
|
-
"What is the user's main goal or task? State it in one sentence.",
|
|
210
|
-
"",
|
|
211
|
-
"### 2. Key Decisions Made",
|
|
212
|
-
"List the important decisions, choices, or conclusions reached during the conversation.",
|
|
213
|
-
"",
|
|
214
|
-
"### 3. Current Progress",
|
|
215
|
-
`Describe the current state. ${toolCalls.length > 0 ? `${toolCalls.length} tool calls and ${toolResults.length} tool results were exchanged.` : "No tools were used."}`,
|
|
216
|
-
"",
|
|
217
|
-
"### 4. Pending Tasks",
|
|
218
|
-
"List any tasks that are in-progress or planned but not yet completed.",
|
|
219
|
-
"",
|
|
220
|
-
"### 5. Important Context",
|
|
221
|
-
"Note any critical facts, constraints, or preferences the user mentioned that must be preserved.",
|
|
222
|
-
"",
|
|
223
|
-
"### 6. Error & Recovery History",
|
|
224
|
-
"Summarize any errors encountered and how they were resolved.",
|
|
225
|
-
"",
|
|
226
|
-
"### 7. User Preferences Expressed",
|
|
227
|
-
`The user sent ${userMsgs.length} messages. Note any stated preferences about style, approach, or constraints.`,
|
|
228
|
-
"",
|
|
229
|
-
"### 8. Technical State",
|
|
230
|
-
"Note file paths, variable names, API endpoints, or configuration values that were discussed.",
|
|
231
|
-
"",
|
|
232
|
-
"### 9. Conversation Flow",
|
|
233
|
-
"Briefly describe the overall flow: what happened first, what changed, where we are now.",
|
|
234
|
-
];
|
|
235
|
-
if (opts?.taskContext) {
|
|
236
|
-
sections.push("", `## Additional Context`, opts.taskContext);
|
|
237
|
-
}
|
|
238
|
-
sections.push("", "## Conversation to Summarize", "", ...messagesToSummarize.map((m) => {
|
|
239
|
-
const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content ?? "");
|
|
240
|
-
const truncated = content.length > 2000 ? content.slice(0, 2000) + "..." : content;
|
|
241
|
-
return `[${m.role}]: ${truncated}`;
|
|
242
|
-
}), "", "## Output Format", "Respond with a concise summary covering all 9 sections above. Use markdown headers.", "Keep the total summary under 800 words. Focus on actionable information.");
|
|
243
|
-
return sections.join("\n");
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Phase 2.2: Head/Tail protected summarization.
|
|
247
|
-
*
|
|
248
|
-
* Protects: system messages + first exchange + last N messages.
|
|
249
|
-
* Compresses: middle section via LLM summarization.
|
|
250
|
-
*/
|
|
251
|
-
export class HeadTailProtectedStrategy {
|
|
252
|
-
config;
|
|
253
|
-
constructor(config) {
|
|
254
|
-
this.config = {
|
|
255
|
-
protectedHeadExchanges: config.protectedHeadExchanges,
|
|
256
|
-
protectedTailMessages: config.protectedTailMessages,
|
|
257
|
-
summarize: config.summarize,
|
|
258
|
-
estimateTokens: config.estimateTokens ?? defaultEstimateTokens,
|
|
259
|
-
taskContext: config.taskContext,
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
compress(messages, _budget) {
|
|
263
|
-
// Sync fallback: just pass through (cannot call LLM synchronously)
|
|
264
|
-
return { messages, droppedCount: 0, strategy: "head-tail-protected" };
|
|
265
|
-
}
|
|
266
|
-
async compressAsync(messages, budget) {
|
|
267
|
-
const start = Date.now();
|
|
268
|
-
const { system, nonSystem } = splitSystemMessages(messages);
|
|
269
|
-
// Check if compression is needed
|
|
270
|
-
const totalTokens = messages.reduce((sum, m) => sum + this.config.estimateTokens(m), 0);
|
|
271
|
-
if (totalTokens <= budget) {
|
|
272
|
-
return { messages, droppedCount: 0, strategy: "head-tail-protected" };
|
|
273
|
-
}
|
|
274
|
-
// Identify protected head: first N user+assistant exchanges
|
|
275
|
-
let headEnd = 0;
|
|
276
|
-
let exchangeCount = 0;
|
|
277
|
-
for (let i = 0; i < nonSystem.length; i++) {
|
|
278
|
-
if (nonSystem[i].role === "user")
|
|
279
|
-
exchangeCount++;
|
|
280
|
-
if (exchangeCount > this.config.protectedHeadExchanges)
|
|
281
|
-
break;
|
|
282
|
-
headEnd = i + 1;
|
|
283
|
-
}
|
|
284
|
-
// Protected tail
|
|
285
|
-
const tailStart = Math.max(headEnd, nonSystem.length - this.config.protectedTailMessages);
|
|
286
|
-
// If there's nothing to compress in the middle, pass through
|
|
287
|
-
if (tailStart <= headEnd) {
|
|
288
|
-
return { messages, droppedCount: 0, strategy: "head-tail-protected" };
|
|
289
|
-
}
|
|
290
|
-
const headMessages = nonSystem.slice(0, headEnd);
|
|
291
|
-
const middleMessages = nonSystem.slice(headEnd, tailStart);
|
|
292
|
-
const tailMessages = nonSystem.slice(tailStart);
|
|
293
|
-
// Summarize middle section via LLM
|
|
294
|
-
const instruction = buildStructuredSummaryPrompt(middleMessages, {
|
|
295
|
-
taskContext: this.config.taskContext,
|
|
296
|
-
});
|
|
297
|
-
const summary = await this.config.summarize(middleMessages, instruction);
|
|
298
|
-
const summaryMessage = {
|
|
299
|
-
role: "system",
|
|
300
|
-
content: `[Conversation summary — ${middleMessages.length} messages compressed]\n\n${summary}`,
|
|
301
|
-
};
|
|
302
|
-
const resultMessages = [
|
|
303
|
-
...system,
|
|
304
|
-
...headMessages,
|
|
305
|
-
summaryMessage,
|
|
306
|
-
...tailMessages,
|
|
307
|
-
];
|
|
308
|
-
const latencyMs = Date.now() - start;
|
|
309
|
-
const tokensAfter = resultMessages.reduce((sum, m) => sum + this.config.estimateTokens(m), 0);
|
|
310
|
-
return {
|
|
311
|
-
messages: resultMessages,
|
|
312
|
-
droppedCount: middleMessages.length,
|
|
313
|
-
strategy: "head-tail-protected",
|
|
314
|
-
metrics: {
|
|
315
|
-
tokensBefore: totalTokens,
|
|
316
|
-
tokensAfter,
|
|
317
|
-
compressionRatio: totalTokens > 0 ? tokensAfter / totalTokens : 1,
|
|
318
|
-
latencyMs,
|
|
319
|
-
usedLlm: true,
|
|
320
|
-
cacheInvalidated: true,
|
|
321
|
-
},
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
/**
|
|
326
|
-
* Phase 2.3: Incremental (partial) compaction.
|
|
327
|
-
*
|
|
328
|
-
* Only summarizes the oldest messages beyond the preserve window.
|
|
329
|
-
* Avoids repeatedly re-summarizing already-compressed content.
|
|
330
|
-
* If a previous summary marker exists, only new old messages are compressed.
|
|
331
|
-
*/
|
|
332
|
-
export class IncrementalCompactStrategy {
|
|
333
|
-
config;
|
|
334
|
-
constructor(config) {
|
|
335
|
-
this.config = {
|
|
336
|
-
preserveRecentCount: config.preserveRecentCount,
|
|
337
|
-
summarize: config.summarize,
|
|
338
|
-
estimateTokens: config.estimateTokens ?? defaultEstimateTokens,
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
compress(messages, _budget) {
|
|
342
|
-
return { messages, droppedCount: 0, strategy: "incremental-compact" };
|
|
343
|
-
}
|
|
344
|
-
async compressAsync(messages, budget) {
|
|
345
|
-
const start = Date.now();
|
|
346
|
-
const { system, nonSystem } = splitSystemMessages(messages);
|
|
347
|
-
// Find existing summary marker
|
|
348
|
-
const existingSummaryIdx = system.findIndex((m) => typeof m.content === "string" && m.content.startsWith("[Conversation summary"));
|
|
349
|
-
const existingSummary = existingSummaryIdx >= 0 ? system[existingSummaryIdx] : undefined;
|
|
350
|
-
const systemWithoutOldSummary = existingSummaryIdx >= 0
|
|
351
|
-
? [...system.slice(0, existingSummaryIdx), ...system.slice(existingSummaryIdx + 1)]
|
|
352
|
-
: system;
|
|
353
|
-
// How many messages to preserve
|
|
354
|
-
const preserveStart = Math.max(0, nonSystem.length - this.config.preserveRecentCount);
|
|
355
|
-
if (preserveStart <= 0) {
|
|
356
|
-
return { messages, droppedCount: 0, strategy: "incremental-compact" };
|
|
357
|
-
}
|
|
358
|
-
const totalTokens = messages.reduce((sum, m) => sum + this.config.estimateTokens(m), 0);
|
|
359
|
-
if (totalTokens <= budget) {
|
|
360
|
-
return { messages, droppedCount: 0, strategy: "incremental-compact" };
|
|
361
|
-
}
|
|
362
|
-
const oldMessages = nonSystem.slice(0, preserveStart);
|
|
363
|
-
const recentMessages = nonSystem.slice(preserveStart);
|
|
364
|
-
// Build summary instruction including existing summary context
|
|
365
|
-
const contextPrefix = existingSummary && typeof existingSummary.content === "string"
|
|
366
|
-
? `Previous summary:\n${existingSummary.content}\n\nNew messages to integrate:`
|
|
367
|
-
: undefined;
|
|
368
|
-
const instruction = buildStructuredSummaryPrompt(oldMessages, { taskContext: contextPrefix });
|
|
369
|
-
const summary = await this.config.summarize(oldMessages, instruction);
|
|
370
|
-
const summaryMessage = {
|
|
371
|
-
role: "system",
|
|
372
|
-
content: `[Conversation summary — ${oldMessages.length} messages compressed]\n\n${summary}`,
|
|
373
|
-
};
|
|
374
|
-
const resultMessages = [...systemWithoutOldSummary, summaryMessage, ...recentMessages];
|
|
375
|
-
const latencyMs = Date.now() - start;
|
|
376
|
-
const tokensAfter = resultMessages.reduce((sum, m) => sum + this.config.estimateTokens(m), 0);
|
|
377
|
-
return {
|
|
378
|
-
messages: resultMessages,
|
|
379
|
-
droppedCount: oldMessages.length,
|
|
380
|
-
strategy: "incremental-compact",
|
|
381
|
-
metrics: {
|
|
382
|
-
tokensBefore: totalTokens,
|
|
383
|
-
tokensAfter,
|
|
384
|
-
compressionRatio: totalTokens > 0 ? tokensAfter / totalTokens : 1,
|
|
385
|
-
latencyMs,
|
|
386
|
-
usedLlm: true,
|
|
387
|
-
cacheInvalidated: true,
|
|
388
|
-
},
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
/**
|
|
393
|
-
* Phase 3: Cache-aware wrapper.
|
|
394
|
-
*
|
|
395
|
-
* Wraps any strategy and tracks whether compression invalidated the
|
|
396
|
-
* provider prompt cache. System prompt prefix stability is preserved
|
|
397
|
-
* by never modifying system[0] (the original system prompt).
|
|
398
|
-
*/
|
|
399
|
-
export class CacheAwareCompressionStrategy {
|
|
400
|
-
config;
|
|
401
|
-
constructor(config) {
|
|
402
|
-
this.config = config;
|
|
403
|
-
}
|
|
404
|
-
compress(messages, budget) {
|
|
405
|
-
const beforeHash = computeMessagePrefixHash(messages);
|
|
406
|
-
const result = this.config.inner.compress(messages, budget);
|
|
407
|
-
const afterHash = computeMessagePrefixHash(result.messages);
|
|
408
|
-
const cacheInvalidated = beforeHash !== afterHash && result.droppedCount > 0;
|
|
409
|
-
if (cacheInvalidated) {
|
|
410
|
-
this.config.onCacheInvalidated?.({ droppedCount: result.droppedCount, strategy: result.strategy });
|
|
411
|
-
}
|
|
412
|
-
return {
|
|
413
|
-
...result,
|
|
414
|
-
metrics: {
|
|
415
|
-
...(result.metrics ?? {
|
|
416
|
-
tokensBefore: 0, tokensAfter: 0, compressionRatio: 0, latencyMs: 0, usedLlm: false,
|
|
417
|
-
}),
|
|
418
|
-
cacheInvalidated,
|
|
419
|
-
},
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
async compressAsync(messages, budget) {
|
|
423
|
-
const beforeHash = computeMessagePrefixHash(messages);
|
|
424
|
-
const result = isAsyncCompressionStrategy(this.config.inner)
|
|
425
|
-
? await this.config.inner.compressAsync(messages, budget)
|
|
426
|
-
: this.config.inner.compress(messages, budget);
|
|
427
|
-
const afterHash = computeMessagePrefixHash(result.messages);
|
|
428
|
-
const cacheInvalidated = beforeHash !== afterHash && result.droppedCount > 0;
|
|
429
|
-
if (cacheInvalidated) {
|
|
430
|
-
this.config.onCacheInvalidated?.({ droppedCount: result.droppedCount, strategy: result.strategy });
|
|
431
|
-
}
|
|
432
|
-
return {
|
|
433
|
-
...result,
|
|
434
|
-
metrics: {
|
|
435
|
-
...(result.metrics ?? {
|
|
436
|
-
tokensBefore: 0, tokensAfter: 0, compressionRatio: 0, latencyMs: 0, usedLlm: false,
|
|
437
|
-
}),
|
|
438
|
-
cacheInvalidated,
|
|
439
|
-
},
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
export const DEFAULT_ADAPTIVE_BUDGET_CONFIG = {
|
|
444
|
-
modelContextWindow: 128_000,
|
|
445
|
-
targetUsageRatio: 0.75,
|
|
446
|
-
minBudget: 16_000,
|
|
447
|
-
maxBudget: 120_000,
|
|
448
|
-
};
|
|
449
|
-
/**
|
|
450
|
-
* Compute the adaptive token budget for a given model + message history.
|
|
451
|
-
*
|
|
452
|
-
* Adjusts based on model context window, and uses the target ratio
|
|
453
|
-
* so compression triggers before hitting the hard limit.
|
|
454
|
-
*/
|
|
455
|
-
export function computeAdaptiveBudget(config = {}) {
|
|
456
|
-
const c = { ...DEFAULT_ADAPTIVE_BUDGET_CONFIG, ...config };
|
|
457
|
-
const target = Math.floor(c.modelContextWindow * c.targetUsageRatio);
|
|
458
|
-
return Math.max(c.minBudget, Math.min(target, c.maxBudget));
|
|
459
|
-
}
|
|
460
|
-
export function selectCompressionTier(currentTokens, budget) {
|
|
461
|
-
const ratio = currentTokens / budget;
|
|
462
|
-
if (ratio <= 0.8)
|
|
463
|
-
return "none";
|
|
464
|
-
if (ratio <= 1.0)
|
|
465
|
-
return "trim-only";
|
|
466
|
-
if (ratio <= 1.5)
|
|
467
|
-
return "sliding-window";
|
|
468
|
-
return "llm-summarize";
|
|
469
|
-
}
|
|
470
|
-
export class CompressionMetricsCollector {
|
|
471
|
-
events = [];
|
|
472
|
-
maxEvents;
|
|
473
|
-
constructor(maxEvents = 100) {
|
|
474
|
-
this.maxEvents = maxEvents;
|
|
475
|
-
}
|
|
476
|
-
record(event) {
|
|
477
|
-
this.events.push(event);
|
|
478
|
-
if (this.events.length > this.maxEvents) {
|
|
479
|
-
this.events.shift();
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
snapshot() {
|
|
483
|
-
const total = this.events.length;
|
|
484
|
-
if (total === 0) {
|
|
485
|
-
return {
|
|
486
|
-
totalCompressions: 0,
|
|
487
|
-
totalLlmCalls: 0,
|
|
488
|
-
totalCacheInvalidations: 0,
|
|
489
|
-
averageCompressionRatio: 1,
|
|
490
|
-
averageLatencyMs: 0,
|
|
491
|
-
totalTokensSaved: 0,
|
|
492
|
-
recentEvents: [],
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
let sumRatio = 0;
|
|
496
|
-
let sumLatency = 0;
|
|
497
|
-
let tokensSaved = 0;
|
|
498
|
-
let llmCalls = 0;
|
|
499
|
-
let cacheInvalidations = 0;
|
|
500
|
-
for (const e of this.events) {
|
|
501
|
-
sumRatio += e.tokensBefore > 0 ? e.tokensAfter / e.tokensBefore : 1;
|
|
502
|
-
sumLatency += e.latencyMs;
|
|
503
|
-
tokensSaved += Math.max(0, e.tokensBefore - e.tokensAfter);
|
|
504
|
-
if (e.usedLlm)
|
|
505
|
-
llmCalls++;
|
|
506
|
-
if (e.cacheInvalidated)
|
|
507
|
-
cacheInvalidations++;
|
|
508
|
-
}
|
|
509
|
-
return {
|
|
510
|
-
totalCompressions: total,
|
|
511
|
-
totalLlmCalls: llmCalls,
|
|
512
|
-
totalCacheInvalidations: cacheInvalidations,
|
|
513
|
-
averageCompressionRatio: sumRatio / total,
|
|
514
|
-
averageLatencyMs: sumLatency / total,
|
|
515
|
-
totalTokensSaved: tokensSaved,
|
|
516
|
-
recentEvents: this.events.slice(-10),
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
reset() {
|
|
520
|
-
this.events.length = 0;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
/**
|
|
524
|
-
* Registry for context engines. Enforces single-active constraint.
|
|
525
|
-
*/
|
|
526
|
-
export class ContextEngineRegistry {
|
|
527
|
-
engines = new Map();
|
|
528
|
-
activeId;
|
|
529
|
-
register(engine) {
|
|
530
|
-
this.engines.set(engine.id, engine);
|
|
531
|
-
}
|
|
532
|
-
activate(id) {
|
|
533
|
-
if (!this.engines.has(id))
|
|
534
|
-
return false;
|
|
535
|
-
this.activeId = id;
|
|
536
|
-
return true;
|
|
537
|
-
}
|
|
538
|
-
getActive() {
|
|
539
|
-
return this.activeId ? this.engines.get(this.activeId) : undefined;
|
|
540
|
-
}
|
|
541
|
-
listEngines() {
|
|
542
|
-
return Array.from(this.engines.values()).map((e) => ({
|
|
543
|
-
id: e.id,
|
|
544
|
-
label: e.label,
|
|
545
|
-
active: e.id === this.activeId,
|
|
546
|
-
}));
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
// ── Helpers ─────────────────────────────────────────────────
|
|
550
|
-
function splitSystemMessages(messages) {
|
|
551
|
-
const system = [];
|
|
552
|
-
const nonSystem = [];
|
|
553
|
-
for (const msg of messages) {
|
|
554
|
-
if (msg.role === "system")
|
|
555
|
-
system.push(msg);
|
|
556
|
-
else
|
|
557
|
-
nonSystem.push(msg);
|
|
558
|
-
}
|
|
559
|
-
return { system, nonSystem };
|
|
560
|
-
}
|
|
561
|
-
function defaultEstimateTokens(msg) {
|
|
562
|
-
const text = typeof msg.content === "string"
|
|
563
|
-
? msg.content
|
|
564
|
-
: msg.content != null
|
|
565
|
-
? JSON.stringify(msg.content)
|
|
566
|
-
: "";
|
|
567
|
-
return Math.ceil(text.length / 4);
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* Simple hash of the first few messages' role+content prefix.
|
|
571
|
-
* Used to detect whether compression changed the prompt prefix
|
|
572
|
-
* (which would invalidate provider prompt caching).
|
|
573
|
-
*/
|
|
574
|
-
function computeMessagePrefixHash(messages) {
|
|
575
|
-
const prefixCount = Math.min(messages.length, 5);
|
|
576
|
-
const parts = [];
|
|
577
|
-
for (let i = 0; i < prefixCount; i++) {
|
|
578
|
-
const m = messages[i];
|
|
579
|
-
const content = typeof m.content === "string" ? m.content.slice(0, 200) : "";
|
|
580
|
-
parts.push(`${m.role}:${content}`);
|
|
581
|
-
}
|
|
582
|
-
return parts.join("|");
|
|
583
|
-
}
|