pi-background-tasks 0.7.6 → 0.7.7
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/PUBLISHING.md +7 -7
- package/README.md +177 -13
- package/TESTING.md +94 -0
- package/TEST_PLAN.md +21 -4
- package/extensions/delegate-child.ts +1 -0
- package/package.json +4 -3
- package/src/core/common.ts +41 -0
- package/src/core/context/parent-snapshot.ts +142 -0
- package/src/core/context/token-budget.ts +890 -0
- package/src/core/context/visible-conversation-v2.ts +551 -0
- package/src/core/delegate/artifacts.ts +479 -0
- package/src/core/delegate/budget.ts +370 -0
- package/src/core/delegate/hook-contract-evidence.json +18 -0
- package/src/core/delegate/hook-contract.ts +153 -0
- package/src/core/delegate/launch.ts +459 -0
- package/src/core/delegate/result-package.ts +443 -0
- package/src/core/delegate/runner.ts +406 -0
- package/src/core/delegate/seed.ts +411 -0
- package/src/core/delegate/types.ts +304 -0
- package/src/core/fusion/artifacts.ts +15 -0
- package/src/core/fusion/budget.ts +444 -54
- package/src/core/fusion/context.ts +108 -509
- package/src/core/fusion/orchestrator.ts +116 -5
- package/src/core/fusion/prompts.ts +5 -1
- package/src/core/fusion/types.ts +154 -36
- package/src/core/registry.ts +174 -0
- package/src/delegate-child-extension.ts +673 -0
- package/src/delegate-extension.ts +587 -0
- package/src/extension.ts +10 -0
- package/src/fusion-extension.ts +2 -0
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { canonicalJson } from '../attested-pi-run.js';
|
|
3
|
+
import {
|
|
4
|
+
projectVisibleConversationV2,
|
|
5
|
+
type OmittedRunCounts,
|
|
6
|
+
type ProjectedConversationV2,
|
|
7
|
+
type ProjectionEntry,
|
|
8
|
+
} from '../context/visible-conversation-v2.js';
|
|
2
9
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
type
|
|
6
|
-
|
|
10
|
+
snapshotParentConversation,
|
|
11
|
+
type ParentContextSource,
|
|
12
|
+
type ParentSnapshotOptions,
|
|
13
|
+
type ReadonlyParentSessionManager,
|
|
14
|
+
} from '../context/parent-snapshot.js';
|
|
7
15
|
import type { Message } from '@earendil-works/pi-ai';
|
|
8
|
-
import { canonicalJson } from '../attested-pi-run.js';
|
|
9
|
-
import { isJsonObject, type JsonObject } from '../common.js';
|
|
10
16
|
import {
|
|
11
17
|
FUSION_BRANCH_FILTER_ID,
|
|
12
18
|
FUSION_COMMAND_CONTEXT_POLICY_ID,
|
|
13
19
|
FUSION_CONTEXT_LEDGER_SCHEMA_VERSION,
|
|
14
20
|
FUSION_CONTEXT_TRANSFORM_ID,
|
|
15
|
-
FUSION_IMAGE_OMISSION_PREFIX,
|
|
16
21
|
FUSION_INPUT_SCHEMA_VERSION,
|
|
17
22
|
FUSION_TOOL_CONTEXT_POLICY_ID,
|
|
18
23
|
FusionError,
|
|
@@ -21,33 +26,18 @@ import {
|
|
|
21
26
|
type FusionCanonicalRequestV3,
|
|
22
27
|
type FusionContextOmissionLedgerV2,
|
|
23
28
|
type FusionContextPolicyDescriptor,
|
|
24
|
-
type FusionContextProjectionMapEntry,
|
|
25
29
|
type FusionConversationProjectionV3,
|
|
26
|
-
type FusionOmittedEventKind,
|
|
27
|
-
type FusionOmittedEventRecord,
|
|
28
|
-
type FusionOmittedRunCounts,
|
|
29
|
-
type FusionProjectionAccounting,
|
|
30
30
|
type FusionProjectionEntry,
|
|
31
|
-
type
|
|
32
|
-
type FusionProjectionTextEntry,
|
|
31
|
+
type FusionProjectionOmissionCounts,
|
|
33
32
|
type FusionRequestAuthority,
|
|
34
33
|
type FusionSource,
|
|
35
|
-
type FusionToolCallNameCount,
|
|
36
34
|
} from './types.js';
|
|
37
35
|
|
|
38
36
|
export const FUSION_BRAINSTORM_TOOL_NAME = 'fusion_brainstorm';
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
getEntries(): SessionEntry[];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface FusionContextSource {
|
|
47
|
-
cwd: string;
|
|
48
|
-
sessionManager: FusionReadonlySessionManager;
|
|
49
|
-
getSystemPrompt(): string;
|
|
50
|
-
}
|
|
38
|
+
/** Retained for source compatibility; Fusion's session access is the shared adapter. */
|
|
39
|
+
export type FusionReadonlySessionManager = ReadonlyParentSessionManager;
|
|
40
|
+
export type FusionContextSource = ParentContextSource;
|
|
51
41
|
|
|
52
42
|
export interface BuildFusionCanonicalInputOptions {
|
|
53
43
|
source: FusionSource;
|
|
@@ -67,129 +57,8 @@ export function normalizeFusionCommandRequest(args: string): string {
|
|
|
67
57
|
return args.trim();
|
|
68
58
|
}
|
|
69
59
|
|
|
70
|
-
function entriesById(entries: readonly SessionEntry[]): Map<string, SessionEntry> {
|
|
71
|
-
const byId = new Map<string, SessionEntry>();
|
|
72
|
-
for (const entry of entries) byId.set(entry.id, entry);
|
|
73
|
-
return byId;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function readArray(record: JsonObject, key: string): readonly unknown[] | undefined {
|
|
77
|
-
const value = record[key];
|
|
78
|
-
return Array.isArray(value) ? value : undefined;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function recordOf(value: unknown): JsonObject | undefined {
|
|
82
|
-
if (!isJsonObject(value) || Array.isArray(value)) return undefined;
|
|
83
|
-
return value;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function entryMessage(entry: SessionEntry): JsonObject | undefined {
|
|
87
|
-
if (entry.type !== 'message') return undefined;
|
|
88
|
-
return recordOf(entry.message);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function toolCallPartMatches(
|
|
92
|
-
part: unknown,
|
|
93
|
-
toolCallId: string | undefined,
|
|
94
|
-
toolName: string,
|
|
95
|
-
): boolean {
|
|
96
|
-
const record = recordOf(part);
|
|
97
|
-
if (record === undefined || record['type'] !== 'toolCall') return false;
|
|
98
|
-
if (toolCallId !== undefined) return record['id'] === toolCallId;
|
|
99
|
-
return record['name'] === toolName;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function messageContainsToolCall(
|
|
103
|
-
message: JsonObject,
|
|
104
|
-
toolCallId: string | undefined,
|
|
105
|
-
toolName: string,
|
|
106
|
-
): boolean {
|
|
107
|
-
if (message['role'] !== 'assistant') return false;
|
|
108
|
-
const content = readArray(message, 'content');
|
|
109
|
-
if (content === undefined) return false;
|
|
110
|
-
for (const part of content) {
|
|
111
|
-
if (toolCallPartMatches(part, toolCallId, toolName)) return true;
|
|
112
|
-
}
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
interface EffectiveLeaf {
|
|
117
|
-
leafId: string | null;
|
|
118
|
-
activeToolCallLeafExcluded: boolean;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function effectiveLeafForTool(
|
|
122
|
-
sessionManager: FusionReadonlySessionManager,
|
|
123
|
-
toolCallId: string | undefined,
|
|
124
|
-
toolName: string,
|
|
125
|
-
): EffectiveLeaf {
|
|
126
|
-
const leaf = sessionManager.getLeafEntry();
|
|
127
|
-
if (leaf === undefined)
|
|
128
|
-
return { leafId: sessionManager.getLeafId(), activeToolCallLeafExcluded: false };
|
|
129
|
-
const message = entryMessage(leaf);
|
|
130
|
-
if (message !== undefined && messageContainsToolCall(message, toolCallId, toolName)) {
|
|
131
|
-
return { leafId: leaf.parentId, activeToolCallLeafExcluded: true };
|
|
132
|
-
}
|
|
133
|
-
return { leafId: sessionManager.getLeafId(), activeToolCallLeafExcluded: false };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function effectiveLeaf(
|
|
137
|
-
sessionManager: FusionReadonlySessionManager,
|
|
138
|
-
options: BuildFusionCanonicalInputOptions,
|
|
139
|
-
): EffectiveLeaf {
|
|
140
|
-
if (options.source !== 'tool')
|
|
141
|
-
return { leafId: sessionManager.getLeafId(), activeToolCallLeafExcluded: false };
|
|
142
|
-
return effectiveLeafForTool(
|
|
143
|
-
sessionManager,
|
|
144
|
-
options.toolCallId,
|
|
145
|
-
options.toolName ?? FUSION_BRAINSTORM_TOOL_NAME,
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function utf8Bytes(value: string): number {
|
|
150
|
-
return Buffer.byteLength(value, 'utf8');
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function sha256Hex(bytes: Buffer): string {
|
|
154
|
-
return createHash('sha256').update(bytes).digest('hex');
|
|
155
|
-
}
|
|
156
|
-
|
|
157
60
|
function sha256Text(value: string): string {
|
|
158
|
-
return
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function uint64be(value: number): Buffer {
|
|
162
|
-
const out = Buffer.alloc(8);
|
|
163
|
-
out.writeBigUInt64BE(BigInt(value));
|
|
164
|
-
return out;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/** Length-prefixed framing so concatenated fields cannot collide across boundaries. */
|
|
168
|
-
function lengthPrefixed(bytes: Buffer): Buffer {
|
|
169
|
-
return Buffer.concat([uint64be(bytes.length), bytes]);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function ledgerLeafHash(index: number, record: FusionOmittedEventRecord): Buffer {
|
|
173
|
-
return createHash('sha256')
|
|
174
|
-
.update(Buffer.from('pi-fusion-ledger-leaf-v1\0', 'utf8'))
|
|
175
|
-
.update(uint64be(index))
|
|
176
|
-
.update(lengthPrefixed(Buffer.from(canonicalJson(record), 'utf8')))
|
|
177
|
-
.digest();
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function ledgerRootHash(leaves: readonly Buffer[]): string {
|
|
181
|
-
const hash = createHash('sha256')
|
|
182
|
-
.update(Buffer.from('pi-fusion-ledger-root-v1\0', 'utf8'))
|
|
183
|
-
.update(uint64be(leaves.length));
|
|
184
|
-
for (const leaf of leaves) hash.update(leaf);
|
|
185
|
-
return hash.digest('hex');
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function unsupportedBlock(label: string): FusionError {
|
|
189
|
-
return new FusionError(
|
|
190
|
-
`fusion context projection encountered an unsupported conversation block: ${label}`,
|
|
191
|
-
{ code: 'context_policy_unsupported_block', childCreated: false },
|
|
192
|
-
);
|
|
61
|
+
return createHash('sha256').update(Buffer.from(value, 'utf8')).digest('hex');
|
|
193
62
|
}
|
|
194
63
|
|
|
195
64
|
function contextPolicyId(source: FusionSource): string {
|
|
@@ -217,382 +86,110 @@ function policyDescriptor(source: FusionSource): FusionContextPolicyDescriptor {
|
|
|
217
86
|
};
|
|
218
87
|
}
|
|
219
88
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
private readonly ledger: FusionOmittedEventRecord[] = [];
|
|
227
|
-
private readonly leaves: Buffer[] = [];
|
|
228
|
-
private readonly projectionMap: FusionContextProjectionMapEntry[] = [];
|
|
229
|
-
private readonly toolCallNames = new Map<string, number>();
|
|
230
|
-
private pendingCounts: Required<FusionOmittedRunCounts> | undefined;
|
|
231
|
-
private pendingBytes = 0;
|
|
232
|
-
private pendingLedgerFirst = 0;
|
|
233
|
-
private pendingLedgerLast = 0;
|
|
234
|
-
private pendingSourceFirst = 0;
|
|
235
|
-
private pendingSourceLast = 0;
|
|
236
|
-
|
|
237
|
-
includedUserTextBytes = 0;
|
|
238
|
-
includedAssistantTextBytes = 0;
|
|
239
|
-
includedImageMarkers = 0;
|
|
240
|
-
emptyTextBlocks = 0;
|
|
241
|
-
|
|
242
|
-
addText(entry: FusionProjectionTextEntry): void {
|
|
243
|
-
this.flush();
|
|
244
|
-
this.entries.push(entry);
|
|
245
|
-
if (entry.role === 'user') this.includedUserTextBytes += utf8Bytes(entry.text);
|
|
246
|
-
else this.includedAssistantTextBytes += utf8Bytes(entry.text);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
addOmission(
|
|
250
|
-
sourceOrdinal: number,
|
|
251
|
-
blockOrdinal: number,
|
|
252
|
-
kind: FusionOmittedEventKind,
|
|
253
|
-
payload: Buffer,
|
|
254
|
-
extra: { toolName?: string; toolCallId?: string; mimeType?: string } = {},
|
|
255
|
-
): void {
|
|
256
|
-
const index = this.ledger.length;
|
|
257
|
-
const record: FusionOmittedEventRecord = {
|
|
258
|
-
index,
|
|
259
|
-
source_ordinal: sourceOrdinal,
|
|
260
|
-
block_ordinal: blockOrdinal,
|
|
261
|
-
kind,
|
|
262
|
-
payload_bytes: payload.length,
|
|
263
|
-
payload_sha256: sha256Hex(payload),
|
|
264
|
-
};
|
|
265
|
-
if (extra.toolName !== undefined) record.tool_name = extra.toolName;
|
|
266
|
-
if (extra.toolCallId !== undefined) record.tool_call_id = extra.toolCallId;
|
|
267
|
-
if (extra.mimeType !== undefined) record.mime_type = extra.mimeType;
|
|
268
|
-
this.ledger.push(record);
|
|
269
|
-
this.leaves.push(ledgerLeafHash(index, record));
|
|
270
|
-
|
|
271
|
-
if (extra.toolName !== undefined && kind === 'tool_call') {
|
|
272
|
-
this.toolCallNames.set(extra.toolName, (this.toolCallNames.get(extra.toolName) ?? 0) + 1);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (kind === 'tool_result_image') {
|
|
276
|
-
this.flush();
|
|
277
|
-
this.addLedgerOnlyImageMap(index);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (this.pendingCounts === undefined) {
|
|
282
|
-
this.pendingCounts = {
|
|
283
|
-
assistant_thinking: 0,
|
|
284
|
-
tool_calls: 0,
|
|
285
|
-
tool_result_texts: 0,
|
|
286
|
-
};
|
|
287
|
-
this.pendingBytes = 0;
|
|
288
|
-
this.pendingLedgerFirst = index;
|
|
289
|
-
this.pendingSourceFirst = sourceOrdinal;
|
|
290
|
-
}
|
|
291
|
-
this.pendingLedgerLast = index;
|
|
292
|
-
this.pendingSourceLast = sourceOrdinal;
|
|
293
|
-
this.pendingBytes += payload.length;
|
|
294
|
-
if (kind === 'assistant_thinking') this.pendingCounts.assistant_thinking += 1;
|
|
295
|
-
else if (kind === 'tool_call') this.pendingCounts.tool_calls += 1;
|
|
296
|
-
else this.pendingCounts.tool_result_texts += 1;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
private addLedgerOnlyImageMap(index: number): void {
|
|
300
|
-
const previous = this.projectionMap[this.projectionMap.length - 1];
|
|
301
|
-
if (
|
|
302
|
-
previous !== undefined &&
|
|
303
|
-
previous.entry_kind === 'ledger_only_tool_result_image' &&
|
|
304
|
-
previous.ledger_index_last + 1 === index
|
|
305
|
-
) {
|
|
306
|
-
previous.ledger_index_last = index;
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
this.projectionMap.push({
|
|
310
|
-
entry_kind: 'ledger_only_tool_result_image',
|
|
311
|
-
ledger_index_first: index,
|
|
312
|
-
ledger_index_last: index,
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
private flush(): void {
|
|
317
|
-
const counts = this.pendingCounts;
|
|
318
|
-
if (counts === undefined) return;
|
|
319
|
-
const canonicalEntryIndex = this.entries.length;
|
|
320
|
-
const entry: FusionProjectionOmissionEntry = {
|
|
321
|
-
at: [this.pendingSourceFirst, this.pendingSourceLast],
|
|
322
|
-
bytes: this.pendingBytes,
|
|
323
|
-
counts: compactCounts(counts),
|
|
324
|
-
kind: 'omitted_activity',
|
|
325
|
-
};
|
|
326
|
-
this.entries.push(entry);
|
|
327
|
-
this.projectionMap.push({
|
|
328
|
-
canonical_entry_index: canonicalEntryIndex,
|
|
329
|
-
entry_kind: 'omitted_activity',
|
|
330
|
-
ledger_index_first: this.pendingLedgerFirst,
|
|
331
|
-
ledger_index_last: this.pendingLedgerLast,
|
|
332
|
-
});
|
|
333
|
-
this.pendingCounts = undefined;
|
|
334
|
-
this.pendingBytes = 0;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
finish(
|
|
338
|
-
source: FusionSource,
|
|
339
|
-
branchFilter: FusionBranchFilterDescriptor,
|
|
340
|
-
messageCount: number,
|
|
341
|
-
): { projection: FusionConversationProjectionV3; ledger: FusionContextOmissionLedgerV2 } {
|
|
342
|
-
this.flush();
|
|
343
|
-
const rootSha256 = ledgerRootHash(this.leaves);
|
|
344
|
-
let omittedRunCount = 0;
|
|
345
|
-
let includedTextEntries = 0;
|
|
346
|
-
let receiptBytes = 0;
|
|
347
|
-
for (const entry of this.entries) {
|
|
348
|
-
if (entry.kind === 'text') includedTextEntries += 1;
|
|
349
|
-
else {
|
|
350
|
-
omittedRunCount += 1;
|
|
351
|
-
receiptBytes += utf8Bytes(canonicalJson(entry));
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
let thinkingBytes = 0;
|
|
355
|
-
let toolCallCount = 0;
|
|
356
|
-
let toolArgumentBytes = 0;
|
|
357
|
-
let toolResultTextCount = 0;
|
|
358
|
-
let toolResultTextBytes = 0;
|
|
359
|
-
let toolResultImageCount = 0;
|
|
360
|
-
let toolResultImageBytes = 0;
|
|
361
|
-
for (const row of this.ledger) {
|
|
362
|
-
if (row.kind === 'assistant_thinking') thinkingBytes += row.payload_bytes;
|
|
363
|
-
else if (row.kind === 'tool_call') {
|
|
364
|
-
toolCallCount += 1;
|
|
365
|
-
toolArgumentBytes += row.payload_bytes;
|
|
366
|
-
} else if (row.kind === 'tool_result_text') {
|
|
367
|
-
toolResultTextCount += 1;
|
|
368
|
-
toolResultTextBytes += row.payload_bytes;
|
|
369
|
-
} else {
|
|
370
|
-
toolResultImageCount += 1;
|
|
371
|
-
toolResultImageBytes += row.payload_bytes;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
const toolCallNames: FusionToolCallNameCount[] = [...this.toolCallNames.entries()]
|
|
375
|
-
.map(([name, calls]) => ({ name, calls }))
|
|
376
|
-
.sort((left, right) => (left.name < right.name ? -1 : left.name > right.name ? 1 : 0));
|
|
377
|
-
const accounting: FusionProjectionAccounting = {
|
|
378
|
-
message_count: messageCount,
|
|
379
|
-
included_text_entry_count: includedTextEntries,
|
|
380
|
-
included_user_text_bytes: this.includedUserTextBytes,
|
|
381
|
-
included_assistant_text_bytes: this.includedAssistantTextBytes,
|
|
382
|
-
included_image_marker_count: this.includedImageMarkers,
|
|
383
|
-
empty_text_block_count: this.emptyTextBlocks,
|
|
384
|
-
omitted_run_count: omittedRunCount,
|
|
385
|
-
omitted_event_count: this.ledger.length,
|
|
386
|
-
omitted_thinking_bytes: thinkingBytes,
|
|
387
|
-
omitted_tool_call_count: toolCallCount,
|
|
388
|
-
omitted_tool_call_argument_bytes: toolArgumentBytes,
|
|
389
|
-
omitted_tool_result_text_count: toolResultTextCount,
|
|
390
|
-
omitted_tool_result_text_bytes: toolResultTextBytes,
|
|
391
|
-
omitted_tool_result_image_count: toolResultImageCount,
|
|
392
|
-
omitted_tool_result_image_bytes: toolResultImageBytes,
|
|
393
|
-
tool_call_names: toolCallNames,
|
|
394
|
-
ledger_entry_count: this.ledger.length,
|
|
395
|
-
ledger_root_sha256: rootSha256,
|
|
396
|
-
omission_receipt_utf8_bytes: receiptBytes,
|
|
397
|
-
};
|
|
398
|
-
return {
|
|
399
|
-
projection: {
|
|
400
|
-
policy: policyDescriptor(source),
|
|
401
|
-
branch_filter: branchFilter,
|
|
402
|
-
entries: this.entries,
|
|
403
|
-
accounting,
|
|
404
|
-
},
|
|
405
|
-
ledger: {
|
|
406
|
-
schema_version: FUSION_CONTEXT_LEDGER_SCHEMA_VERSION,
|
|
407
|
-
policy_id: contextPolicyId(source),
|
|
408
|
-
transform: FUSION_CONTEXT_TRANSFORM_ID,
|
|
409
|
-
entries: this.ledger,
|
|
410
|
-
projection_map: this.projectionMap,
|
|
411
|
-
root_sha256: rootSha256,
|
|
412
|
-
},
|
|
413
|
-
};
|
|
414
|
-
}
|
|
89
|
+
function compactOmissionCounts(counts: OmittedRunCounts): FusionProjectionOmissionCounts {
|
|
90
|
+
return [
|
|
91
|
+
counts.assistant_thinking ?? 0,
|
|
92
|
+
counts.tool_calls ?? 0,
|
|
93
|
+
counts.tool_result_texts ?? 0,
|
|
94
|
+
];
|
|
415
95
|
}
|
|
416
96
|
|
|
417
|
-
function
|
|
418
|
-
|
|
97
|
+
function expandOmissionCounts(counts: FusionProjectionOmissionCounts): OmittedRunCounts {
|
|
98
|
+
const out: OmittedRunCounts = {};
|
|
99
|
+
const [assistantThinking, toolCalls, toolResults] = counts;
|
|
100
|
+
if (assistantThinking > 0) out.assistant_thinking = assistantThinking;
|
|
101
|
+
if (toolCalls > 0) out.tool_calls = toolCalls;
|
|
102
|
+
if (toolResults > 0) out.tool_result_texts = toolResults;
|
|
103
|
+
return out;
|
|
419
104
|
}
|
|
420
105
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
return out;
|
|
106
|
+
export function compactFusionProjectionEntry(entry: ProjectionEntry): FusionProjectionEntry {
|
|
107
|
+
if (entry.kind === 'text') {
|
|
108
|
+
return [
|
|
109
|
+
't',
|
|
110
|
+
entry.role === 'user' ? 'u' : 'a',
|
|
111
|
+
entry.source_ordinal,
|
|
112
|
+
entry.block_ordinal,
|
|
113
|
+
entry.text,
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
return ['o', [entry.at[0], entry.at[1]], entry.bytes, compactOmissionCounts(entry.counts)];
|
|
433
117
|
}
|
|
434
118
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
*/
|
|
439
|
-
function projectUserMessage(
|
|
440
|
-
builder: ProjectionBuilder,
|
|
441
|
-
content: Message['content'],
|
|
442
|
-
sourceOrdinal: number,
|
|
443
|
-
): void {
|
|
444
|
-
if (typeof content === 'string') {
|
|
445
|
-
if (content.length === 0) {
|
|
446
|
-
builder.emptyTextBlocks += 1;
|
|
447
|
-
return;
|
|
448
|
-
}
|
|
449
|
-
builder.addText({
|
|
119
|
+
export function expandFusionProjectionEntry(entry: FusionProjectionEntry): ProjectionEntry {
|
|
120
|
+
if (entry[0] === 't') {
|
|
121
|
+
return {
|
|
450
122
|
kind: 'text',
|
|
451
|
-
source_ordinal:
|
|
452
|
-
block_ordinal:
|
|
453
|
-
role: 'user',
|
|
454
|
-
text:
|
|
455
|
-
}
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
for (const [blockOrdinal, block] of content.entries()) {
|
|
459
|
-
if (block.type === 'text') {
|
|
460
|
-
if (block.text.length === 0) {
|
|
461
|
-
builder.emptyTextBlocks += 1;
|
|
462
|
-
continue;
|
|
463
|
-
}
|
|
464
|
-
builder.addText({
|
|
465
|
-
kind: 'text',
|
|
466
|
-
source_ordinal: sourceOrdinal,
|
|
467
|
-
block_ordinal: blockOrdinal,
|
|
468
|
-
role: 'user',
|
|
469
|
-
text: block.text,
|
|
470
|
-
});
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
if (block.type === 'image') {
|
|
474
|
-
builder.includedImageMarkers += 1;
|
|
475
|
-
builder.addText({
|
|
476
|
-
kind: 'text',
|
|
477
|
-
source_ordinal: sourceOrdinal,
|
|
478
|
-
block_ordinal: blockOrdinal,
|
|
479
|
-
role: 'user',
|
|
480
|
-
text: imageMarker(block.mimeType),
|
|
481
|
-
});
|
|
482
|
-
continue;
|
|
483
|
-
}
|
|
484
|
-
throw unsupportedBlock(`user block ${String(Reflect.get(block, 'type'))}`);
|
|
123
|
+
source_ordinal: entry[2],
|
|
124
|
+
block_ordinal: entry[3],
|
|
125
|
+
role: entry[1] === 'u' ? 'user' : 'assistant',
|
|
126
|
+
text: entry[4],
|
|
127
|
+
};
|
|
485
128
|
}
|
|
129
|
+
return {
|
|
130
|
+
kind: 'omitted_activity',
|
|
131
|
+
at: [entry[1][0], entry[1][1]],
|
|
132
|
+
bytes: entry[2],
|
|
133
|
+
counts: expandOmissionCounts(entry[3]),
|
|
134
|
+
};
|
|
486
135
|
}
|
|
487
136
|
|
|
488
|
-
function
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
): void {
|
|
493
|
-
for (const [blockOrdinal, block] of content.entries()) {
|
|
494
|
-
if (block.type === 'text') {
|
|
495
|
-
if (block.text.length === 0) {
|
|
496
|
-
builder.emptyTextBlocks += 1;
|
|
497
|
-
continue;
|
|
498
|
-
}
|
|
499
|
-
builder.addText({
|
|
500
|
-
kind: 'text',
|
|
501
|
-
source_ordinal: sourceOrdinal,
|
|
502
|
-
block_ordinal: blockOrdinal,
|
|
503
|
-
role: 'assistant',
|
|
504
|
-
text: block.text,
|
|
505
|
-
});
|
|
506
|
-
continue;
|
|
507
|
-
}
|
|
508
|
-
if (block.type === 'thinking') {
|
|
509
|
-
builder.addOmission(
|
|
510
|
-
sourceOrdinal,
|
|
511
|
-
blockOrdinal,
|
|
512
|
-
'assistant_thinking',
|
|
513
|
-
Buffer.from(block.thinking, 'utf8'),
|
|
514
|
-
);
|
|
515
|
-
continue;
|
|
516
|
-
}
|
|
517
|
-
if (block.type === 'toolCall') {
|
|
518
|
-
builder.addOmission(
|
|
519
|
-
sourceOrdinal,
|
|
520
|
-
blockOrdinal,
|
|
521
|
-
'tool_call',
|
|
522
|
-
Buffer.from(canonicalJson(block.arguments), 'utf8'),
|
|
523
|
-
{ toolName: block.name, toolCallId: block.id },
|
|
524
|
-
);
|
|
525
|
-
continue;
|
|
526
|
-
}
|
|
527
|
-
throw unsupportedBlock(`assistant block ${String(Reflect.get(block, 'type'))}`);
|
|
528
|
-
}
|
|
137
|
+
function compactFusionProjectionEntries(
|
|
138
|
+
entries: readonly ProjectionEntry[],
|
|
139
|
+
): readonly FusionProjectionEntry[] {
|
|
140
|
+
return entries.map(compactFusionProjectionEntry);
|
|
529
141
|
}
|
|
530
142
|
|
|
531
|
-
function
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
): void {
|
|
536
|
-
const content = message.content;
|
|
537
|
-
if (typeof content === 'string') {
|
|
538
|
-
builder.addOmission(
|
|
539
|
-
sourceOrdinal,
|
|
540
|
-
0,
|
|
541
|
-
'tool_result_text',
|
|
542
|
-
Buffer.from(content, 'utf8'),
|
|
543
|
-
{ toolName: message.toolName, toolCallId: message.toolCallId },
|
|
544
|
-
);
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
for (const [blockOrdinal, block] of content.entries()) {
|
|
548
|
-
if (block.type === 'text') {
|
|
549
|
-
builder.addOmission(
|
|
550
|
-
sourceOrdinal,
|
|
551
|
-
blockOrdinal,
|
|
552
|
-
'tool_result_text',
|
|
553
|
-
Buffer.from(block.text, 'utf8'),
|
|
554
|
-
{ toolName: message.toolName, toolCallId: message.toolCallId },
|
|
555
|
-
);
|
|
556
|
-
continue;
|
|
557
|
-
}
|
|
558
|
-
if (block.type === 'image') {
|
|
559
|
-
builder.addOmission(
|
|
560
|
-
sourceOrdinal,
|
|
561
|
-
blockOrdinal,
|
|
562
|
-
'tool_result_image',
|
|
563
|
-
Buffer.from(block.data, 'utf8'),
|
|
564
|
-
{
|
|
565
|
-
toolName: message.toolName,
|
|
566
|
-
toolCallId: message.toolCallId,
|
|
567
|
-
mimeType: block.mimeType,
|
|
568
|
-
},
|
|
569
|
-
);
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
throw unsupportedBlock(`tool result block ${String(Reflect.get(block, 'type'))}`);
|
|
143
|
+
function compactOmissionReceiptBytes(entries: readonly FusionProjectionEntry[]): number {
|
|
144
|
+
let total = 0;
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
if (entry[0] === 'o') total += Buffer.byteLength(canonicalJson(entry), 'utf8');
|
|
573
147
|
}
|
|
148
|
+
return total;
|
|
574
149
|
}
|
|
575
150
|
|
|
576
151
|
/**
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
*
|
|
152
|
+
* Seal the shared transform output into Fusion's versioned envelopes.
|
|
153
|
+
*
|
|
154
|
+
* Fusion v4 compacts only the child-facing projection entries. Ledger rows are
|
|
155
|
+
* carried through unchanged, so the ledger root commits to exactly the same
|
|
156
|
+
* omitted payload records before and after tuple encoding. Golden tests pin the
|
|
157
|
+
* new canonical-input bytes and the unchanged ledger bytes.
|
|
580
158
|
*/
|
|
159
|
+
function sealFusionProjection(
|
|
160
|
+
projected: ProjectedConversationV2,
|
|
161
|
+
source: FusionSource,
|
|
162
|
+
branchFilter: FusionBranchFilterDescriptor,
|
|
163
|
+
): { projection: FusionConversationProjectionV3; ledger: FusionContextOmissionLedgerV2 } {
|
|
164
|
+
const entries = compactFusionProjectionEntries(projected.entries);
|
|
165
|
+
return {
|
|
166
|
+
projection: {
|
|
167
|
+
policy: policyDescriptor(source),
|
|
168
|
+
branch_filter: branchFilter,
|
|
169
|
+
entries,
|
|
170
|
+
accounting: {
|
|
171
|
+
...projected.accounting,
|
|
172
|
+
omission_receipt_utf8_bytes: compactOmissionReceiptBytes(entries),
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
ledger: {
|
|
176
|
+
schema_version: FUSION_CONTEXT_LEDGER_SCHEMA_VERSION,
|
|
177
|
+
policy_id: contextPolicyId(source),
|
|
178
|
+
transform: FUSION_CONTEXT_TRANSFORM_ID,
|
|
179
|
+
entries: projected.ledger.entries,
|
|
180
|
+
projection_map: projected.ledger.projection_map,
|
|
181
|
+
root_sha256: projected.ledger.root_sha256,
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Preserved public entry point; delegates to the shared frozen transform. */
|
|
581
187
|
export function projectFusionConversation(
|
|
582
188
|
messages: readonly Message[],
|
|
583
189
|
source: FusionSource,
|
|
584
190
|
branchFilter: FusionBranchFilterDescriptor,
|
|
585
191
|
): { projection: FusionConversationProjectionV3; ledger: FusionContextOmissionLedgerV2 } {
|
|
586
|
-
|
|
587
|
-
for (const [sourceOrdinal, message] of messages.entries()) {
|
|
588
|
-
if (message.role === 'user') projectUserMessage(builder, message.content, sourceOrdinal);
|
|
589
|
-
else if (message.role === 'assistant')
|
|
590
|
-
projectAssistantMessage(builder, message.content, sourceOrdinal);
|
|
591
|
-
else if (message.role === 'toolResult')
|
|
592
|
-
projectToolResultMessage(builder, message, sourceOrdinal);
|
|
593
|
-
else throw unsupportedBlock(`message role ${String(Reflect.get(message, 'role'))}`);
|
|
594
|
-
}
|
|
595
|
-
return builder.finish(source, branchFilter, messages.length);
|
|
192
|
+
return sealFusionProjection(projectVisibleConversationV2(messages), source, branchFilter);
|
|
596
193
|
}
|
|
597
194
|
|
|
598
195
|
export function buildFusionCanonicalInput(
|
|
@@ -605,18 +202,20 @@ export function buildFusionCanonicalInput(
|
|
|
605
202
|
childCreated: false,
|
|
606
203
|
});
|
|
607
204
|
}
|
|
608
|
-
const entries = ctx.sessionManager.getEntries();
|
|
609
|
-
const leaf = effectiveLeaf(ctx.sessionManager, options);
|
|
610
|
-
const sessionContext = buildSessionContext(entries, leaf.leafId, entriesById(entries));
|
|
611
|
-
const llmMessages = convertToLlm(sessionContext.messages);
|
|
612
205
|
const toolName = options.toolName ?? FUSION_BRAINSTORM_TOOL_NAME;
|
|
206
|
+
const snapshotOptions: ParentSnapshotOptions = {
|
|
207
|
+
toolName,
|
|
208
|
+
excludeActiveToolCallLeaf: options.source === 'tool',
|
|
209
|
+
};
|
|
210
|
+
if (options.toolCallId !== undefined) snapshotOptions.toolCallId = options.toolCallId;
|
|
211
|
+
const snapshot = snapshotParentConversation(ctx, snapshotOptions);
|
|
613
212
|
const branchFilter: FusionBranchFilterDescriptor = {
|
|
614
213
|
id: FUSION_BRANCH_FILTER_ID,
|
|
615
214
|
tool_name: toolName,
|
|
616
215
|
tool_call_id: options.source === 'tool' ? (options.toolCallId ?? null) : null,
|
|
617
|
-
active_tool_call_leaf_excluded:
|
|
216
|
+
active_tool_call_leaf_excluded: snapshot.activeToolCallLeafExcluded,
|
|
618
217
|
};
|
|
619
|
-
const projected = projectFusionConversation(
|
|
218
|
+
const projected = projectFusionConversation(snapshot.messages, options.source, branchFilter);
|
|
620
219
|
const request: FusionCanonicalRequestV3 = {
|
|
621
220
|
source: options.source,
|
|
622
221
|
authority: requestAuthority(options.source),
|
|
@@ -634,6 +233,6 @@ export function buildFusionCanonicalInput(
|
|
|
634
233
|
input,
|
|
635
234
|
serialized: canonicalJson(input),
|
|
636
235
|
ledger: projected.ledger,
|
|
637
|
-
transcriptLeafId:
|
|
236
|
+
transcriptLeafId: snapshot.leafId,
|
|
638
237
|
};
|
|
639
238
|
}
|