@plurnk/plurnk-digest 2.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -11
- package/SPEC.md +32 -3
- package/dist/Digest.d.ts.map +1 -1
- package/dist/Digest.js +1 -7
- package/dist/Digest.js.map +1 -1
- package/dist/DigestRender.d.ts +4 -1
- package/dist/DigestRender.d.ts.map +1 -1
- package/dist/DigestRender.js +221 -41
- package/dist/DigestRender.js.map +1 -1
- package/dist/DigestRequiem.d.ts.map +1 -1
- package/dist/DigestRequiem.js +5 -23
- package/dist/DigestRequiem.js.map +1 -1
- package/dist/digest-rows.d.ts +3 -2
- package/dist/digest-rows.d.ts.map +1 -1
- package/dist/evidence.d.ts +3 -1
- package/dist/evidence.d.ts.map +1 -1
- package/package.json +5 -5
package/dist/DigestRender.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Digest renderers ({§digest-programmatic-surface}): the markdown, JSON, reasoning, and packet
|
|
2
2
|
// artifacts of one DigestModel, reading heavy evidence on demand through DigestEvidence.
|
|
3
3
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
4
|
+
import { dirname, join, posix } from "node:path";
|
|
5
5
|
import { aggregateProviderAccounting, } from "@plurnk/plurnk-providers";
|
|
6
6
|
import { Validator } from "@plurnk/plurnk-contracts";
|
|
7
7
|
import { EDIT_FORMS } from "./digest-rows.js";
|
|
@@ -11,6 +11,26 @@ function* projectRows(rows, project) {
|
|
|
11
11
|
yield project(row);
|
|
12
12
|
}
|
|
13
13
|
export default class DigestRender {
|
|
14
|
+
static #literal(body, language = "text") {
|
|
15
|
+
let length = 3;
|
|
16
|
+
for (const match of body.matchAll(/`+/gu))
|
|
17
|
+
length = Math.max(length, match[0].length + 1);
|
|
18
|
+
const fence = "`".repeat(length);
|
|
19
|
+
return `${fence}${language}\n${body}\n${fence}`;
|
|
20
|
+
}
|
|
21
|
+
static #link(from, target, label) {
|
|
22
|
+
return `[${label}](${posix.relative(posix.dirname(from), target)})`;
|
|
23
|
+
}
|
|
24
|
+
static #turnLinks(m, turn, from) {
|
|
25
|
+
const stem = DigestRender.packetStems(m).get(turn.id);
|
|
26
|
+
if (stem === undefined)
|
|
27
|
+
return [];
|
|
28
|
+
return [
|
|
29
|
+
...(turn.has_packet === 1 ? [DigestRender.#link(from, `${stem}.request.md`, "Input")] : []),
|
|
30
|
+
...(turn.program !== null ? [DigestRender.#link(from, `${stem}.assistant.md`, "Output")] : []),
|
|
31
|
+
...(turn.has_reasoning === 1 ? [DigestRender.#link(from, `${stem}.reasoning.md`, "Reasoning")] : []),
|
|
32
|
+
];
|
|
33
|
+
}
|
|
14
34
|
static #summarize(text, n = 80) {
|
|
15
35
|
if (text === null || text === undefined)
|
|
16
36
|
return "";
|
|
@@ -217,18 +237,13 @@ export default class DigestRender {
|
|
|
217
237
|
const { packet } = m.evidence.packet(turn);
|
|
218
238
|
if (packet === null)
|
|
219
239
|
return null;
|
|
220
|
-
|
|
240
|
+
const messages = packet.messages();
|
|
241
|
+
if (messages.length === 0)
|
|
242
|
+
return null;
|
|
243
|
+
return messages
|
|
221
244
|
.map(({ role, content }) => `${role}\n${content}`)
|
|
222
245
|
.join("\n");
|
|
223
246
|
}
|
|
224
|
-
// {§emission-row} — the emissions the turn's worker announced; a stored request places those its log
|
|
225
|
-
// section carried.
|
|
226
|
-
static #emissionsOf(turn, m) {
|
|
227
|
-
const loop = m.loopsById.get(turn.loop_id);
|
|
228
|
-
if (loop === undefined)
|
|
229
|
-
throw new Error(`digest: turn ${turn.id} names loop ${turn.loop_id}, which the digest did not read`);
|
|
230
|
-
return m.emissionsByWorker.get(loop.worker_id) ?? new Map();
|
|
231
|
-
}
|
|
232
247
|
static #commonPrefixLength(left, right) {
|
|
233
248
|
const bound = Math.min(left.length, right.length);
|
|
234
249
|
let n = 0;
|
|
@@ -479,8 +494,11 @@ export default class DigestRender {
|
|
|
479
494
|
: null;
|
|
480
495
|
// {§provider-wire-emission} — an empty emission is read from what the wire carried, never guessed at.
|
|
481
496
|
const wireLine = content.length === 0 && packet?.assistant != null ? DigestRender.wireLine(packet.assistantRaw) : null;
|
|
497
|
+
const roles = packet?.messages().map(({ role }) => role).join(" → ");
|
|
498
|
+
const requestLine = packet === null ? null
|
|
499
|
+
: ` ↳ request: ${roles || "input sections not retained"} (${stem}.request.md)`;
|
|
482
500
|
const opLines = DigestRender.#renderOpLines(m.logEntriesByTurn.get(turn.id) ?? [], m);
|
|
483
|
-
return [head, ...(summary ? [summary] : []), ...(reasoningLine ? [reasoningLine] : []), ...(wireLine ? [wireLine] : []), ...opLines].join("\n");
|
|
501
|
+
return [head, ...(requestLine ? [requestLine] : []), ...(summary ? [summary] : []), ...(reasoningLine ? [reasoningLine] : []), ...(wireLine ? [wireLine] : []), ...opLines].join("\n");
|
|
484
502
|
}
|
|
485
503
|
// {§provider-wire-emission} — public so the line can be witnessed on its own.
|
|
486
504
|
static wireLine(assistantRaw) {
|
|
@@ -541,12 +559,55 @@ export default class DigestRender {
|
|
|
541
559
|
`Tokens: ${usageStr}`,
|
|
542
560
|
`Cost: ${costStr}${kindStr}${usagelessStr}`,
|
|
543
561
|
`Wire: ${wireStr}`,
|
|
562
|
+
`Room: ${DigestRender.#renderRoom(worker, m)}`,
|
|
544
563
|
`Op mix: ${opMix.length > 0 ? opMix : "(no ops)"}`,
|
|
545
564
|
`EDITs: ${DigestRender.#renderEditCensus(DigestRender.#editCensus(m).byWorker.get(worker.id))}`,
|
|
546
565
|
`Emissions: ${DigestRender.#renderEmissions(worker, m)}`,
|
|
547
566
|
`Reasonings: ${DigestRender.#renderReasonings(worker, m)}`,
|
|
548
567
|
].join("\n");
|
|
549
568
|
}
|
|
569
|
+
// {§digest-room-line} — the room in provider tokens: the largest budget the model was shown, at its
|
|
570
|
+
// request's own ratio, against the capacity; the wall's estimate against the provider's count; and the
|
|
571
|
+
// requests the estimate put under the wall while the count was over it.
|
|
572
|
+
static #renderRoom(worker, m) {
|
|
573
|
+
const measured = DigestRender.#roomMeasures(worker, m);
|
|
574
|
+
if (measured.length === 0)
|
|
575
|
+
return "(no measured requests)";
|
|
576
|
+
const room = Math.max(...measured.map(({ weight, budget, count, capacity }) => budget * count / weight / capacity));
|
|
577
|
+
const errors = measured.map(({ estimate, count }) => (estimate - count) / count);
|
|
578
|
+
const missed = measured.filter(({ estimate, count, wall }) => estimate <= wall && count > wall).length;
|
|
579
|
+
const percent = (ratio) => `${ratio < 0 ? "-" : "+"}${Math.abs(100 * ratio).toFixed(1)}%`;
|
|
580
|
+
return `budget up to ${Math.round(100 * room)}% of capacity · wall estimate ${percent(Math.min(...errors))} to ${percent(Math.max(...errors))} of the count`
|
|
581
|
+
+ (missed === 0 ? "" : ` ⚠ ${missed} over the wall the estimate admitted`);
|
|
582
|
+
}
|
|
583
|
+
// One measure per packet-bearing inference turn whose request records a known capacity and wall and a
|
|
584
|
+
// provider count: the exact preflight measurement, else the reported input.
|
|
585
|
+
static #roomMeasures(worker, m) {
|
|
586
|
+
const measures = [];
|
|
587
|
+
for (const loop of m.loopsByWorker.get(worker.id) ?? []) {
|
|
588
|
+
for (const turn of m.turnsByLoop.get(loop.id) ?? []) {
|
|
589
|
+
if (turn.kind !== "inference" || turn.has_packet !== 1)
|
|
590
|
+
continue;
|
|
591
|
+
const packet = m.evidence.packet(turn).packet;
|
|
592
|
+
if (packet === null || packet.budget === null || packet.budget === 0 || packet.weight === 0)
|
|
593
|
+
continue;
|
|
594
|
+
const call = m.modelCalls.findLast((row) => row.turn_id === turn.id && row.kind === "emission" && row.capacity !== null);
|
|
595
|
+
const capacity = DigestRender.parseJson(call?.capacity);
|
|
596
|
+
if (capacity?.inputCapacity == null || capacity.inputWall == null)
|
|
597
|
+
continue;
|
|
598
|
+
const reported = (m.requestsByTurn.get(turn.id) ?? [])
|
|
599
|
+
.findLast((row) => row.kind === "emission" && row.outcome === "response" && row.usage_input !== null)?.usage_input ?? null;
|
|
600
|
+
const count = capacity.prompt?.kind === "exact" ? capacity.prompt.tokens ?? null : reported;
|
|
601
|
+
if (count === null || count === 0)
|
|
602
|
+
continue;
|
|
603
|
+
measures.push({
|
|
604
|
+
weight: packet.weight, budget: packet.budget, capacity: capacity.inputCapacity, wall: capacity.inputWall, count,
|
|
605
|
+
estimate: Math.ceil(packet.weight * capacity.inputCapacity / packet.budget),
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return measures;
|
|
610
|
+
}
|
|
550
611
|
// {§emission-row} — announced, retired by the model, and echoed back as headings in its own text.
|
|
551
612
|
static #renderEmissions(worker, m) {
|
|
552
613
|
const rows = m.emissionRows.filter((row) => row.worker_id === worker.id);
|
|
@@ -603,6 +664,7 @@ export default class DigestRender {
|
|
|
603
664
|
for (const worker of workspaceWorkers) {
|
|
604
665
|
lines.push("");
|
|
605
666
|
lines.push(`### Worker #${worker.id} — ${worker.name}`);
|
|
667
|
+
lines.push(`Owner: ${worker.owner}`);
|
|
606
668
|
lines.push("");
|
|
607
669
|
lines.push("```");
|
|
608
670
|
lines.push(DigestRender.#renderWorkerShape(worker, m));
|
|
@@ -629,24 +691,15 @@ export default class DigestRender {
|
|
|
629
691
|
if (loop.status !== 200 && terminal?.problem?.detail !== undefined) {
|
|
630
692
|
lines.push(`Terminal${loop.terminated_by !== null ? ` (${loop.terminated_by})` : ""}: ${terminal.problem.detail.trim()}`);
|
|
631
693
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
.
|
|
636
|
-
|
|
637
|
-
|
|
694
|
+
lines.push("");
|
|
695
|
+
lines.push("| Turn | Packet artifacts | Physical requests |", "| --- | --- | --- |");
|
|
696
|
+
for (const turn of m.turnsByLoop.get(loop.id) ?? []) {
|
|
697
|
+
const requests = (m.requestsByTurn.get(turn.id) ?? []).map((request) => DigestRender.#link("digest.md", `requests/${request.id}.md`, `${request.id}: ${request.kind} ${request.outcome ?? request.state}`));
|
|
698
|
+
lines.push(`| ${turn.sequence} (${turn.kind}) | ${DigestRender.#turnLinks(m, turn, "digest.md").join(" · ") || "—"} | ${requests.join(" · ") || "—"} |`);
|
|
699
|
+
}
|
|
638
700
|
lines.push("");
|
|
639
701
|
const turnLines = (m.turnsByLoop.get(loop.id) ?? []).map((t) => DigestRender.#renderTurnLine(t, m));
|
|
640
|
-
|
|
641
|
-
const matches = s.match(/`+/g);
|
|
642
|
-
return matches ? Math.max(...matches.map((match) => match.length)) : 0;
|
|
643
|
-
};
|
|
644
|
-
const maxBackticks = turnLines.reduce((max, line) => Math.max(max, findMaxTicks(line)), 0);
|
|
645
|
-
const fence = "`".repeat(Math.max(3, maxBackticks + 1));
|
|
646
|
-
lines.push(fence);
|
|
647
|
-
for (const line of turnLines)
|
|
648
|
-
lines.push(line);
|
|
649
|
-
lines.push(fence);
|
|
702
|
+
lines.push(DigestRender.#literal(turnLines.join("\n"), ""));
|
|
650
703
|
}
|
|
651
704
|
}
|
|
652
705
|
}
|
|
@@ -667,7 +720,7 @@ export default class DigestRender {
|
|
|
667
720
|
lines.push("(operation turn; no provider inference)");
|
|
668
721
|
const reasoning = m.evidence.reasoning(t);
|
|
669
722
|
if (reasoning !== null)
|
|
670
|
-
lines.push("", reasoning);
|
|
723
|
+
lines.push("", DigestRender.#literal(reasoning));
|
|
671
724
|
continue;
|
|
672
725
|
}
|
|
673
726
|
const attempts = m.attemptsByTurn.get(t.id) ?? [];
|
|
@@ -680,13 +733,14 @@ export default class DigestRender {
|
|
|
680
733
|
if (packetFailure !== null)
|
|
681
734
|
lines.push("(stored provider packet is invalid; see its packet artifacts)");
|
|
682
735
|
else if (typeof reasoning === "string" && reasoning.length > 0)
|
|
683
|
-
lines.push(reasoning);
|
|
736
|
+
lines.push(DigestRender.#literal(reasoning));
|
|
684
737
|
else
|
|
685
738
|
lines.push("(no admitted provider reasoning)");
|
|
686
739
|
continue;
|
|
687
740
|
}
|
|
688
741
|
for (const attempt of attempts) {
|
|
689
|
-
const
|
|
742
|
+
const retainedResponse = m.evidence.response(attempt.model_call_id);
|
|
743
|
+
const response = DigestRender.parseJson(retainedResponse, {});
|
|
690
744
|
const parseErrors = DigestRender.parseJson(attempt.parse_errors, []);
|
|
691
745
|
lines.push("");
|
|
692
746
|
const disposition = attempt.state === "error"
|
|
@@ -703,7 +757,7 @@ export default class DigestRender {
|
|
|
703
757
|
lines.push(`Failure: ${JSON.stringify(DigestRender.parseJson(attempt.failure, attempt.failure))}`);
|
|
704
758
|
}
|
|
705
759
|
for (const request of m.requestsByAttempt.get(attempt.id) ?? []) {
|
|
706
|
-
lines.push(`Physical request ${request.sequence}: [${request.state === "settled" ? request.outcome : request.state} evidence](requests/${request.id}.
|
|
760
|
+
lines.push(`Physical request ${request.sequence}: [${request.state === "settled" ? request.outcome : request.state} evidence](requests/${request.id}.md)`);
|
|
707
761
|
}
|
|
708
762
|
if (attempt.accepted !== 1) {
|
|
709
763
|
for (const error of parseErrors) {
|
|
@@ -714,7 +768,9 @@ export default class DigestRender {
|
|
|
714
768
|
}
|
|
715
769
|
const reasoning = response.assistant?.reasoning ?? null;
|
|
716
770
|
if (typeof reasoning === "string" && reasoning.length > 0)
|
|
717
|
-
lines.push(reasoning);
|
|
771
|
+
lines.push(DigestRender.#literal(reasoning));
|
|
772
|
+
else if (retainedResponse === null && attempt.state === "response")
|
|
773
|
+
lines.push("(response evidence is not retained; readable reasoning availability is unknown)");
|
|
718
774
|
else {
|
|
719
775
|
const reasoningTokens = DigestRender.#accounting(m.requestsByAttempt.get(attempt.id) ?? [])?.usage?.outputTokenDetails?.reasoningTokens;
|
|
720
776
|
lines.push(reasoningTokens !== undefined && reasoningTokens > 0
|
|
@@ -726,6 +782,108 @@ export default class DigestRender {
|
|
|
726
782
|
}
|
|
727
783
|
return lines.join("\n");
|
|
728
784
|
}
|
|
785
|
+
// {§share-packet-names}: render the owned envelope, never infer roles from filenames or body text.
|
|
786
|
+
static request(messages, preamble = []) {
|
|
787
|
+
const blocks = messages.map(({ role, content }, index) => {
|
|
788
|
+
return `### ${index + 1}. ${role}\n\n${DigestRender.#literal(content)}`;
|
|
789
|
+
});
|
|
790
|
+
return ["# Request", ...preamble, "## Input messages", messages.map(({ role }) => role).join(" → "),
|
|
791
|
+
messages.length === 0 ? "No input message sections are retained; the original input cannot be reconstructed."
|
|
792
|
+
: "Stored text-message envelope, not a transport capture. Native payloads and provider transformations are not shown.",
|
|
793
|
+
...blocks, ""].join("\n\n");
|
|
794
|
+
}
|
|
795
|
+
// {§digest-navigation}: navigation surrounds evidence; channel files stay verbatim.
|
|
796
|
+
static #requestPreamble(m, turn) {
|
|
797
|
+
const stems = DigestRender.packetStems(m);
|
|
798
|
+
const stem = stems.get(turn.id);
|
|
799
|
+
const from = `${stem}.request.md`;
|
|
800
|
+
const loop = m.loopsById.get(turn.loop_id);
|
|
801
|
+
const siblings = m.turns.filter((row) => row.has_packet === 1
|
|
802
|
+
&& m.loopsById.get(row.loop_id)?.worker_id === loop.worker_id).toSorted((a, b) => a.id - b.id);
|
|
803
|
+
const index = siblings.findIndex(({ id }) => id === turn.id);
|
|
804
|
+
const previous = siblings[index - 1];
|
|
805
|
+
const next = siblings[index + 1];
|
|
806
|
+
const navigation = [
|
|
807
|
+
DigestRender.#link(from, "digest.md", "Digest"),
|
|
808
|
+
...(previous === undefined ? [] : [DigestRender.#link(from, `${stems.get(previous.id)}.request.md`, "Previous")]),
|
|
809
|
+
...(next === undefined ? [] : [DigestRender.#link(from, `${stems.get(next.id)}.request.md`, "Next")]),
|
|
810
|
+
...DigestRender.#turnLinks(m, turn, from).filter((link) => !link.startsWith("[Input]")),
|
|
811
|
+
];
|
|
812
|
+
const lines = [posix.basename(stem), navigation.join(" · ")];
|
|
813
|
+
const requests = m.requestsByTurn.get(turn.id) ?? [];
|
|
814
|
+
if (requests.length > 0) {
|
|
815
|
+
const table = ["| Request | Call | Admission | Outcome |", "| --- | --- | --- | --- |"];
|
|
816
|
+
for (const request of requests) {
|
|
817
|
+
table.push(`| ${DigestRender.#link(from, `requests/${request.id}.md`, String(request.id))} | ${request.kind} ${request.inference_call_id} | ${DigestRender.#admission(m, request)} | ${request.outcome ?? request.state} |`);
|
|
818
|
+
}
|
|
819
|
+
lines.push("## Attempts", table.join("\n"));
|
|
820
|
+
}
|
|
821
|
+
for (const attempt of m.attemptsByTurn.get(turn.id) ?? []) {
|
|
822
|
+
if (attempt.accepted === 1)
|
|
823
|
+
continue;
|
|
824
|
+
const ordinal = String(attempt.sequence).padStart(3, "0");
|
|
825
|
+
const prefix = `${stem}.attempt${ordinal}`;
|
|
826
|
+
if (attempt.state !== "response") {
|
|
827
|
+
lines.push(`### Emission attempt ${attempt.sequence}: ${attempt.state}`, DigestRender.#link(from, `${prefix}.${attempt.state}.json`, "Call evidence"));
|
|
828
|
+
if (attempt.failure !== null)
|
|
829
|
+
lines.push(DigestRender.#literal(attempt.failure, "json"));
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
const admission = attempt.accepted === 0 ? "rejected" : "unadmitted";
|
|
833
|
+
lines.push(`### Emission attempt ${attempt.sequence}: ${admission}`, m.evidence.response(attempt.model_call_id) === null ? "Response evidence is not retained."
|
|
834
|
+
: DigestRender.#link(from, `${prefix}.${admission}.assistant.md`, "Exact output"), DigestRender.#literal(JSON.stringify(DigestRender.parseJson(attempt.parse_errors, []), null, 2), "json"));
|
|
835
|
+
}
|
|
836
|
+
return lines;
|
|
837
|
+
}
|
|
838
|
+
static #admission(m, request) {
|
|
839
|
+
if (request.kind === "bare")
|
|
840
|
+
return "not an emission";
|
|
841
|
+
if (request.outcome !== "response")
|
|
842
|
+
return "not admitted";
|
|
843
|
+
const call = m.modelCalls.find(({ id }) => id === request.inference_call_id);
|
|
844
|
+
return call?.accepted === 1 ? "admitted" : call?.accepted === 0 ? "rejected" : "not admitted";
|
|
845
|
+
}
|
|
846
|
+
static #physicalRequest(m, request, evidence) {
|
|
847
|
+
const from = `requests/${request.id}.md`;
|
|
848
|
+
const turn = m.turns.find(({ id }) => id === request.turn_id);
|
|
849
|
+
const fields = typeof evidence === "object" && evidence !== null ? evidence : {};
|
|
850
|
+
const capture = fields.request;
|
|
851
|
+
const lines = [
|
|
852
|
+
`# Physical request ${request.id}`,
|
|
853
|
+
[DigestRender.#link(from, "digest.md", "Digest"),
|
|
854
|
+
DigestRender.#link(from, `requests/${request.id}.json`, "Complete evidence")].join(" · "),
|
|
855
|
+
...(turn === undefined ? [] : [`Originating turn: ${DigestRender.#turnLinks(m, turn, from).join(" · ") || "no packet artifacts retained"}`]),
|
|
856
|
+
DigestRender.#literal(JSON.stringify({
|
|
857
|
+
provider: request.provider, model: request.model, kind: request.kind,
|
|
858
|
+
inferenceCallId: request.inference_call_id, sequence: request.sequence,
|
|
859
|
+
state: request.state, outcome: request.outcome, admission: DigestRender.#admission(m, request),
|
|
860
|
+
startedAt: request.started_at, completedAt: request.completed_at,
|
|
861
|
+
}, null, 2), "json"),
|
|
862
|
+
"## Accounting",
|
|
863
|
+
request.state === "pending" ? "Request is unsettled; accounting is not yet available."
|
|
864
|
+
: DigestRender.#literal(JSON.stringify(DigestRender.#requestAccounting(request), null, 2), "json"),
|
|
865
|
+
"## Dispatched request",
|
|
866
|
+
typeof capture?.body !== "string" ? "Dispatched request body was not retained."
|
|
867
|
+
: `${capture.method} ${capture.origin}\n\nExact serialized body; URL details and request headers are not captured.\n\n${DigestRender.#literal(capture.body, "json")}`,
|
|
868
|
+
];
|
|
869
|
+
const call = m.modelCalls.find(({ id }) => id === request.inference_call_id);
|
|
870
|
+
if (request.outcome === "response" && call?.accepted === 0) {
|
|
871
|
+
lines.push("## Admission", "The provider returned a response, but Plurnk rejected its emission.", DigestRender.#literal(JSON.stringify(DigestRender.parseJson(call.parse_errors, []), null, 2), "json"));
|
|
872
|
+
}
|
|
873
|
+
if (fields.error !== undefined)
|
|
874
|
+
lines.push("## Failure", DigestRender.#literal(JSON.stringify(fields.error, null, 2), "json"));
|
|
875
|
+
if (evidence === null)
|
|
876
|
+
lines.push("## Response", "No settled response evidence is retained.");
|
|
877
|
+
else {
|
|
878
|
+
if (request.outcome === "error")
|
|
879
|
+
lines.push("Partial output; this physical request failed. It is not an admitted response.");
|
|
880
|
+
for (const [field, heading] of [["reasoning", "Reasoning"], ["content", "Content"]]) {
|
|
881
|
+
lines.push(`## ${heading}`, typeof fields[field] === "string"
|
|
882
|
+
? DigestRender.#literal(fields[field]) : `No readable ${field} is retained in this request record.`);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return `${lines.join("\n\n")}\n`;
|
|
886
|
+
}
|
|
729
887
|
// Per-turn forensic files. turnOps is the source authority; PacketWire
|
|
730
888
|
// reproduces provider request slots, and assistantRaw preserves provider bytes.
|
|
731
889
|
// {§share-packet-names}: the stem each turn's packet files carry is its log coordinate, worker, loop and
|
|
@@ -793,21 +951,32 @@ export default class DigestRender {
|
|
|
793
951
|
const padded = stems.get(turn.id);
|
|
794
952
|
const files = [];
|
|
795
953
|
const { packet, packetFailure } = m.evidence.packet(turn);
|
|
954
|
+
const preamble = turn.has_packet === 1 ? DigestRender.#requestPreamble(m, turn) : [];
|
|
796
955
|
if (packetFailure !== null) {
|
|
797
|
-
files.push([`${padded}.
|
|
956
|
+
files.push([`${padded}.request.md`, ["# Request", ...preamble, "Stored packet is invalid; no input messages were reconstructed.",
|
|
957
|
+
DigestRender.#literal(JSON.stringify(packetFailure.error, null, 2), "json"),
|
|
958
|
+
DigestRender.#link(`${padded}.request.md`, `${padded}.packet.raw.txt`, "Stored packet")].join("\n\n")], [`${padded}.packet.raw.txt`, packetFailure.raw], [`${padded}.packet.invalid.json`, JSON.stringify({
|
|
798
959
|
turnId: turn.id,
|
|
799
960
|
error: packetFailure.error,
|
|
800
961
|
}, null, 2)]);
|
|
801
962
|
}
|
|
802
963
|
if (packet !== null) {
|
|
803
|
-
files.push([`${padded}.system.md`, packet.slot("system")], [`${padded}.user.md`, packet.slot("user")]);
|
|
804
964
|
// {§packet-wire-envelope} — the exact text messages the request carried; a stored packet
|
|
805
965
|
// whose log cannot be projected is evidence of its own, never a reason to stop the digest.
|
|
806
966
|
try {
|
|
807
|
-
|
|
967
|
+
const messages = packet.messages();
|
|
968
|
+
if (messages.length > 0)
|
|
969
|
+
files.push([`${padded}.system.md`, packet.slot("system")], [`${padded}.user.md`, packet.slot("user")], [`${padded}.wire.json`, JSON.stringify(messages, null, 2)]);
|
|
970
|
+
files.push([`${padded}.request.md`, DigestRender.request(messages, [
|
|
971
|
+
...preamble,
|
|
972
|
+
...(packet.attachments.length === 0 ? [] : ["## Native attachments", "Stored descriptors; selection alone does not prove transport or provider acceptance.", DigestRender.#literal(JSON.stringify(packet.attachments, null, 2), "json")]),
|
|
973
|
+
])]);
|
|
808
974
|
}
|
|
809
975
|
catch (cause) {
|
|
810
976
|
files.push([`${padded}.wire.invalid.json`, JSON.stringify({ turnId: turn.id, error: cause instanceof Error ? cause.message : String(cause) }, null, 2)]);
|
|
977
|
+
files.push([`${padded}.request.md`, ["# Request", ...preamble,
|
|
978
|
+
"Stored input could not be projected; no message order was reconstructed.",
|
|
979
|
+
DigestRender.#link(`${padded}.request.md`, `${padded}.wire.invalid.json`, "Projection failure")].join("\n\n")]);
|
|
811
980
|
}
|
|
812
981
|
}
|
|
813
982
|
if (source !== null) {
|
|
@@ -844,22 +1013,30 @@ export default class DigestRender {
|
|
|
844
1013
|
}, null, 2));
|
|
845
1014
|
continue;
|
|
846
1015
|
}
|
|
847
|
-
const
|
|
848
|
-
const
|
|
1016
|
+
const retainedResponse = m.evidence.response(attempt.model_call_id);
|
|
1017
|
+
const admission = attempt.accepted === 0 ? "rejected" : "unadmitted";
|
|
1018
|
+
const prefix = `${padded}.attempt${attemptPadded}.${admission}`;
|
|
1019
|
+
write(`${prefix}.parse-errors.json`, JSON.stringify(DigestRender.parseJson(attempt.parse_errors, []), null, 2));
|
|
1020
|
+
write(`${prefix}.attributions.json`, JSON.stringify(DigestRender.parseJson(attempt.attributions, []), null, 2));
|
|
1021
|
+
if (retainedResponse === null) {
|
|
1022
|
+
write(`${prefix}.response.md`, `# Emission attempt ${attempt.sequence}: ${admission}\n\nResponse evidence is not retained.\n`);
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
const response = DigestRender.parseJson(retainedResponse, {});
|
|
849
1026
|
const attemptFiles = [
|
|
850
1027
|
[
|
|
851
1028
|
`${prefix}.assistant.md`,
|
|
852
1029
|
typeof response.assistant?.content === "string" ? response.assistant.content : "",
|
|
853
1030
|
],
|
|
854
1031
|
[`${prefix}.response.json`, JSON.stringify(response, null, 2)],
|
|
855
|
-
[`${prefix}.parse-errors.json`, JSON.stringify(DigestRender.parseJson(attempt.parse_errors, []), null, 2)],
|
|
856
|
-
[`${prefix}.attributions.json`, JSON.stringify(DigestRender.parseJson(attempt.attributions, []), null, 2)],
|
|
857
1032
|
];
|
|
858
1033
|
for (const [file, body] of attemptFiles)
|
|
859
1034
|
write(file, body);
|
|
860
1035
|
}
|
|
861
1036
|
});
|
|
862
1037
|
for (const request of m.providerRequests) {
|
|
1038
|
+
const evidence = DigestRender.parseJson(m.evidence.request(request.id));
|
|
1039
|
+
write(`requests/${request.id}.md`, DigestRender.#physicalRequest(m, request, evidence));
|
|
863
1040
|
write(`requests/${request.id}.json`, JSON.stringify({
|
|
864
1041
|
id: request.id,
|
|
865
1042
|
inferenceCallId: request.inference_call_id,
|
|
@@ -870,7 +1047,7 @@ export default class DigestRender {
|
|
|
870
1047
|
startedAt: request.started_at,
|
|
871
1048
|
completedAt: request.completed_at,
|
|
872
1049
|
accounting: request.state === "settled" ? DigestRender.#requestAccounting(request) : null,
|
|
873
|
-
evidence
|
|
1050
|
+
evidence,
|
|
874
1051
|
}, null, 2));
|
|
875
1052
|
}
|
|
876
1053
|
return written;
|
|
@@ -889,12 +1066,13 @@ export default class DigestRender {
|
|
|
889
1066
|
id: r.id,
|
|
890
1067
|
workspace_id: r.workspace_id,
|
|
891
1068
|
name: r.name,
|
|
1069
|
+
owner: r.owner,
|
|
892
1070
|
accounting: DigestRender.#accounting(m.requestsByWorker.get(r.id) ?? []),
|
|
893
1071
|
edit_census: DigestRender.#editCensus(m).byWorker.get(r.id) ?? null,
|
|
894
1072
|
})),
|
|
895
1073
|
loops: m.loops.map((l) => ({
|
|
896
1074
|
id: l.id, worker_id: l.worker_id, sequence: l.sequence, status: l.status,
|
|
897
|
-
prompt: l.prompt,
|
|
1075
|
+
prompt: l.prompt,
|
|
898
1076
|
terminated_by: l.terminated_by,
|
|
899
1077
|
claimed_at: l.claimed_at,
|
|
900
1078
|
terminated_at: l.terminated_at,
|
|
@@ -989,6 +1167,8 @@ export default class DigestRender {
|
|
|
989
1167
|
id: le.id, worker_id: le.worker_id, loop_id: le.loop_id,
|
|
990
1168
|
turn_id: le.turn_id, sequence: le.sequence, origin: le.origin,
|
|
991
1169
|
source: le.source, model_call_id: le.model_call_id,
|
|
1170
|
+
...(le.inherited_history === undefined ? {} : { inherited_history: le.inherited_history === 1 }),
|
|
1171
|
+
...(le.ambient_event_id === undefined ? {} : { ambient_event_id: le.ambient_event_id }),
|
|
992
1172
|
attrs: DigestRender.parseJson(le.attrs, {}),
|
|
993
1173
|
op: le.op, target: DigestRender.#renderTarget(le),
|
|
994
1174
|
status_rx: le.status_rx, state: le.state, outcome: le.outcome,
|