@earendil-works/pi-durable 0.99.1 → 0.99.2
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/README.md +435 -46
- package/dist/entries.d.ts +20 -2
- package/dist/entries.d.ts.map +1 -1
- package/dist/entries.js +14 -1
- package/dist/entries.js.map +1 -1
- package/dist/env/index.d.ts +13 -38
- package/dist/env/index.d.ts.map +1 -1
- package/dist/env/index.js +2 -0
- package/dist/env/index.js.map +1 -1
- package/dist/env/node.d.ts.map +1 -1
- package/dist/env/node.js +51 -58
- package/dist/env/node.js.map +1 -1
- package/dist/harness/compaction.d.ts +56 -0
- package/dist/harness/compaction.d.ts.map +1 -0
- package/dist/harness/compaction.js +385 -0
- package/dist/harness/compaction.js.map +1 -0
- package/dist/harness/config.d.ts +10 -1
- package/dist/harness/config.d.ts.map +1 -1
- package/dist/harness/config.js +6 -0
- package/dist/harness/config.js.map +1 -1
- package/dist/harness/context.d.ts +8 -0
- package/dist/harness/context.d.ts.map +1 -1
- package/dist/harness/context.js +32 -21
- package/dist/harness/context.js.map +1 -1
- package/dist/harness/events.d.ts +166 -0
- package/dist/harness/events.d.ts.map +1 -0
- package/dist/harness/events.js +319 -0
- package/dist/harness/events.js.map +1 -0
- package/dist/harness/generation.d.ts +37 -6
- package/dist/harness/generation.d.ts.map +1 -1
- package/dist/harness/generation.js +350 -72
- package/dist/harness/generation.js.map +1 -1
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +124 -3
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/inbox.d.ts +57 -0
- package/dist/harness/inbox.d.ts.map +1 -0
- package/dist/harness/inbox.js +99 -0
- package/dist/harness/inbox.js.map +1 -0
- package/dist/harness/json.d.ts +10 -0
- package/dist/harness/json.d.ts.map +1 -0
- package/dist/harness/json.js +33 -0
- package/dist/harness/json.js.map +1 -0
- package/dist/harness/live.d.ts +66 -7
- package/dist/harness/live.d.ts.map +1 -1
- package/dist/harness/live.js +71 -7
- package/dist/harness/live.js.map +1 -1
- package/dist/harness/output.d.ts +62 -0
- package/dist/harness/output.d.ts.map +1 -0
- package/dist/harness/output.js +261 -0
- package/dist/harness/output.js.map +1 -0
- package/dist/harness/prompt.d.ts +10 -5
- package/dist/harness/prompt.d.ts.map +1 -1
- package/dist/harness/prompt.js +66 -13
- package/dist/harness/prompt.js.map +1 -1
- package/dist/harness/registry.d.ts.map +1 -1
- package/dist/harness/registry.js +11 -2
- package/dist/harness/registry.js.map +1 -1
- package/dist/harness/scheduler.d.ts +37 -12
- package/dist/harness/scheduler.d.ts.map +1 -1
- package/dist/harness/scheduler.js +632 -72
- package/dist/harness/scheduler.js.map +1 -1
- package/dist/harness/submissions.d.ts +12 -7
- package/dist/harness/submissions.d.ts.map +1 -1
- package/dist/harness/submissions.js +72 -33
- package/dist/harness/submissions.js.map +1 -1
- package/dist/harness/tool.d.ts +36 -0
- package/dist/harness/tool.d.ts.map +1 -0
- package/dist/harness/tool.js +412 -0
- package/dist/harness/tool.js.map +1 -0
- package/dist/harness/types.d.ts +185 -11
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/usage.d.ts +18 -0
- package/dist/harness/usage.d.ts.map +1 -0
- package/dist/harness/usage.js +57 -0
- package/dist/harness/usage.js.map +1 -0
- package/dist/harness/view.d.ts +43 -0
- package/dist/harness/view.d.ts.map +1 -0
- package/dist/harness/view.js +194 -0
- package/dist/harness/view.js.map +1 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/session/observation.d.ts +18 -11
- package/dist/session/observation.d.ts.map +1 -1
- package/dist/session/observation.js +24 -13
- package/dist/session/observation.js.map +1 -1
- package/dist/session/session.d.ts +10 -1
- package/dist/session/session.d.ts.map +1 -1
- package/dist/session/session.js +18 -4
- package/dist/session/session.js.map +1 -1
- package/dist/session/transaction.d.ts +10 -1
- package/dist/session/transaction.d.ts.map +1 -1
- package/dist/session/transaction.js +83 -27
- package/dist/session/transaction.js.map +1 -1
- package/dist/storage/jsonl/storage.d.ts +22 -2
- package/dist/storage/jsonl/storage.d.ts.map +1 -1
- package/dist/storage/jsonl/storage.js +1 -1
- package/dist/storage/jsonl/storage.js.map +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/memory.js.map +1 -1
- package/dist/storage/sqlite/migrations.js +1 -1
- package/dist/storage/sqlite/migrations.js.map +1 -1
- package/dist/testing/storage-benchmark.d.ts.map +1 -1
- package/dist/testing/storage-benchmark.js +0 -1
- package/dist/testing/storage-benchmark.js.map +1 -1
- package/dist/testing/storage-conformance.d.ts.map +1 -1
- package/dist/testing/storage-conformance.js +34 -2
- package/dist/testing/storage-conformance.js.map +1 -1
- package/dist/tools/bash.d.ts +28 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +69 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/edit-diff.d.ts +89 -0
- package/dist/tools/edit-diff.d.ts.map +1 -0
- package/dist/tools/edit-diff.js +386 -0
- package/dist/tools/edit-diff.js.map +1 -0
- package/dist/tools/edit.d.ts +18 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +116 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/env.d.ts +5 -0
- package/dist/tools/env.d.ts.map +1 -0
- package/dist/tools/env.js +7 -0
- package/dist/tools/env.js.map +1 -0
- package/dist/tools/file-mutation-queue.d.ts +5 -0
- package/dist/tools/file-mutation-queue.d.ts.map +1 -0
- package/dist/tools/file-mutation-queue.js +46 -0
- package/dist/tools/file-mutation-queue.js.map +1 -0
- package/dist/tools/image.d.ts +2 -0
- package/dist/tools/image.d.ts.map +1 -0
- package/dist/tools/image.js +92 -0
- package/dist/tools/image.js.map +1 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +5 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/path-utils.d.ts +5 -0
- package/dist/tools/path-utils.d.ts.map +1 -0
- package/dist/tools/path-utils.js +26 -0
- package/dist/tools/path-utils.js.map +1 -0
- package/dist/tools/read.d.ts +17 -0
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +100 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/write.d.ts +10 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +30 -0
- package/dist/tools/write.js.map +1 -0
- package/dist/{env/utils/truncate.d.ts → truncate.d.ts} +1 -17
- package/dist/truncate.d.ts.map +1 -0
- package/dist/truncate.js +146 -0
- package/dist/truncate.js.map +1 -0
- package/dist/types.d.ts +86 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/docs/pico-v5-handoff.md +366 -86
- package/docs/pico-v5.md +1373 -283
- package/package.json +11 -3
- package/dist/env/utils/adaptive-publisher.d.ts +0 -24
- package/dist/env/utils/adaptive-publisher.d.ts.map +0 -1
- package/dist/env/utils/adaptive-publisher.js +0 -79
- package/dist/env/utils/adaptive-publisher.js.map +0 -1
- package/dist/env/utils/output-capture.d.ts +0 -31
- package/dist/env/utils/output-capture.d.ts.map +0 -1
- package/dist/env/utils/output-capture.js +0 -238
- package/dist/env/utils/output-capture.js.map +0 -1
- package/dist/env/utils/truncate.d.ts.map +0 -1
- package/dist/env/utils/truncate.js +0 -294
- package/dist/env/utils/truncate.js.map +0 -1
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { calculateContextTokens, estimateMessageTokens } from "@earendil-works/pi-ai/utils/estimate";
|
|
2
|
+
import { isRetryableAssistantError, retryDelayMs } from "@earendil-works/pi-ai/utils/retry";
|
|
3
|
+
import { CompactionEntry } from "../entries.js";
|
|
4
|
+
import { defineTask } from "../tasks.js";
|
|
5
|
+
import { ConversationConfig, DEFAULT_COMPACTION_POLICY, DEFAULT_RETRY_POLICY } from "./config.js";
|
|
6
|
+
import { orderToolResults } from "./context.js";
|
|
7
|
+
import { addCompactionStatus, compactionStatus, LiveDoc, removeCompactionStatus } from "./live.js";
|
|
8
|
+
import { admitSubmission } from "./submissions.js";
|
|
9
|
+
import { recordUsage } from "./usage.js";
|
|
10
|
+
/** Longest tool result text a serialized summary source keeps. */
|
|
11
|
+
const TOOL_RESULT_MAX_CHARS = 2000;
|
|
12
|
+
const SUMMARY_PREFIX = "The conversation history before this point was compacted into the following summary:\n\n<summary>\n";
|
|
13
|
+
const SUMMARY_SUFFIX = "\n</summary>";
|
|
14
|
+
const SUMMARIZATION_SYSTEM_PROMPT = `You are a context summarization assistant. Your task is to read a conversation between a user and an AI assistant, then produce a structured summary following the exact format specified.
|
|
15
|
+
|
|
16
|
+
Do NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.`;
|
|
17
|
+
const SUMMARIZATION_PROMPT = `The messages above are a conversation to summarize. Create a structured context checkpoint summary that another LLM will use to continue the work. If the conversation starts with an earlier summary, preserve its information and fold the newer messages into it.
|
|
18
|
+
|
|
19
|
+
Use this EXACT format:
|
|
20
|
+
|
|
21
|
+
## Goal
|
|
22
|
+
[What is the user trying to accomplish? Can be multiple items if the session covers different tasks.]
|
|
23
|
+
|
|
24
|
+
## Constraints & Preferences
|
|
25
|
+
- [Any constraints, preferences, or requirements mentioned by user]
|
|
26
|
+
- [Or "(none)" if none were mentioned]
|
|
27
|
+
|
|
28
|
+
## Progress
|
|
29
|
+
### Done
|
|
30
|
+
- [x] [Completed tasks/changes]
|
|
31
|
+
|
|
32
|
+
### In Progress
|
|
33
|
+
- [ ] [Current work]
|
|
34
|
+
|
|
35
|
+
### Blocked
|
|
36
|
+
- [Issues preventing progress, if any]
|
|
37
|
+
|
|
38
|
+
## Key Decisions
|
|
39
|
+
- **[Decision]**: [Brief rationale]
|
|
40
|
+
|
|
41
|
+
## Next Steps
|
|
42
|
+
1. [Ordered list of what should happen next]
|
|
43
|
+
|
|
44
|
+
## Critical Context
|
|
45
|
+
- [Any data, examples, or references needed to continue]
|
|
46
|
+
- [Or "(none)" if not applicable]
|
|
47
|
+
|
|
48
|
+
Keep each section concise. Preserve exact file paths, function names, and error messages.`;
|
|
49
|
+
/**
|
|
50
|
+
* Built-in compaction task (spec §8.7): select an old prefix of the model context, summarize it, and place a summary
|
|
51
|
+
* entry whose `head` is the first kept entry. A compaction the generation owns blocks it and appends directly; a
|
|
52
|
+
* conversation-owned one places its summary through a write submission.
|
|
53
|
+
*/
|
|
54
|
+
export const CompactionTask = defineTask({
|
|
55
|
+
name: "pi.compaction",
|
|
56
|
+
version: 1,
|
|
57
|
+
initial: () => ({ phase: "select" }),
|
|
58
|
+
phases: {
|
|
59
|
+
select: async (task, runtime, context) => {
|
|
60
|
+
const { conversationId } = runtime;
|
|
61
|
+
const config = (await runtime.snapshot(ConversationConfig, conversationId, context)) ??
|
|
62
|
+
ConversationConfig.definition.initial();
|
|
63
|
+
const ref = config.model;
|
|
64
|
+
const model = ref === undefined ? undefined : runtime.models.getModel(ref.provider, ref.modelId);
|
|
65
|
+
if (ref === undefined || model === undefined)
|
|
66
|
+
return failNoModel(runtime, ref, context);
|
|
67
|
+
const policy = config.compaction ?? DEFAULT_COMPACTION_POLICY;
|
|
68
|
+
const view = await runtime.context(conversationId, context);
|
|
69
|
+
const cut = selectCut(view, policy.keepRecentTokens);
|
|
70
|
+
if (cut === undefined)
|
|
71
|
+
return complete(runtime, context);
|
|
72
|
+
const firstKept = view.entries[cut].id;
|
|
73
|
+
const { reason, instructions } = task.input;
|
|
74
|
+
const compaction = {
|
|
75
|
+
reason,
|
|
76
|
+
entries: view.entries.slice(0, cut),
|
|
77
|
+
messages: summarizedMessages(view, cut),
|
|
78
|
+
firstKept,
|
|
79
|
+
...(instructions === undefined ? {} : { instructions }),
|
|
80
|
+
};
|
|
81
|
+
let decision;
|
|
82
|
+
await runtime.hooks.each("beforeCompact", async (hook) => {
|
|
83
|
+
if (decision === undefined)
|
|
84
|
+
decision = await hook(compaction, runtime, context);
|
|
85
|
+
});
|
|
86
|
+
if (decision !== undefined && "decline" in decision)
|
|
87
|
+
return complete(runtime, context);
|
|
88
|
+
if (decision !== undefined)
|
|
89
|
+
return place(runtime, firstKept, decision.summary, context);
|
|
90
|
+
const request = {
|
|
91
|
+
attempt: 1,
|
|
92
|
+
model: ref,
|
|
93
|
+
thinkingLevel: config.thinkingLevel,
|
|
94
|
+
streamOptions: config.streamOptions ?? {},
|
|
95
|
+
maxTokens: Math.min(Math.floor(0.8 * policy.reserveTokens), model.maxTokens > 0 ? model.maxTokens : Number.POSITIVE_INFINITY),
|
|
96
|
+
tail: view.entries.reduce((tail, entry) => (entry.id > tail ? entry.id : tail), firstKept),
|
|
97
|
+
firstKept,
|
|
98
|
+
};
|
|
99
|
+
await runtime.commit(() => ({ status: "running", checkpoint: { phase: "summarize", ...request } }), context);
|
|
100
|
+
},
|
|
101
|
+
summarize: async (task, runtime, context) => {
|
|
102
|
+
const { phase: _, ...request } = task.state.checkpoint;
|
|
103
|
+
const { model: ref, thinkingLevel, streamOptions, maxTokens, tail, firstKept, attempt } = request;
|
|
104
|
+
const model = runtime.models.getModel(ref.provider, ref.modelId);
|
|
105
|
+
if (model === undefined)
|
|
106
|
+
return failNoModel(runtime, ref, context);
|
|
107
|
+
// The context at `tail` is immutable, so this is the range `select` chose.
|
|
108
|
+
const view = await runtime.context(runtime.conversationId, context, tail);
|
|
109
|
+
const cut = view.entries.findIndex((entry) => entry.id === firstKept);
|
|
110
|
+
const now = runtime.now();
|
|
111
|
+
const messages = [
|
|
112
|
+
{ role: "system", content: SUMMARIZATION_SYSTEM_PROMPT, timestamp: now },
|
|
113
|
+
{
|
|
114
|
+
role: "user",
|
|
115
|
+
content: [{ type: "text", text: summaryPrompt(summarizedMessages(view, cut), task.input.instructions) }],
|
|
116
|
+
timestamp: now,
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
const { deferred: _deferred, ...forwarded } = streamOptions;
|
|
120
|
+
const options = {
|
|
121
|
+
...forwarded,
|
|
122
|
+
cacheRetention: "none",
|
|
123
|
+
maxTokens,
|
|
124
|
+
signal: runtime.signal,
|
|
125
|
+
...(thinkingLevel === "off" ? {} : { reasoning: thinkingLevel }),
|
|
126
|
+
};
|
|
127
|
+
const message = await runtime.models.completeSimple(model, { messages }, options);
|
|
128
|
+
// An abort mark or close: the abort invocation or the reopened task handles the committed state.
|
|
129
|
+
runtime.signal.throwIfAborted();
|
|
130
|
+
const summary = summaryText(message);
|
|
131
|
+
const policy = (await runtime.snapshot(ConversationConfig, runtime.conversationId, context))?.retry ??
|
|
132
|
+
DEFAULT_RETRY_POLICY;
|
|
133
|
+
const retry = message.stopReason === "error" &&
|
|
134
|
+
isRetryableAssistantError(message) &&
|
|
135
|
+
policy.enabled &&
|
|
136
|
+
attempt <= policy.maxRetries;
|
|
137
|
+
const until = retry ? runtime.now() + retryDelayMs(policy, attempt) : 0;
|
|
138
|
+
await runtime.commit(async (tx, current) => {
|
|
139
|
+
await recordUsage(tx, runtime.conversationId, "models", `${message.provider}/${message.model}`, message.usage);
|
|
140
|
+
const live = await tx.doc(LiveDoc, runtime.conversationId);
|
|
141
|
+
if (summary !== undefined)
|
|
142
|
+
return placeSummary(tx, runtime, current, live, firstKept, summary);
|
|
143
|
+
if (retry) {
|
|
144
|
+
const status = compactionStatus(live, runtime.taskId);
|
|
145
|
+
if (status !== undefined)
|
|
146
|
+
status.retry = { at: until, error: message.errorMessage ?? "" };
|
|
147
|
+
return { status: "running", checkpoint: { phase: "retry", ...request, until } };
|
|
148
|
+
}
|
|
149
|
+
removeCompactionStatus(live, runtime.taskId);
|
|
150
|
+
const text = summaryFailure(message);
|
|
151
|
+
return {
|
|
152
|
+
status: "terminal",
|
|
153
|
+
outcome: { status: "failed", error: { message: text, detail: { reason: "model_error" } } },
|
|
154
|
+
};
|
|
155
|
+
}, context);
|
|
156
|
+
},
|
|
157
|
+
retry: async (task, runtime, context) => {
|
|
158
|
+
const { phase: _, until, ...request } = task.state.checkpoint;
|
|
159
|
+
await runtime.sleep(until, context);
|
|
160
|
+
const attempt = request.attempt + 1;
|
|
161
|
+
await runtime.commit(async (tx) => {
|
|
162
|
+
const status = compactionStatus(await tx.doc(LiveDoc, runtime.conversationId), runtime.taskId);
|
|
163
|
+
if (status !== undefined) {
|
|
164
|
+
status.attempt = attempt;
|
|
165
|
+
delete status.retry;
|
|
166
|
+
}
|
|
167
|
+
return { status: "running", checkpoint: { phase: "summarize", ...request, attempt } };
|
|
168
|
+
}, context);
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
abort: async (_task, runtime, context) => {
|
|
172
|
+
await runtime.commit(async (tx) => {
|
|
173
|
+
removeCompactionStatus(await tx.doc(LiveDoc, runtime.conversationId), runtime.taskId);
|
|
174
|
+
return { status: "terminal", outcome: { status: "aborted" } };
|
|
175
|
+
}, context);
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
/**
|
|
179
|
+
* Create a compaction task with its status in this commit. `owner` is the generation that waits for it (a blocking
|
|
180
|
+
* compaction); without one it is conversation-owned, and `background` unless it is manual.
|
|
181
|
+
*/
|
|
182
|
+
export async function createCompaction(tx, conversationId, input, owner) {
|
|
183
|
+
const ownership = owner === undefined ? { kind: "conversation" } : { kind: "task", taskId: owner };
|
|
184
|
+
const background = owner === undefined && input.reason !== "manual";
|
|
185
|
+
const taskId = await tx.createTask(CompactionTask, input, { ownership, conversationId, background });
|
|
186
|
+
const status = { taskId, reason: input.reason, blocking: owner !== undefined, attempt: 1 };
|
|
187
|
+
addCompactionStatus(await tx.doc(LiveDoc, conversationId), status);
|
|
188
|
+
return taskId;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Index in `view.entries` of the first entry a summary keeps, or `undefined` when there is nothing to compact
|
|
192
|
+
* (spec §8.7). Walks back from the tail until `keepRecentTokens` are kept, then cuts at the first candidate at or after
|
|
193
|
+
* that entry: an entry whose contribution starts with a user or assistant message, never a tool result, and never a
|
|
194
|
+
* user entry that a result of the preceding assistant's calls still follows.
|
|
195
|
+
*/
|
|
196
|
+
export function selectCut(view, keepRecentTokens) {
|
|
197
|
+
const { contributions } = view;
|
|
198
|
+
const start = view.head === undefined ? 0 : 1;
|
|
199
|
+
const candidates = [];
|
|
200
|
+
for (let index = start; index < contributions.length; index++) {
|
|
201
|
+
if (isCandidate(contributions, index))
|
|
202
|
+
candidates.push(index);
|
|
203
|
+
}
|
|
204
|
+
let kept = 0;
|
|
205
|
+
let cut;
|
|
206
|
+
for (let index = contributions.length - 1; index >= start; index--) {
|
|
207
|
+
for (const message of contributions[index])
|
|
208
|
+
kept += estimateMessageTokens(message);
|
|
209
|
+
if (kept < keepRecentTokens)
|
|
210
|
+
continue;
|
|
211
|
+
cut = candidates.find((candidate) => candidate >= index) ?? candidates.at(-1);
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
if (cut === undefined)
|
|
215
|
+
return undefined;
|
|
216
|
+
for (let index = start; index < cut; index++)
|
|
217
|
+
if (contributions[index].length > 0)
|
|
218
|
+
return cut;
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
function isCandidate(contributions, index) {
|
|
222
|
+
const first = contributions[index][0];
|
|
223
|
+
if (first?.role === "assistant")
|
|
224
|
+
return true;
|
|
225
|
+
if (first?.role !== "user")
|
|
226
|
+
return false;
|
|
227
|
+
// A result of the preceding assistant's calls that follows this entry, before the next assistant, belongs before it.
|
|
228
|
+
let calls = new Set();
|
|
229
|
+
for (let before = index - 1; before >= 0; before--) {
|
|
230
|
+
const assistant = contributions[before].findLast((message) => message.role === "assistant");
|
|
231
|
+
if (assistant === undefined)
|
|
232
|
+
continue;
|
|
233
|
+
calls = new Set(assistant.content.flatMap((content) => (content.type === "toolCall" ? [content.id] : [])));
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
if (calls.size === 0)
|
|
237
|
+
return true;
|
|
238
|
+
for (let after = index; after < contributions.length; after++) {
|
|
239
|
+
for (const [position, message] of contributions[after].entries()) {
|
|
240
|
+
if (message.role === "assistant" && (after > index || position > 0))
|
|
241
|
+
return true;
|
|
242
|
+
if (message.role === "toolResult" && calls.has(message.toolCallId))
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
/** Model messages of the entries before `cut`: the head marker first, ordered like model context (spec §2.1). */
|
|
249
|
+
export function summarizedMessages(view, cut) {
|
|
250
|
+
return orderToolResults(view.contributions.slice(0, cut).flat());
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Size of a request over `view` followed by `extra` (spec §8.3): the usage of the newest assistant appended after the
|
|
254
|
+
* head marker, whose request included the marker, plus estimates of the messages after it; without one, estimates of
|
|
255
|
+
* every message.
|
|
256
|
+
*/
|
|
257
|
+
export function estimateContext(view, extra) {
|
|
258
|
+
let measured;
|
|
259
|
+
const after = view.head?.id ?? Number.NEGATIVE_INFINITY;
|
|
260
|
+
for (let index = view.entries.length - 1; index >= 0 && measured === undefined; index--) {
|
|
261
|
+
if (view.entries[index].id <= after)
|
|
262
|
+
continue;
|
|
263
|
+
measured = view.contributions[index].findLast((message) => message.role === "assistant" && calculateContextTokens(message.usage) > 0);
|
|
264
|
+
}
|
|
265
|
+
const from = measured === undefined ? 0 : view.messages.lastIndexOf(measured) + 1;
|
|
266
|
+
let tokens = measured === undefined ? 0 : calculateContextTokens(measured.usage);
|
|
267
|
+
for (const message of view.messages.slice(from))
|
|
268
|
+
tokens += estimateMessageTokens(message);
|
|
269
|
+
for (const message of extra)
|
|
270
|
+
tokens += estimateMessageTokens(message);
|
|
271
|
+
return tokens;
|
|
272
|
+
}
|
|
273
|
+
/** The summary of a clean `stop` with text and no tool call; anything else is not a summary. */
|
|
274
|
+
function summaryText(message) {
|
|
275
|
+
if (message.stopReason !== "stop" || message.content.some((content) => content.type === "toolCall"))
|
|
276
|
+
return undefined;
|
|
277
|
+
const text = message.content
|
|
278
|
+
.flatMap((content) => (content.type === "text" ? [content.text] : []))
|
|
279
|
+
.join("\n")
|
|
280
|
+
.trim();
|
|
281
|
+
return text.length === 0 ? undefined : text;
|
|
282
|
+
}
|
|
283
|
+
function summaryFailure(message) {
|
|
284
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") {
|
|
285
|
+
return `Summarization failed: ${message.errorMessage ?? message.stopReason}`;
|
|
286
|
+
}
|
|
287
|
+
if (message.stopReason === "length")
|
|
288
|
+
return "Summarization hit the token limit; the summary is incomplete";
|
|
289
|
+
if (message.content.some((content) => content.type === "toolCall"))
|
|
290
|
+
return "Summarization attempted to call a tool";
|
|
291
|
+
return "Summarization produced no text";
|
|
292
|
+
}
|
|
293
|
+
/** The summarizer's user message: the serialized conversation, the prompt, and any instructions. */
|
|
294
|
+
function summaryPrompt(messages, instructions) {
|
|
295
|
+
const focus = instructions === undefined ? "" : `\n\nAdditional focus: ${instructions}`;
|
|
296
|
+
return `<conversation>\n${serializeConversation(messages)}\n</conversation>\n\n${SUMMARIZATION_PROMPT}${focus}`;
|
|
297
|
+
}
|
|
298
|
+
/** Messages as plain text, so the summarizer reads a transcript instead of continuing it. System messages are omitted. */
|
|
299
|
+
export function serializeConversation(messages) {
|
|
300
|
+
const parts = [];
|
|
301
|
+
for (const message of messages) {
|
|
302
|
+
if (message.role === "user") {
|
|
303
|
+
const text = contentText(message.content);
|
|
304
|
+
if (text.length > 0)
|
|
305
|
+
parts.push(`[User]: ${text}`);
|
|
306
|
+
}
|
|
307
|
+
else if (message.role === "assistant") {
|
|
308
|
+
const thinking = message.content.flatMap((content) => (content.type === "thinking" ? [content.thinking] : []));
|
|
309
|
+
const text = message.content.flatMap((content) => (content.type === "text" ? [content.text] : []));
|
|
310
|
+
const calls = message.content.flatMap((content) => content.type === "toolCall"
|
|
311
|
+
? [
|
|
312
|
+
`${content.name}(${Object.entries(content.arguments)
|
|
313
|
+
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
|
|
314
|
+
.join(", ")})`,
|
|
315
|
+
]
|
|
316
|
+
: []);
|
|
317
|
+
if (thinking.length > 0)
|
|
318
|
+
parts.push(`[Assistant thinking]: ${thinking.join("\n")}`);
|
|
319
|
+
if (text.length > 0)
|
|
320
|
+
parts.push(`[Assistant]: ${text.join("\n")}`);
|
|
321
|
+
if (calls.length > 0)
|
|
322
|
+
parts.push(`[Assistant tool calls]: ${calls.join("; ")}`);
|
|
323
|
+
}
|
|
324
|
+
else if (message.role === "toolResult") {
|
|
325
|
+
const text = contentText(message.content);
|
|
326
|
+
if (text.length > 0)
|
|
327
|
+
parts.push(`[Tool result]: ${truncate(text, TOOL_RESULT_MAX_CHARS)}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return parts.join("\n\n");
|
|
331
|
+
}
|
|
332
|
+
function contentText(content) {
|
|
333
|
+
if (typeof content === "string")
|
|
334
|
+
return content;
|
|
335
|
+
return content
|
|
336
|
+
.flatMap((block) => (block.type === "text" && block.text !== undefined ? [block.text] : []))
|
|
337
|
+
.join("\n");
|
|
338
|
+
}
|
|
339
|
+
function truncate(text, maxChars) {
|
|
340
|
+
if (text.length <= maxChars)
|
|
341
|
+
return text;
|
|
342
|
+
return `${text.slice(0, maxChars)}\n\n[... ${text.length - maxChars} more characters truncated]`;
|
|
343
|
+
}
|
|
344
|
+
/** Place a summary supplied by a hook in its own commit. */
|
|
345
|
+
async function place(runtime, firstKept, summary, context) {
|
|
346
|
+
await runtime.commit(async (tx, current) => placeSummary(tx, runtime, current, await tx.doc(LiveDoc, runtime.conversationId), firstKept, summary), context);
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Place the summary entry and complete (spec §8.7). A blocking compaction, owned by its generation, appends it: the
|
|
350
|
+
* generation holds the run and waits. A conversation-owned one admits it as a write submission, placed at once when
|
|
351
|
+
* idle, otherwise at the next boundary, or settled `stale`.
|
|
352
|
+
* REMINDER: nothing else may append to a busy conversation, so every non-blocking summary goes through admission.
|
|
353
|
+
*/
|
|
354
|
+
async function placeSummary(tx, runtime, current, live, firstKept, summary) {
|
|
355
|
+
removeCompactionStatus(live, runtime.taskId);
|
|
356
|
+
const text = `${SUMMARY_PREFIX}${summary}${SUMMARY_SUFFIX}`;
|
|
357
|
+
const entry = {
|
|
358
|
+
kind: CompactionEntry.kind,
|
|
359
|
+
head: firstKept,
|
|
360
|
+
model: [{ role: "user", content: [{ type: "text", text }], timestamp: runtime.now() }],
|
|
361
|
+
data: { reason: current.input.reason },
|
|
362
|
+
};
|
|
363
|
+
const result = current.owner === undefined
|
|
364
|
+
? {
|
|
365
|
+
// The scheduler commits runtime changes through a Session transaction, which admission needs.
|
|
366
|
+
submissionId: await admitSubmission(tx, runtime.conversationId, { type: "write", requestId: `compaction:${runtime.taskId}`, entry }, runtime.now()),
|
|
367
|
+
}
|
|
368
|
+
: { entryId: (await tx.appendEntry(runtime.conversationId, entry)).id };
|
|
369
|
+
return { status: "terminal", outcome: { status: "completed", result } };
|
|
370
|
+
}
|
|
371
|
+
/** Remove the status and complete without a summary. */
|
|
372
|
+
async function complete(runtime, context) {
|
|
373
|
+
await runtime.commit(async (tx) => {
|
|
374
|
+
removeCompactionStatus(await tx.doc(LiveDoc, runtime.conversationId), runtime.taskId);
|
|
375
|
+
return { status: "terminal", outcome: { status: "completed", result: {} } };
|
|
376
|
+
}, context);
|
|
377
|
+
}
|
|
378
|
+
async function failNoModel(runtime, ref, context) {
|
|
379
|
+
const message = ref === undefined ? "No model is configured" : `Model ${ref.provider}/${ref.modelId} is not available`;
|
|
380
|
+
await runtime.commit(async (tx) => {
|
|
381
|
+
removeCompactionStatus(await tx.doc(LiveDoc, runtime.conversationId), runtime.taskId);
|
|
382
|
+
return { status: "terminal", outcome: { status: "failed", error: { message, detail: { reason: "no_model" } } } };
|
|
383
|
+
}, context);
|
|
384
|
+
}
|
|
385
|
+
//# sourceMappingURL=compaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compaction.js","sourceRoot":"","sources":["../../src/harness/compaction.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context, Draft } from \"@earendil-works/chord\";\nimport type { AssistantMessage, Message, ModelThinkingLevel, SimpleStreamOptions } from \"@earendil-works/pi-ai\";\nimport { calculateContextTokens, estimateMessageTokens } from \"@earendil-works/pi-ai/utils/estimate\";\nimport { isRetryableAssistantError, retryDelayMs } from \"@earendil-works/pi-ai/utils/retry\";\nimport { CompactionEntry } from \"../entries.ts\";\nimport type { Transaction } from \"../session/transaction.ts\";\nimport { defineTask } from \"../tasks.ts\";\nimport type {\n\tConversationId,\n\tEntryDraft,\n\tEntryId,\n\tNextTaskState,\n\tRunningTask,\n\tTaskId,\n\tTaskRuntime,\n\tTx,\n} from \"../types.ts\";\nimport { ConversationConfig, DEFAULT_COMPACTION_POLICY, DEFAULT_RETRY_POLICY } from \"./config.ts\";\nimport { orderToolResults } from \"./context.ts\";\nimport { addCompactionStatus, compactionStatus, LiveDoc, type LiveState, removeCompactionStatus } from \"./live.ts\";\nimport { admitSubmission } from \"./submissions.ts\";\nimport type {\n\tCompactionHooks,\n\tCompactionReason,\n\tCompactionResult,\n\tContextView,\n\tConversationStreamOptions,\n\tModelRef,\n} from \"./types.ts\";\nimport { recordUsage } from \"./usage.ts\";\n\nexport type CompactionInput = { reason: CompactionReason; instructions?: string };\n\n/** The pinned summarization request. */\nexport type SummaryRequest = {\n\tattempt: number;\n\tmodel: ModelRef;\n\tthinkingLevel: ModelThinkingLevel;\n\tstreamOptions: ConversationStreamOptions;\n\tmaxTokens: number;\n\t/** Newest entry of the context the range was selected from. */\n\ttail: EntryId;\n\t/** First entry kept verbatim; the summary's `head`. */\n\tfirstKept: EntryId;\n};\n\nexport type CompactionCheckpoint =\n\t| { phase: \"select\" }\n\t| ({ phase: \"summarize\" } & SummaryRequest)\n\t| ({ phase: \"retry\"; until: number } & SummaryRequest);\n\ntype Runtime = TaskRuntime<CompactionInput, CompactionCheckpoint, CompactionResult, CompactionHooks>;\ntype Next = NextTaskState<CompactionCheckpoint, CompactionResult>;\n\n/** Longest tool result text a serialized summary source keeps. */\nconst TOOL_RESULT_MAX_CHARS = 2000;\n\nconst SUMMARY_PREFIX =\n\t\"The conversation history before this point was compacted into the following summary:\\n\\n<summary>\\n\";\nconst SUMMARY_SUFFIX = \"\\n</summary>\";\n\nconst SUMMARIZATION_SYSTEM_PROMPT = `You are a context summarization assistant. Your task is to read a conversation between a user and an AI assistant, then produce a structured summary following the exact format specified.\n\nDo NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.`;\n\nconst SUMMARIZATION_PROMPT = `The messages above are a conversation to summarize. Create a structured context checkpoint summary that another LLM will use to continue the work. If the conversation starts with an earlier summary, preserve its information and fold the newer messages into it.\n\nUse this EXACT format:\n\n## Goal\n[What is the user trying to accomplish? Can be multiple items if the session covers different tasks.]\n\n## Constraints & Preferences\n- [Any constraints, preferences, or requirements mentioned by user]\n- [Or \"(none)\" if none were mentioned]\n\n## Progress\n### Done\n- [x] [Completed tasks/changes]\n\n### In Progress\n- [ ] [Current work]\n\n### Blocked\n- [Issues preventing progress, if any]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale]\n\n## Next Steps\n1. [Ordered list of what should happen next]\n\n## Critical Context\n- [Any data, examples, or references needed to continue]\n- [Or \"(none)\" if not applicable]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.`;\n\n/**\n * Built-in compaction task (spec §8.7): select an old prefix of the model context, summarize it, and place a summary\n * entry whose `head` is the first kept entry. A compaction the generation owns blocks it and appends directly; a\n * conversation-owned one places its summary through a write submission.\n */\nexport const CompactionTask = defineTask<CompactionInput, CompactionCheckpoint, CompactionResult, CompactionHooks>({\n\tname: \"pi.compaction\",\n\tversion: 1,\n\tinitial: () => ({ phase: \"select\" }),\n\tphases: {\n\t\tselect: async (task, runtime, context) => {\n\t\t\tconst { conversationId } = runtime;\n\t\t\tconst config =\n\t\t\t\t(await runtime.snapshot(ConversationConfig, conversationId, context)) ??\n\t\t\t\tConversationConfig.definition.initial();\n\t\t\tconst ref = config.model;\n\t\t\tconst model = ref === undefined ? undefined : runtime.models.getModel(ref.provider, ref.modelId);\n\t\t\tif (ref === undefined || model === undefined) return failNoModel(runtime, ref, context);\n\t\t\tconst policy = config.compaction ?? DEFAULT_COMPACTION_POLICY;\n\t\t\tconst view = await runtime.context(conversationId, context);\n\t\t\tconst cut = selectCut(view, policy.keepRecentTokens);\n\t\t\tif (cut === undefined) return complete(runtime, context);\n\t\t\tconst firstKept = view.entries[cut]!.id;\n\t\t\tconst { reason, instructions } = task.input;\n\t\t\tconst compaction = {\n\t\t\t\treason,\n\t\t\t\tentries: view.entries.slice(0, cut),\n\t\t\t\tmessages: summarizedMessages(view, cut),\n\t\t\t\tfirstKept,\n\t\t\t\t...(instructions === undefined ? {} : { instructions }),\n\t\t\t};\n\t\t\tlet decision: { readonly decline: true } | { readonly summary: string } | undefined;\n\t\t\tawait runtime.hooks.each(\"beforeCompact\", async (hook) => {\n\t\t\t\tif (decision === undefined) decision = await hook(compaction, runtime, context);\n\t\t\t});\n\t\t\tif (decision !== undefined && \"decline\" in decision) return complete(runtime, context);\n\t\t\tif (decision !== undefined) return place(runtime, firstKept, decision.summary, context);\n\t\t\tconst request: SummaryRequest = {\n\t\t\t\tattempt: 1,\n\t\t\t\tmodel: ref,\n\t\t\t\tthinkingLevel: config.thinkingLevel,\n\t\t\t\tstreamOptions: config.streamOptions ?? {},\n\t\t\t\tmaxTokens: Math.min(\n\t\t\t\t\tMath.floor(0.8 * policy.reserveTokens),\n\t\t\t\t\tmodel.maxTokens > 0 ? model.maxTokens : Number.POSITIVE_INFINITY,\n\t\t\t\t),\n\t\t\t\ttail: view.entries.reduce((tail, entry) => (entry.id > tail ? entry.id : tail), firstKept),\n\t\t\t\tfirstKept,\n\t\t\t};\n\t\t\tawait runtime.commit(() => ({ status: \"running\", checkpoint: { phase: \"summarize\", ...request } }), context);\n\t\t},\n\t\tsummarize: async (task, runtime, context) => {\n\t\t\tconst { phase: _, ...request } = task.state.checkpoint;\n\t\t\tconst { model: ref, thinkingLevel, streamOptions, maxTokens, tail, firstKept, attempt } = request;\n\t\t\tconst model = runtime.models.getModel(ref.provider, ref.modelId);\n\t\t\tif (model === undefined) return failNoModel(runtime, ref, context);\n\t\t\t// The context at `tail` is immutable, so this is the range `select` chose.\n\t\t\tconst view = await runtime.context(runtime.conversationId, context, tail);\n\t\t\tconst cut = view.entries.findIndex((entry) => entry.id === firstKept);\n\t\t\tconst now = runtime.now();\n\t\t\tconst messages: Message[] = [\n\t\t\t\t{ role: \"system\", content: SUMMARIZATION_SYSTEM_PROMPT, timestamp: now },\n\t\t\t\t{\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: [{ type: \"text\", text: summaryPrompt(summarizedMessages(view, cut), task.input.instructions) }],\n\t\t\t\t\ttimestamp: now,\n\t\t\t\t},\n\t\t\t];\n\t\t\tconst { deferred: _deferred, ...forwarded } = streamOptions;\n\t\t\tconst options: SimpleStreamOptions = {\n\t\t\t\t...forwarded,\n\t\t\t\tcacheRetention: \"none\",\n\t\t\t\tmaxTokens,\n\t\t\t\tsignal: runtime.signal,\n\t\t\t\t...(thinkingLevel === \"off\" ? {} : { reasoning: thinkingLevel }),\n\t\t\t};\n\t\t\tconst message = await runtime.models.completeSimple(model, { messages }, options);\n\t\t\t// An abort mark or close: the abort invocation or the reopened task handles the committed state.\n\t\t\truntime.signal.throwIfAborted();\n\t\t\tconst summary = summaryText(message);\n\t\t\tconst policy =\n\t\t\t\t(await runtime.snapshot(ConversationConfig, runtime.conversationId, context))?.retry ??\n\t\t\t\tDEFAULT_RETRY_POLICY;\n\t\t\tconst retry =\n\t\t\t\tmessage.stopReason === \"error\" &&\n\t\t\t\tisRetryableAssistantError(message) &&\n\t\t\t\tpolicy.enabled &&\n\t\t\t\tattempt <= policy.maxRetries;\n\t\t\tconst until = retry ? runtime.now() + retryDelayMs(policy, attempt) : 0;\n\t\t\tawait runtime.commit(async (tx, current): Promise<Next> => {\n\t\t\t\tawait recordUsage(\n\t\t\t\t\ttx,\n\t\t\t\t\truntime.conversationId,\n\t\t\t\t\t\"models\",\n\t\t\t\t\t`${message.provider}/${message.model}`,\n\t\t\t\t\tmessage.usage,\n\t\t\t\t);\n\t\t\t\tconst live = await tx.doc(LiveDoc, runtime.conversationId);\n\t\t\t\tif (summary !== undefined) return placeSummary(tx, runtime, current, live, firstKept, summary);\n\t\t\t\tif (retry) {\n\t\t\t\t\tconst status = compactionStatus(live, runtime.taskId);\n\t\t\t\t\tif (status !== undefined) status.retry = { at: until, error: message.errorMessage ?? \"\" };\n\t\t\t\t\treturn { status: \"running\", checkpoint: { phase: \"retry\", ...request, until } };\n\t\t\t\t}\n\t\t\t\tremoveCompactionStatus(live, runtime.taskId);\n\t\t\t\tconst text = summaryFailure(message);\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"terminal\",\n\t\t\t\t\toutcome: { status: \"failed\", error: { message: text, detail: { reason: \"model_error\" } } },\n\t\t\t\t};\n\t\t\t}, context);\n\t\t},\n\t\tretry: async (task, runtime, context) => {\n\t\t\tconst { phase: _, until, ...request } = task.state.checkpoint;\n\t\t\tawait runtime.sleep(until, context);\n\t\t\tconst attempt = request.attempt + 1;\n\t\t\tawait runtime.commit(async (tx) => {\n\t\t\t\tconst status = compactionStatus(await tx.doc(LiveDoc, runtime.conversationId), runtime.taskId);\n\t\t\t\tif (status !== undefined) {\n\t\t\t\t\tstatus.attempt = attempt;\n\t\t\t\t\tdelete status.retry;\n\t\t\t\t}\n\t\t\t\treturn { status: \"running\", checkpoint: { phase: \"summarize\", ...request, attempt } };\n\t\t\t}, context);\n\t\t},\n\t},\n\tabort: async (_task, runtime, context) => {\n\t\tawait runtime.commit(async (tx) => {\n\t\t\tremoveCompactionStatus(await tx.doc(LiveDoc, runtime.conversationId), runtime.taskId);\n\t\t\treturn { status: \"terminal\", outcome: { status: \"aborted\" } };\n\t\t}, context);\n\t},\n});\n\n/**\n * Create a compaction task with its status in this commit. `owner` is the generation that waits for it (a blocking\n * compaction); without one it is conversation-owned, and `background` unless it is manual.\n */\nexport async function createCompaction(\n\ttx: Tx,\n\tconversationId: ConversationId,\n\tinput: CompactionInput,\n\towner?: TaskId,\n): Promise<TaskId<CompactionResult>> {\n\tconst ownership =\n\t\towner === undefined ? ({ kind: \"conversation\" } as const) : { kind: \"task\" as const, taskId: owner };\n\tconst background = owner === undefined && input.reason !== \"manual\";\n\tconst taskId = await tx.createTask(CompactionTask, input, { ownership, conversationId, background });\n\tconst status = { taskId, reason: input.reason, blocking: owner !== undefined, attempt: 1 };\n\taddCompactionStatus(await tx.doc(LiveDoc, conversationId), status);\n\treturn taskId;\n}\n\n/**\n * Index in `view.entries` of the first entry a summary keeps, or `undefined` when there is nothing to compact\n * (spec §8.7). Walks back from the tail until `keepRecentTokens` are kept, then cuts at the first candidate at or after\n * that entry: an entry whose contribution starts with a user or assistant message, never a tool result, and never a\n * user entry that a result of the preceding assistant's calls still follows.\n */\nexport function selectCut(view: ContextView, keepRecentTokens: number): number | undefined {\n\tconst { contributions } = view;\n\tconst start = view.head === undefined ? 0 : 1;\n\tconst candidates: number[] = [];\n\tfor (let index = start; index < contributions.length; index++) {\n\t\tif (isCandidate(contributions, index)) candidates.push(index);\n\t}\n\tlet kept = 0;\n\tlet cut: number | undefined;\n\tfor (let index = contributions.length - 1; index >= start; index--) {\n\t\tfor (const message of contributions[index]!) kept += estimateMessageTokens(message);\n\t\tif (kept < keepRecentTokens) continue;\n\t\tcut = candidates.find((candidate) => candidate >= index) ?? candidates.at(-1);\n\t\tbreak;\n\t}\n\tif (cut === undefined) return undefined;\n\tfor (let index = start; index < cut; index++) if (contributions[index]!.length > 0) return cut;\n\treturn undefined;\n}\n\nfunction isCandidate(contributions: ContextView[\"contributions\"], index: number): boolean {\n\tconst first = contributions[index]![0];\n\tif (first?.role === \"assistant\") return true;\n\tif (first?.role !== \"user\") return false;\n\t// A result of the preceding assistant's calls that follows this entry, before the next assistant, belongs before it.\n\tlet calls: ReadonlySet<string> = new Set();\n\tfor (let before = index - 1; before >= 0; before--) {\n\t\tconst assistant = contributions[before]!.findLast((message) => message.role === \"assistant\");\n\t\tif (assistant === undefined) continue;\n\t\tcalls = new Set(assistant.content.flatMap((content) => (content.type === \"toolCall\" ? [content.id] : [])));\n\t\tbreak;\n\t}\n\tif (calls.size === 0) return true;\n\tfor (let after = index; after < contributions.length; after++) {\n\t\tfor (const [position, message] of contributions[after]!.entries()) {\n\t\t\tif (message.role === \"assistant\" && (after > index || position > 0)) return true;\n\t\t\tif (message.role === \"toolResult\" && calls.has(message.toolCallId)) return false;\n\t\t}\n\t}\n\treturn true;\n}\n\n/** Model messages of the entries before `cut`: the head marker first, ordered like model context (spec §2.1). */\nexport function summarizedMessages(view: ContextView, cut: number): Message[] {\n\treturn orderToolResults(view.contributions.slice(0, cut).flat());\n}\n\n/**\n * Size of a request over `view` followed by `extra` (spec §8.3): the usage of the newest assistant appended after the\n * head marker, whose request included the marker, plus estimates of the messages after it; without one, estimates of\n * every message.\n */\nexport function estimateContext(view: ContextView, extra: readonly Message[]): number {\n\tlet measured: AssistantMessage | undefined;\n\tconst after = view.head?.id ?? Number.NEGATIVE_INFINITY;\n\tfor (let index = view.entries.length - 1; index >= 0 && measured === undefined; index--) {\n\t\tif (view.entries[index]!.id <= after) continue;\n\t\tmeasured = view.contributions[index]!.findLast(\n\t\t\t(message): message is AssistantMessage =>\n\t\t\t\tmessage.role === \"assistant\" && calculateContextTokens(message.usage) > 0,\n\t\t);\n\t}\n\tconst from = measured === undefined ? 0 : view.messages.lastIndexOf(measured) + 1;\n\tlet tokens = measured === undefined ? 0 : calculateContextTokens(measured.usage);\n\tfor (const message of view.messages.slice(from)) tokens += estimateMessageTokens(message);\n\tfor (const message of extra) tokens += estimateMessageTokens(message);\n\treturn tokens;\n}\n\n/** The summary of a clean `stop` with text and no tool call; anything else is not a summary. */\nfunction summaryText(message: AssistantMessage): string | undefined {\n\tif (message.stopReason !== \"stop\" || message.content.some((content) => content.type === \"toolCall\"))\n\t\treturn undefined;\n\tconst text = message.content\n\t\t.flatMap((content) => (content.type === \"text\" ? [content.text] : []))\n\t\t.join(\"\\n\")\n\t\t.trim();\n\treturn text.length === 0 ? undefined : text;\n}\n\nfunction summaryFailure(message: AssistantMessage): string {\n\tif (message.stopReason === \"error\" || message.stopReason === \"aborted\") {\n\t\treturn `Summarization failed: ${message.errorMessage ?? message.stopReason}`;\n\t}\n\tif (message.stopReason === \"length\") return \"Summarization hit the token limit; the summary is incomplete\";\n\tif (message.content.some((content) => content.type === \"toolCall\")) return \"Summarization attempted to call a tool\";\n\treturn \"Summarization produced no text\";\n}\n\n/** The summarizer's user message: the serialized conversation, the prompt, and any instructions. */\nfunction summaryPrompt(messages: readonly Message[], instructions: string | undefined): string {\n\tconst focus = instructions === undefined ? \"\" : `\\n\\nAdditional focus: ${instructions}`;\n\treturn `<conversation>\\n${serializeConversation(messages)}\\n</conversation>\\n\\n${SUMMARIZATION_PROMPT}${focus}`;\n}\n\n/** Messages as plain text, so the summarizer reads a transcript instead of continuing it. System messages are omitted. */\nexport function serializeConversation(messages: readonly Message[]): string {\n\tconst parts: string[] = [];\n\tfor (const message of messages) {\n\t\tif (message.role === \"user\") {\n\t\t\tconst text = contentText(message.content);\n\t\t\tif (text.length > 0) parts.push(`[User]: ${text}`);\n\t\t} else if (message.role === \"assistant\") {\n\t\t\tconst thinking = message.content.flatMap((content) => (content.type === \"thinking\" ? [content.thinking] : []));\n\t\t\tconst text = message.content.flatMap((content) => (content.type === \"text\" ? [content.text] : []));\n\t\t\tconst calls = message.content.flatMap((content) =>\n\t\t\t\tcontent.type === \"toolCall\"\n\t\t\t\t\t? [\n\t\t\t\t\t\t\t`${content.name}(${Object.entries(content.arguments)\n\t\t\t\t\t\t\t\t.map(([key, value]) => `${key}=${JSON.stringify(value)}`)\n\t\t\t\t\t\t\t\t.join(\", \")})`,\n\t\t\t\t\t\t]\n\t\t\t\t\t: [],\n\t\t\t);\n\t\t\tif (thinking.length > 0) parts.push(`[Assistant thinking]: ${thinking.join(\"\\n\")}`);\n\t\t\tif (text.length > 0) parts.push(`[Assistant]: ${text.join(\"\\n\")}`);\n\t\t\tif (calls.length > 0) parts.push(`[Assistant tool calls]: ${calls.join(\"; \")}`);\n\t\t} else if (message.role === \"toolResult\") {\n\t\t\tconst text = contentText(message.content);\n\t\t\tif (text.length > 0) parts.push(`[Tool result]: ${truncate(text, TOOL_RESULT_MAX_CHARS)}`);\n\t\t}\n\t}\n\treturn parts.join(\"\\n\\n\");\n}\n\nfunction contentText(content: string | readonly { readonly type: string; readonly text?: string }[]): string {\n\tif (typeof content === \"string\") return content;\n\treturn content\n\t\t.flatMap((block) => (block.type === \"text\" && block.text !== undefined ? [block.text] : []))\n\t\t.join(\"\\n\");\n}\n\nfunction truncate(text: string, maxChars: number): string {\n\tif (text.length <= maxChars) return text;\n\treturn `${text.slice(0, maxChars)}\\n\\n[... ${text.length - maxChars} more characters truncated]`;\n}\n\n/** Place a summary supplied by a hook in its own commit. */\nasync function place(runtime: Runtime, firstKept: EntryId, summary: string, context: Context): Promise<void> {\n\tawait runtime.commit(\n\t\tasync (tx, current) =>\n\t\t\tplaceSummary(tx, runtime, current, await tx.doc(LiveDoc, runtime.conversationId), firstKept, summary),\n\t\tcontext,\n\t);\n}\n\n/**\n * Place the summary entry and complete (spec §8.7). A blocking compaction, owned by its generation, appends it: the\n * generation holds the run and waits. A conversation-owned one admits it as a write submission, placed at once when\n * idle, otherwise at the next boundary, or settled `stale`.\n * REMINDER: nothing else may append to a busy conversation, so every non-blocking summary goes through admission.\n */\nasync function placeSummary(\n\ttx: Tx,\n\truntime: Runtime,\n\tcurrent: RunningTask<CompactionInput, CompactionCheckpoint, CompactionResult>,\n\tlive: Draft<LiveState>,\n\tfirstKept: EntryId,\n\tsummary: string,\n): Promise<Next> {\n\tremoveCompactionStatus(live, runtime.taskId);\n\tconst text = `${SUMMARY_PREFIX}${summary}${SUMMARY_SUFFIX}`;\n\tconst entry = {\n\t\tkind: CompactionEntry.kind,\n\t\thead: firstKept,\n\t\tmodel: [{ role: \"user\", content: [{ type: \"text\", text }], timestamp: runtime.now() }],\n\t\tdata: { reason: current.input.reason },\n\t} satisfies EntryDraft;\n\tconst result: CompactionResult =\n\t\tcurrent.owner === undefined\n\t\t\t? {\n\t\t\t\t\t// The scheduler commits runtime changes through a Session transaction, which admission needs.\n\t\t\t\t\tsubmissionId: await admitSubmission(\n\t\t\t\t\t\ttx as Transaction,\n\t\t\t\t\t\truntime.conversationId,\n\t\t\t\t\t\t{ type: \"write\", requestId: `compaction:${runtime.taskId}`, entry },\n\t\t\t\t\t\truntime.now(),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t: { entryId: (await tx.appendEntry(runtime.conversationId, entry)).id };\n\treturn { status: \"terminal\", outcome: { status: \"completed\", result } };\n}\n\n/** Remove the status and complete without a summary. */\nasync function complete(runtime: Runtime, context: Context): Promise<void> {\n\tawait runtime.commit(async (tx) => {\n\t\tremoveCompactionStatus(await tx.doc(LiveDoc, runtime.conversationId), runtime.taskId);\n\t\treturn { status: \"terminal\", outcome: { status: \"completed\", result: {} } };\n\t}, context);\n}\n\nasync function failNoModel(runtime: Runtime, ref: ModelRef | undefined, context: Context): Promise<void> {\n\tconst message =\n\t\tref === undefined ? \"No model is configured\" : `Model ${ref.provider}/${ref.modelId} is not available`;\n\tawait runtime.commit(async (tx) => {\n\t\tremoveCompactionStatus(await tx.doc(LiveDoc, runtime.conversationId), runtime.taskId);\n\t\treturn { status: \"terminal\", outcome: { status: \"failed\", error: { message, detail: { reason: \"no_model\" } } } };\n\t}, context);\n}\n"]}
|
package/dist/harness/config.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ModelThinkingLevel } from "@earendil-works/pi-ai";
|
|
2
|
-
import type { ConversationRetryPolicy, ConversationStreamOptions } from "./types.ts";
|
|
2
|
+
import type { CompactionPolicy, ConversationRetryPolicy, ConversationStreamOptions, QueueMode, ToolExecutionMode } from "./types.ts";
|
|
3
3
|
/** Durable per-conversation model, thinking level, request options, and desired tool loadout. */
|
|
4
4
|
export type ConversationConfigState = {
|
|
5
5
|
model?: {
|
|
@@ -13,8 +13,17 @@ export type ConversationConfigState = {
|
|
|
13
13
|
streamOptions?: ConversationStreamOptions;
|
|
14
14
|
/** Durable generation attempt retries; absent uses `DEFAULT_RETRY_POLICY`. */
|
|
15
15
|
retry?: ConversationRetryPolicy;
|
|
16
|
+
/** Whether a round's tools run at once or in call order; absent means `parallel`. */
|
|
17
|
+
toolExecution?: ToolExecutionMode;
|
|
18
|
+
/** How many queued steers a boundary places; absent means `one-at-a-time`. */
|
|
19
|
+
steeringMode?: QueueMode;
|
|
20
|
+
/** How many queued follow-ups a final boundary places; absent means `one-at-a-time`. */
|
|
21
|
+
followUpMode?: QueueMode;
|
|
22
|
+
/** Automatic compaction thresholds; absent uses `DEFAULT_COMPACTION_POLICY`. */
|
|
23
|
+
compaction?: CompactionPolicy;
|
|
16
24
|
};
|
|
17
25
|
export declare const DEFAULT_RETRY_POLICY: ConversationRetryPolicy;
|
|
26
|
+
export declare const DEFAULT_COMPACTION_POLICY: CompactionPolicy;
|
|
18
27
|
/** Built-in configuration document; rewindable so forks start from the configuration at their fork entry. */
|
|
19
28
|
export declare const ConversationConfig: import("../types.ts").RewindableConversationDocToken<ConversationConfigState>;
|
|
20
29
|
//# sourceMappingURL=config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/harness/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/harness/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,KAAK,EACX,gBAAgB,EAChB,uBAAuB,EACvB,yBAAyB,EACzB,SAAS,EACT,iBAAiB,EACjB,MAAM,YAAY,CAAC;AAEpB,iGAAiG;AACjG,MAAM,MAAM,uBAAuB,GAAG;IACrC,KAAK,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,aAAa,EAAE,kBAAkB,CAAC;IAClC,6FAA6F;IAC7F,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,6CAA6C;IAC7C,aAAa,CAAC,EAAE,yBAAyB,CAAC;IAC1C,8EAA8E;IAC9E,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,qFAAqF;IACrF,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,8EAA8E;IAC9E,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,wFAAwF;IACxF,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,gFAAgF;IAChF,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC9B,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,uBAKlC,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,gBAKvC,CAAC;AAEF,6GAA6G;AAC7G,eAAO,MAAM,kBAAkB,+EAQ7B,CAAC"}
|
package/dist/harness/config.js
CHANGED
|
@@ -5,6 +5,12 @@ export const DEFAULT_RETRY_POLICY = {
|
|
|
5
5
|
baseDelayMs: 2000,
|
|
6
6
|
maxAgentDelayMs: 60000,
|
|
7
7
|
};
|
|
8
|
+
export const DEFAULT_COMPACTION_POLICY = {
|
|
9
|
+
enabled: true,
|
|
10
|
+
reserveTokens: 16384,
|
|
11
|
+
keepRecentTokens: 20000,
|
|
12
|
+
backgroundTokens: 32768,
|
|
13
|
+
};
|
|
8
14
|
/** Built-in configuration document; rewindable so forks start from the configuration at their fork entry. */
|
|
9
15
|
export const ConversationConfig = defineDoc({
|
|
10
16
|
kind: "pi.conversation.config",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/harness/config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/harness/config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AA6B5C,MAAM,CAAC,MAAM,oBAAoB,GAA4B;IAC5D,OAAO,EAAE,IAAI;IACb,UAAU,EAAE,CAAC;IACb,WAAW,EAAE,IAAI;IACjB,eAAe,EAAE,KAAK;CACtB,CAAC;AAEF,MAAM,CAAC,MAAM,yBAAyB,GAAqB;IAC1D,OAAO,EAAE,IAAI;IACb,aAAa,EAAE,KAAK;IACpB,gBAAgB,EAAE,KAAK;IACvB,gBAAgB,EAAE,KAAK;CACvB,CAAC;AAEF,6GAA6G;AAC7G,MAAM,CAAC,MAAM,kBAAkB,GAAG,SAAS,CAA0B;IACpE,IAAI,EAAE,wBAAwB;IAC9B,OAAO,EAAE,CAAC;IACV,KAAK,EAAE,cAAc;IACrB,OAAO,EAAE,YAAY;IACrB,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IAC1D,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI;CAC1B,CAAC,CAAC","sourcesContent":["import type { ModelThinkingLevel } from \"@earendil-works/pi-ai\";\nimport { defineDoc } from \"../documents.ts\";\nimport type {\n\tCompactionPolicy,\n\tConversationRetryPolicy,\n\tConversationStreamOptions,\n\tQueueMode,\n\tToolExecutionMode,\n} from \"./types.ts\";\n\n/** Durable per-conversation model, thinking level, request options, and desired tool loadout. */\nexport type ConversationConfigState = {\n\tmodel?: { provider: string; modelId: string };\n\tthinkingLevel: ModelThinkingLevel;\n\t/** Desired tool names in offered order; names may be unregistered in the current process. */\n\tactiveTools: string[];\n\t/** Forwarded to every generation request. */\n\tstreamOptions?: ConversationStreamOptions;\n\t/** Durable generation attempt retries; absent uses `DEFAULT_RETRY_POLICY`. */\n\tretry?: ConversationRetryPolicy;\n\t/** Whether a round's tools run at once or in call order; absent means `parallel`. */\n\ttoolExecution?: ToolExecutionMode;\n\t/** How many queued steers a boundary places; absent means `one-at-a-time`. */\n\tsteeringMode?: QueueMode;\n\t/** How many queued follow-ups a final boundary places; absent means `one-at-a-time`. */\n\tfollowUpMode?: QueueMode;\n\t/** Automatic compaction thresholds; absent uses `DEFAULT_COMPACTION_POLICY`. */\n\tcompaction?: CompactionPolicy;\n};\n\nexport const DEFAULT_RETRY_POLICY: ConversationRetryPolicy = {\n\tenabled: true,\n\tmaxRetries: 3,\n\tbaseDelayMs: 2000,\n\tmaxAgentDelayMs: 60000,\n};\n\nexport const DEFAULT_COMPACTION_POLICY: CompactionPolicy = {\n\tenabled: true,\n\treserveTokens: 16384,\n\tkeepRecentTokens: 20000,\n\tbackgroundTokens: 32768,\n};\n\n/** Built-in configuration document; rewindable so forks start from the configuration at their fork entry. */\nexport const ConversationConfig = defineDoc<ConversationConfigState>({\n\tkind: \"pi.conversation.config\",\n\tversion: 1,\n\tscope: \"conversation\",\n\thistory: \"rewindable\",\n\tfork: \"asOf\",\n\tinitial: () => ({ thinkingLevel: \"off\", activeTools: [] }),\n\tcheckpointWhen: () => true,\n});\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Context } from "@earendil-works/chord";
|
|
2
|
+
import type { Message } from "@earendil-works/pi-ai";
|
|
2
3
|
import type { SessionImpl } from "../session/session.ts";
|
|
3
4
|
import type { ConversationId, EntryId, EntryRecord, Storage } from "../types.ts";
|
|
4
5
|
import type { ContextView } from "./types.ts";
|
|
@@ -24,4 +25,11 @@ export declare function readContext(session: SessionImpl, storage: Storage, conv
|
|
|
24
25
|
* the newest edit in the range wins. Context entries are H followed by the range's non-head entries.
|
|
25
26
|
*/
|
|
26
27
|
export declare function deriveContext(storage: Storage, conversationId: ConversationId, bounds: ContextBounds | undefined, context: Context): Promise<ContextView>;
|
|
28
|
+
/** The raw active entries within captured bounds, without deriving model context. */
|
|
29
|
+
export declare function activeEntries(storage: Storage, conversationId: ConversationId, bounds: ContextBounds | undefined, context: Context): Promise<EntryRecord[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Place each assistant's tool results directly after it in call order. Results are taken from the messages before
|
|
32
|
+
* the next assistant; a missing result is synthesized and unmatched results are dropped.
|
|
33
|
+
*/
|
|
34
|
+
export declare function orderToolResults(messages: readonly Message[]): Message[];
|
|
27
35
|
//# sourceMappingURL=context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/harness/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/harness/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAoB,OAAO,EAA+B,MAAM,uBAAuB,CAAC;AACpG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAe,cAAc,EAAU,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAM9C,iFAAiF;AACjF,MAAM,MAAM,aAAa,GAAG;IAC3B,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,GAAG;QAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,SAAS,CAAC;IACtE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,cAAc,EAC9B,OAAO,EAAE,OAAO,EAChB,EAAE,CAAC,EAAE,OAAO,GACV,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAYpC;AAED,0GAA0G;AAC1G,wBAAsB,WAAW,CAChC,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,cAAc,EAC9B,OAAO,EAAE,OAAO,EAChB,EAAE,CAAC,EAAE,OAAO,GACV,OAAO,CAAC,WAAW,CAAC,CAGtB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAClC,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,cAAc,EAC9B,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,OAAO,EAAE,OAAO,GACd,OAAO,CAAC,WAAW,CAAC,CAkBtB;AAED,qFAAqF;AACrF,wBAAsB,aAAa,CAClC,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,cAAc,EAC9B,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,OAAO,EAAE,OAAO,GACd,OAAO,CAAC,WAAW,EAAE,CAAC,CAGxB;AAgCD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,EAAE,CAsBxE"}
|
package/dist/harness/context.js
CHANGED
|
@@ -34,7 +34,32 @@ export async function readContext(session, storage, conversationId, context, at)
|
|
|
34
34
|
*/
|
|
35
35
|
export async function deriveContext(storage, conversationId, bounds, context) {
|
|
36
36
|
if (bounds === undefined)
|
|
37
|
-
return { head: undefined, entries: [], messages: [] };
|
|
37
|
+
return { head: undefined, entries: [], contributions: [], messages: [] };
|
|
38
|
+
const head = bounds.head;
|
|
39
|
+
const range = await scanRange(storage, conversationId, bounds, context);
|
|
40
|
+
const edits = new Map();
|
|
41
|
+
// Edits of every entry in the range count, including older head markers that `selectActive()` drops.
|
|
42
|
+
for (const entry of range)
|
|
43
|
+
for (const edit of entry.edits ?? [])
|
|
44
|
+
edits.set(edit.target, edit);
|
|
45
|
+
const entries = selectActive(head, range);
|
|
46
|
+
const contributions = entries.map((entry) => {
|
|
47
|
+
const edit = edits.get(entry.id);
|
|
48
|
+
if (edit?.action === "omit")
|
|
49
|
+
return [];
|
|
50
|
+
const contributed = edit?.action === "replace" ? edit.messages : (entry.model ?? []);
|
|
51
|
+
return contributed.filter((message) => message.role !== "assistant" || !EXCLUDED_STOP_REASONS.has(message.stopReason));
|
|
52
|
+
});
|
|
53
|
+
return { head, entries, contributions, messages: orderToolResults(contributions.flat()) };
|
|
54
|
+
}
|
|
55
|
+
/** The raw active entries within captured bounds, without deriving model context. */
|
|
56
|
+
export async function activeEntries(storage, conversationId, bounds, context) {
|
|
57
|
+
if (bounds === undefined)
|
|
58
|
+
return [];
|
|
59
|
+
return selectActive(bounds.head, await scanRange(storage, conversationId, bounds, context));
|
|
60
|
+
}
|
|
61
|
+
/** Visible entries from the head marker's head, or transcript start, through the tail, oldest first. */
|
|
62
|
+
async function scanRange(storage, conversationId, bounds, context) {
|
|
38
63
|
const head = bounds.head;
|
|
39
64
|
const range = [];
|
|
40
65
|
let cursor;
|
|
@@ -45,31 +70,17 @@ export async function deriveContext(storage, conversationId, bounds, context) {
|
|
|
45
70
|
range.push(...page.items);
|
|
46
71
|
cursor = page.next;
|
|
47
72
|
} while (cursor !== undefined);
|
|
48
|
-
range.reverse();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const entries = head === undefined ? range : [head, ...range.filter((entry) => entry.head === undefined)];
|
|
54
|
-
const messages = [];
|
|
55
|
-
for (const entry of entries) {
|
|
56
|
-
const edit = edits.get(entry.id);
|
|
57
|
-
if (edit?.action === "omit")
|
|
58
|
-
continue;
|
|
59
|
-
const contributed = edit?.action === "replace" ? edit.messages : (entry.model ?? []);
|
|
60
|
-
for (const message of contributed) {
|
|
61
|
-
if (message.role === "assistant" && EXCLUDED_STOP_REASONS.has(message.stopReason))
|
|
62
|
-
continue;
|
|
63
|
-
messages.push(message);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return { head, entries, messages: orderToolResults(messages) };
|
|
73
|
+
return range.reverse();
|
|
74
|
+
}
|
|
75
|
+
/** The head marker followed by the range's non-head entries, or the whole range without a marker. */
|
|
76
|
+
function selectActive(head, range) {
|
|
77
|
+
return head === undefined ? range : [head, ...range.filter((entry) => entry.head === undefined)];
|
|
67
78
|
}
|
|
68
79
|
/**
|
|
69
80
|
* Place each assistant's tool results directly after it in call order. Results are taken from the messages before
|
|
70
81
|
* the next assistant; a missing result is synthesized and unmatched results are dropped.
|
|
71
82
|
*/
|
|
72
|
-
function orderToolResults(messages) {
|
|
83
|
+
export function orderToolResults(messages) {
|
|
73
84
|
const ordered = [];
|
|
74
85
|
for (let index = 0; index < messages.length; index++) {
|
|
75
86
|
const message = messages[index];
|