narraeon 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/node/protocol/appPreferences.js +25 -0
- package/dist/node/protocol/conversationObservation.js +67 -0
- package/dist/node/protocol/v1.js +188 -34
- package/dist/node/protocol/worldExtensions.js +16 -0
- package/dist/node/protocol/worldMaintenance.js +77 -0
- package/dist/node/protocol/worldPromptDiagnostics.js +1 -0
- package/dist/node/runtime/ConversationChanges.js +24 -0
- package/dist/node/runtime/V1Runtime.js +447 -121
- package/dist/node/runtime/artifact/FileNativeArtifactStore.js +326 -12
- package/dist/node/runtime/authoring/AuthoringRequestSnapshot.js +63 -0
- package/dist/node/runtime/authoring/ModelToolConversation.js +218 -0
- package/dist/node/runtime/config/AppPreferencesStore.js +46 -17
- package/dist/node/runtime/content/ContentTreeFingerprint.js +13 -0
- package/dist/node/runtime/content/ContentWorkspace.js +43 -13
- package/dist/node/runtime/content/CurrentTreeContentLibrary.js +128 -46
- package/dist/node/runtime/content/FileNativeContentTree.js +28 -10
- package/dist/node/runtime/content/PackageFollowups.js +80 -0
- package/dist/node/runtime/extension/FrontendExtensionBundle.js +19 -14
- package/dist/node/runtime/extension/PackageScriptPermissions.js +70 -0
- package/dist/node/runtime/extension/WorldExtensionControls.js +205 -0
- package/dist/node/runtime/extension/WorldExtensionRequests.js +64 -0
- package/dist/node/runtime/model/FileNativeModelAdapters.js +21 -466
- package/dist/node/runtime/model/ModelConnectionStore.js +44 -10
- package/dist/node/runtime/model/ModelHost.js +31 -1
- package/dist/node/runtime/model/ModelHostStream.js +51 -5
- package/dist/node/runtime/play/FileNativeContinuityCorrection.js +55 -45
- package/dist/node/runtime/play/FileNativePlayPresetStore.js +256 -238
- package/dist/node/runtime/play/FileNativePlayTimelineStore.js +118 -9
- package/dist/node/runtime/play/OrderedFollowups.js +71 -0
- package/dist/node/runtime/play/OrderedPlayPrompts.js +180 -0
- package/dist/node/runtime/play/PlayCallChain.js +814 -61
- package/dist/node/runtime/play/PlayContinuity.js +88 -0
- package/dist/node/runtime/play/PlayDocumentTools.js +205 -287
- package/dist/node/runtime/play/PlayFollowupRequests.js +34 -3
- package/dist/node/runtime/play/PlayPresetWorkbench.js +45 -54
- package/dist/node/runtime/play/PlayPromptRun.js +86 -0
- package/dist/node/runtime/prompt/FileNativePromptCompiler.js +670 -93
- package/dist/node/runtime/prompt/FileNativeToolRegistry.js +37 -21
- package/dist/node/runtime/prompt/PromptCompilationCodec.js +322 -0
- package/dist/node/runtime/prompt/WorldMaintenanceReport.js +251 -0
- package/dist/node/runtime/prompt/WorldMaterialCoverage.js +83 -0
- package/dist/node/runtime/prompt/WorldPromptDiagnostics.js +185 -0
- package/dist/node/runtime/setting/FileNativeSettingImprovementStore.js +1027 -0
- package/dist/node/runtime/setting/SettingAuthoringTransaction.js +1339 -0
- package/dist/node/runtime/setting/SettingImprovementSession.js +714 -0
- package/dist/node/runtime/world/FileNativeAuthorityV3.js +88 -1
- package/dist/node/runtime/world/FileNativeWorldStore.js +292 -27
- package/dist/node/runtime/world/PlayerViewBindings.js +30 -0
- package/dist/node/runtime/world/PlayerViewRenderer.js +27 -34
- package/dist/node/runtime/world/WorldDocumentMaintenance.js +174 -0
- package/dist/node/runtime/world/WorldDocumentStore.js +166 -96
- package/dist/node/runtime/world/WorldOperationCoordinator.js +103 -2
- package/dist/node/runtime/world-revision/FileNativeWorldRevisionStore.js +696 -0
- package/dist/node/runtime/world-revision/WorldRevisionAuthoringTransaction.js +380 -0
- package/dist/node/runtime/world-revision/WorldRevisionSession.js +709 -0
- package/dist/node/runtime/world-revision/WorldRevisionWorkspace.js +494 -0
- package/dist/node/server/ConversationStream.js +109 -0
- package/dist/node/server/createServer.js +5 -1
- package/dist/node/shared/artifact-payload-contract.js +123 -0
- package/dist/node/shared/default-play-prompts.js +25 -45
- package/dist/node/shared/default-preset-host.js +114 -138
- package/dist/node/shared/default-setting-improvement-prompt.js +52 -38
- package/dist/node/shared/display-regex.js +114 -0
- package/dist/node/shared/first-party-action-choices.js +1 -6
- package/dist/node/shared/first-party-player-view.js +1 -6
- package/dist/node/shared/ordered-author-prompts.js +64 -0
- package/dist/node/shared/ordered-followups.js +42 -0
- package/dist/node/shared/ordered-play-prompts.js +73 -0
- package/dist/web/assets/index-BXfelCi9.js +1207 -0
- package/dist/web/assets/index-BYnhUxlk.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/node/runtime/model/SettingAuthorStream.js +0 -21
- package/dist/node/runtime/setting/DocumentCandidateSettingImprovement.js +0 -1731
- package/dist/web/assets/index-D5VqDvjY.js +0 -1192
- package/dist/web/assets/index-Xa0s35CJ.css +0 -1
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { worldWritePositionText } from "../../protocol/worldMaintenance.js";
|
|
2
|
+
import { parseDocument } from "yaml";
|
|
3
|
+
export function worldMaintenanceSettings(frame) {
|
|
4
|
+
const slotIds = new Set();
|
|
5
|
+
for (const entry of Array.isArray(frame.context) ? frame.context : []) {
|
|
6
|
+
const slot = record(entry) && record(entry.slot) ? entry.slot : null;
|
|
7
|
+
if (slot === null ||
|
|
8
|
+
(slot.id === undefined && slot.advisoryBytes === undefined))
|
|
9
|
+
continue;
|
|
10
|
+
if (typeof slot.id !== "string" ||
|
|
11
|
+
!/^[a-z][a-z0-9_-]{0,63}$/u.test(slot.id) ||
|
|
12
|
+
slotIds.has(slot.id))
|
|
13
|
+
throw new Error("Advisory slots require unique stable slot.id values");
|
|
14
|
+
slotIds.add(slot.id);
|
|
15
|
+
if (slot.advisoryBytes !== undefined && !positiveBytes(slot.advisoryBytes))
|
|
16
|
+
throw new Error("slot.advisoryBytes must be a positive UTF-8 byte limit");
|
|
17
|
+
}
|
|
18
|
+
const value = frame.maintenance;
|
|
19
|
+
if (value === undefined)
|
|
20
|
+
return { documents: {} };
|
|
21
|
+
if (!record(value) ||
|
|
22
|
+
Object.keys(value).some((key) => key !== "documents" && key !== "clock"))
|
|
23
|
+
throw new Error("world frame.maintenance accepts only documents and clock");
|
|
24
|
+
const documents = value.documents ?? {};
|
|
25
|
+
if (!record(documents) ||
|
|
26
|
+
Object.entries(documents).some(([key, limit]) => !/^@[a-z][a-z0-9-]*$/u.test(key) || !positiveBytes(limit)))
|
|
27
|
+
throw new Error("maintenance.documents maps document @short-refs to positive advisory UTF-8 byte limits");
|
|
28
|
+
if (value.clock === undefined)
|
|
29
|
+
return { documents: documents };
|
|
30
|
+
const clock = value.clock;
|
|
31
|
+
if (!record(clock) ||
|
|
32
|
+
Object.keys(clock).length !== 2 ||
|
|
33
|
+
typeof clock.document !== "string" ||
|
|
34
|
+
!clock.document.startsWith("@") ||
|
|
35
|
+
!record(clock.locator) ||
|
|
36
|
+
Object.keys(clock.locator).length !== 1)
|
|
37
|
+
throw new Error("maintenance.clock requires an exact document @short-ref and locator");
|
|
38
|
+
const path = clock.locator.yaml ?? clock.locator.markdown;
|
|
39
|
+
if (!Array.isArray(path) ||
|
|
40
|
+
path.length === 0 ||
|
|
41
|
+
!path.every((part) => typeof part === "string") ||
|
|
42
|
+
(!Object.hasOwn(clock.locator, "yaml") &&
|
|
43
|
+
!Object.hasOwn(clock.locator, "markdown")))
|
|
44
|
+
throw new Error("maintenance.clock.locator must name an exact YAML or Markdown node");
|
|
45
|
+
return {
|
|
46
|
+
documents: documents,
|
|
47
|
+
clock: {
|
|
48
|
+
document: clock.document,
|
|
49
|
+
locator: Object.hasOwn(clock.locator, "yaml")
|
|
50
|
+
? { yaml: path }
|
|
51
|
+
: { markdown: path },
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function listWorldDocuments(snapshot) {
|
|
56
|
+
const result = [];
|
|
57
|
+
const pending = [""];
|
|
58
|
+
const visited = new Set();
|
|
59
|
+
while (pending.length > 0) {
|
|
60
|
+
const directory = pending.shift();
|
|
61
|
+
if (visited.has(directory))
|
|
62
|
+
continue;
|
|
63
|
+
visited.add(directory);
|
|
64
|
+
let cursor = null;
|
|
65
|
+
do {
|
|
66
|
+
const page = snapshot.query({
|
|
67
|
+
kind: "catalog",
|
|
68
|
+
directory,
|
|
69
|
+
limit: 100,
|
|
70
|
+
cursor,
|
|
71
|
+
});
|
|
72
|
+
if (page.kind !== "catalog")
|
|
73
|
+
throw new Error("World-document inventory could not be read");
|
|
74
|
+
for (const entry of page.entries) {
|
|
75
|
+
if (entry.kind === "directory") {
|
|
76
|
+
if (!entry.logicalPath.startsWith(`${snapshot.logicalRoot}/`))
|
|
77
|
+
throw new Error("World-document inventory escaped its logical root");
|
|
78
|
+
pending.push(entry.logicalPath.slice(snapshot.logicalRoot.length + 1));
|
|
79
|
+
}
|
|
80
|
+
else if (entry.document !== undefined)
|
|
81
|
+
result.push(entry.document);
|
|
82
|
+
}
|
|
83
|
+
cursor = page.page.nextCursor;
|
|
84
|
+
} while (cursor !== null);
|
|
85
|
+
}
|
|
86
|
+
return result.sort((a, b) => a.logicalPath.localeCompare(b.logicalPath));
|
|
87
|
+
}
|
|
88
|
+
export function measureWorldMaterials(snapshot, frame, materials, facts = {}) {
|
|
89
|
+
const settings = worldMaintenanceSettings(frame);
|
|
90
|
+
const contributions = new Map();
|
|
91
|
+
for (const material of materials)
|
|
92
|
+
for (const contribution of material.contributions ?? [])
|
|
93
|
+
contributions.set(contribution.shortRef, (contributions.get(contribution.shortRef) ?? 0) + contribution.bytes);
|
|
94
|
+
const documents = listWorldDocuments(snapshot).map((document) => {
|
|
95
|
+
const read = snapshot.query({
|
|
96
|
+
kind: "read_document",
|
|
97
|
+
document: { shortRef: document.shortRef },
|
|
98
|
+
});
|
|
99
|
+
if (read.kind !== "read_document")
|
|
100
|
+
throw new Error(`Document @${document.shortRef} could not be measured`);
|
|
101
|
+
const bodyBytes = Buffer.byteLength(read.body, "utf8");
|
|
102
|
+
const fact = facts[document.shortRef] ?? {
|
|
103
|
+
baselineKind: "unavailable",
|
|
104
|
+
baselineBytes: null,
|
|
105
|
+
lastBodyWrite: null,
|
|
106
|
+
lastMetadataWrite: null,
|
|
107
|
+
bodyChangedContexts: 0,
|
|
108
|
+
metadataChangedContexts: 0,
|
|
109
|
+
};
|
|
110
|
+
const advisoryBytes = settings.documents[`@${document.shortRef}`] ?? null;
|
|
111
|
+
return {
|
|
112
|
+
...fact,
|
|
113
|
+
shortRef: document.shortRef,
|
|
114
|
+
title: document.title,
|
|
115
|
+
bodyBytes,
|
|
116
|
+
injectedBytes: contributions.get(document.shortRef) ?? 0,
|
|
117
|
+
growthBytes: fact.baselineBytes === null ? null : bodyBytes - fact.baselineBytes,
|
|
118
|
+
growthRatio: fact.baselineBytes === null || fact.baselineBytes === 0
|
|
119
|
+
? null
|
|
120
|
+
: bodyBytes / fact.baselineBytes,
|
|
121
|
+
advisoryBytes,
|
|
122
|
+
overAdvisory: advisoryBytes !== null && bodyBytes > advisoryBytes,
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
const slots = (Array.isArray(frame.context) ? frame.context : []).flatMap((entry) => {
|
|
126
|
+
const slot = record(entry) && record(entry.slot) ? entry.slot : null;
|
|
127
|
+
if (slot === null ||
|
|
128
|
+
(slot.id === undefined && slot.advisoryBytes === undefined))
|
|
129
|
+
return [];
|
|
130
|
+
// worldMaintenanceSettings validated all declared slot ids and limits.
|
|
131
|
+
const id = slot.id;
|
|
132
|
+
const bytes = materials
|
|
133
|
+
.filter(({ slotId }) => slotId === slot.id)
|
|
134
|
+
.reduce((sum, { markdown }) => sum + Buffer.byteLength(markdown, "utf8"), 0);
|
|
135
|
+
const advisoryBytes = typeof slot.advisoryBytes === "number" ? slot.advisoryBytes : null;
|
|
136
|
+
return [
|
|
137
|
+
{
|
|
138
|
+
id,
|
|
139
|
+
bytes,
|
|
140
|
+
advisoryBytes,
|
|
141
|
+
overAdvisory: advisoryBytes !== null && bytes > advisoryBytes,
|
|
142
|
+
documents: [
|
|
143
|
+
...new Set(materials
|
|
144
|
+
.filter(({ slotId }) => slotId === slot.id)
|
|
145
|
+
.flatMap(({ contributions: items }) => items?.map(({ shortRef }) => shortRef) ?? [])),
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
unit: "utf8_bytes",
|
|
152
|
+
worldMaterialsBytes: 0,
|
|
153
|
+
checkpointHistoryBytes: materials
|
|
154
|
+
.filter(({ source }) => source.startsWith("runtime:checkpoint-history:"))
|
|
155
|
+
.reduce((sum, { markdown }) => sum + Buffer.byteLength(markdown, "utf8"), 0),
|
|
156
|
+
authorInstructionBytes: 0,
|
|
157
|
+
runtimeInstructionBytes: 0,
|
|
158
|
+
toolDefinitionBytes: 0,
|
|
159
|
+
documents,
|
|
160
|
+
slots,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
export function refreshMaintenanceTotals(compilation) {
|
|
164
|
+
const report = compilation.maintenance;
|
|
165
|
+
if (report === undefined)
|
|
166
|
+
return;
|
|
167
|
+
const bytes = (role) => compilation.logicalMessages
|
|
168
|
+
.filter((message) => message.role === role)
|
|
169
|
+
.reduce((sum, { markdown }) => sum + Buffer.byteLength(markdown, "utf8"), 0);
|
|
170
|
+
report.worldMaterialsBytes = bytes("world_context");
|
|
171
|
+
report.authorInstructionBytes = bytes("author_instruction");
|
|
172
|
+
report.runtimeInstructionBytes = bytes("runtime_system");
|
|
173
|
+
report.toolDefinitionBytes = Buffer.byteLength(JSON.stringify(compilation.tools), "utf8");
|
|
174
|
+
}
|
|
175
|
+
export function renderWriteSizeAdvice(report, shortRef, previousBodyBytes, locale) {
|
|
176
|
+
const document = report.documents.find((entry) => entry.shortRef === shortRef);
|
|
177
|
+
if (document === undefined)
|
|
178
|
+
return "";
|
|
179
|
+
const zh = locale === "zh-CN";
|
|
180
|
+
const lines = [];
|
|
181
|
+
if (document.overAdvisory) {
|
|
182
|
+
const delta = previousBodyBytes === null
|
|
183
|
+
? zh
|
|
184
|
+
? "本次新建"
|
|
185
|
+
: "new document"
|
|
186
|
+
: `${zh ? "本次变化" : "change"} ${document.bodyBytes - previousBodyBytes >= 0 ? "+" : ""}${document.bodyBytes - previousBodyBytes}`;
|
|
187
|
+
lines.push(zh
|
|
188
|
+
? `整理提示:正文 ${document.bodyBytes} UTF-8 字节,建议上限 ${document.advisoryBytes};${delta}。`
|
|
189
|
+
: `Maintenance advice: body ${document.bodyBytes} UTF-8 bytes, advisory limit ${document.advisoryBytes}; ${delta}.`);
|
|
190
|
+
}
|
|
191
|
+
for (const slot of report.slots.filter((entry) => entry.overAdvisory && entry.documents.includes(shortRef)))
|
|
192
|
+
lines.push(zh
|
|
193
|
+
? `材料槽 ${slot.id}:${slot.bytes} UTF-8 字节,建议上限 ${slot.advisoryBytes}。`
|
|
194
|
+
: `Material slot ${slot.id}: ${slot.bytes} UTF-8 bytes, advisory limit ${slot.advisoryBytes}.`);
|
|
195
|
+
return lines.join("\n");
|
|
196
|
+
}
|
|
197
|
+
export function readDeclaredWorldClock(snapshot) {
|
|
198
|
+
const source = snapshot.files.find(({ path }) => path === "control/frame.yaml")?.contents;
|
|
199
|
+
if (source === undefined)
|
|
200
|
+
return undefined;
|
|
201
|
+
const parsed = parseDocument(source);
|
|
202
|
+
if (parsed.errors.length > 0)
|
|
203
|
+
return undefined;
|
|
204
|
+
const frame = parsed.toJS();
|
|
205
|
+
if (!record(frame))
|
|
206
|
+
return undefined;
|
|
207
|
+
const clock = worldMaintenanceSettings(frame).clock;
|
|
208
|
+
if (clock === undefined)
|
|
209
|
+
return undefined;
|
|
210
|
+
const selected = snapshot.query({
|
|
211
|
+
kind: "select_node",
|
|
212
|
+
document: { shortRef: clock.document.slice(1) },
|
|
213
|
+
locator: clock.locator,
|
|
214
|
+
});
|
|
215
|
+
if (selected.kind !== "select_node")
|
|
216
|
+
return undefined;
|
|
217
|
+
if (selected.node.codec === "markdown")
|
|
218
|
+
return selected.node.markdown;
|
|
219
|
+
const value = selected.node.value;
|
|
220
|
+
return typeof value === "string" ||
|
|
221
|
+
typeof value === "number" ||
|
|
222
|
+
typeof value === "boolean"
|
|
223
|
+
? String(value)
|
|
224
|
+
: undefined;
|
|
225
|
+
}
|
|
226
|
+
function positiveBytes(value) {
|
|
227
|
+
return Number.isSafeInteger(value) && Number(value) > 0;
|
|
228
|
+
}
|
|
229
|
+
function record(value) {
|
|
230
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
231
|
+
}
|
|
232
|
+
export function renderDocumentWritePosition(shortRef, fact, locale) {
|
|
233
|
+
if (fact === undefined)
|
|
234
|
+
return `${locale === "zh-CN" ? "最近写入位置" : "Last committed write"} @${shortRef}: ${locale === "zh-CN" ? "未记录" : "not recorded"}`;
|
|
235
|
+
return locale === "zh-CN"
|
|
236
|
+
? `最近写入位置 @${shortRef}:\n正文:${worldWritePositionText(fact.lastBodyWrite, locale)}\n元信息:${worldWritePositionText(fact.lastMetadataWrite, locale)}`
|
|
237
|
+
: `Last committed write @${shortRef}:\nBody: ${worldWritePositionText(fact.lastBodyWrite, locale)}\nMetadata: ${worldWritePositionText(fact.lastMetadataWrite, locale)}`;
|
|
238
|
+
}
|
|
239
|
+
export function renderWorldMaintenanceReport(report, locale) {
|
|
240
|
+
if (report === undefined)
|
|
241
|
+
return "";
|
|
242
|
+
const zh = locale === "zh-CN";
|
|
243
|
+
return [
|
|
244
|
+
zh
|
|
245
|
+
? "# 文档整理报告(UTF-8 字节;超限仅提示)"
|
|
246
|
+
: "# Document maintenance (UTF-8 bytes; advisory only)",
|
|
247
|
+
`${zh ? "世界材料 / 其中检查点历史 / 作者提示 / Runtime 提示 / 工具定义" : "World materials / checkpoint history subset / author instructions / Runtime instructions / tool definitions"}: ${report.worldMaterialsBytes} / ${report.checkpointHistoryBytes} / ${report.authorInstructionBytes} / ${report.runtimeInstructionBytes} / ${report.toolDefinitionBytes}`,
|
|
248
|
+
...report.documents.map((document) => `@${document.shortRef}: ${zh ? "正文 / 注入贡献 / 建议上限 / 基准 / 增长" : "body / injection contribution / advisory / baseline / growth"}: ${document.bodyBytes} / ${document.injectedBytes} / ${document.advisoryBytes ?? "—"} / ${document.baselineBytes ?? "—"} / ${document.growthBytes ?? "—"}${document.overAdvisory ? (zh ? "(超过建议)" : " (over advisory)") : ""}`),
|
|
249
|
+
...report.slots.map((slot) => `${zh ? "材料槽" : "Slot"} ${slot.id}: ${slot.bytes} / ${slot.advisoryBytes ?? "—"}${slot.overAdvisory ? (zh ? "(超过建议)" : " (over advisory)") : ""}`),
|
|
250
|
+
].join("\n");
|
|
251
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/** Body scopes and catalog summaries are independent kinds of exposure. */
|
|
2
|
+
export function worldMaterialCoverage(coverage) {
|
|
3
|
+
const fullInjected = new Set();
|
|
4
|
+
const nodeInjected = new Set();
|
|
5
|
+
const catalogSummary = new Set();
|
|
6
|
+
const injectedSelections = coverage.flatMap((entry) => {
|
|
7
|
+
for (const ref of entry.catalogEntries ?? [])
|
|
8
|
+
catalogSummary.add(ref);
|
|
9
|
+
const scope = entry.readAuthorization;
|
|
10
|
+
if (scope === undefined)
|
|
11
|
+
return [];
|
|
12
|
+
(scope.locator === null ? fullInjected : nodeInjected).add(scope.shortRef);
|
|
13
|
+
return [scope];
|
|
14
|
+
});
|
|
15
|
+
return { fullInjected, nodeInjected, catalogSummary, injectedSelections };
|
|
16
|
+
}
|
|
17
|
+
export function renderFreshContextCoverage(snapshot, coverage, shortRef, locale = "en") {
|
|
18
|
+
const exposure = worldMaterialCoverage(coverage);
|
|
19
|
+
const full = exposure.fullInjected.has(shortRef);
|
|
20
|
+
const nodes = exposure.nodeInjected.has(shortRef);
|
|
21
|
+
const summary = exposure.catalogSummary.has(shortRef);
|
|
22
|
+
const zh = locale === "zh-CN";
|
|
23
|
+
const scope = full
|
|
24
|
+
? zh
|
|
25
|
+
? "完整正文"
|
|
26
|
+
: "full body"
|
|
27
|
+
: nodes
|
|
28
|
+
? zh
|
|
29
|
+
? "指定节点"
|
|
30
|
+
: "selected nodes"
|
|
31
|
+
: summary
|
|
32
|
+
? zh
|
|
33
|
+
? "仅目录摘要"
|
|
34
|
+
: "catalog summary only"
|
|
35
|
+
: zh
|
|
36
|
+
? "不直接注入,需要按需读取"
|
|
37
|
+
: "not directly injected; read on demand";
|
|
38
|
+
const lines = [
|
|
39
|
+
`${zh ? "如果现在新开上下文" : "If a fresh context started now"}: ${scope}${nodes && !full && summary ? (zh ? ",以及目录摘要" : " and catalog summary") : ""}`,
|
|
40
|
+
];
|
|
41
|
+
if (!full && nodes) {
|
|
42
|
+
const scopes = exposure.injectedSelections.filter((entry) => entry.shortRef === shortRef && entry.locator !== null);
|
|
43
|
+
lines.push(`${zh ? "节点范围" : "Node scopes"}: ${scopes.map(({ locator }) => JSON.stringify(locator)).join(", ")}`);
|
|
44
|
+
}
|
|
45
|
+
if (!full && summary) {
|
|
46
|
+
const read = snapshot.query({
|
|
47
|
+
kind: "read_document",
|
|
48
|
+
document: { shortRef },
|
|
49
|
+
});
|
|
50
|
+
if (read.kind === "read_document")
|
|
51
|
+
lines.push(`${zh ? "摘要" : "Summary"}: ${read.document.summary}`);
|
|
52
|
+
}
|
|
53
|
+
if (!full && !nodes && !summary) {
|
|
54
|
+
const references = exposure.injectedSelections.flatMap((selection) => {
|
|
55
|
+
const read = snapshot.query({
|
|
56
|
+
kind: "read_document",
|
|
57
|
+
document: { shortRef: selection.shortRef },
|
|
58
|
+
});
|
|
59
|
+
const locator = selection.locator ??
|
|
60
|
+
(read.kind === "read_document" && read.codec === "yaml"
|
|
61
|
+
? { yaml: [] }
|
|
62
|
+
: null);
|
|
63
|
+
if (locator === null)
|
|
64
|
+
return [];
|
|
65
|
+
const node = snapshot.query({
|
|
66
|
+
kind: "select_node",
|
|
67
|
+
document: { shortRef: selection.shortRef },
|
|
68
|
+
locator,
|
|
69
|
+
});
|
|
70
|
+
return node.kind === "select_node" &&
|
|
71
|
+
node.references.some(({ target }) => target.shortRef === shortRef)
|
|
72
|
+
? [`@${selection.shortRef}`]
|
|
73
|
+
: [];
|
|
74
|
+
});
|
|
75
|
+
if (references.length > 0)
|
|
76
|
+
lines.push(`${zh ? "已注入材料中的直接引用来自" : "Direct reference in injected material"}: ${[...new Set(references)].join(", ")}`);
|
|
77
|
+
else if (coverage.some(({ status }) => status === "paged_catalog"))
|
|
78
|
+
lines.push(zh
|
|
79
|
+
? "目录仍有后续页,可使用 state_list 继续查找。"
|
|
80
|
+
: "Catalogs have further pages; use state_list to continue discovery.");
|
|
81
|
+
}
|
|
82
|
+
return lines.join("\n");
|
|
83
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/** Diagnostic comparison only: never edits the compiler or Provider cache routing. */
|
|
2
|
+
export function comparePromptPrefixes(previous, current) {
|
|
3
|
+
const previousEncoding = previous?.encoding;
|
|
4
|
+
const currentEncoding = current.encoding;
|
|
5
|
+
const beforeBlocks = previous === null ? [] : sourceBlocks(previous.bootstrap);
|
|
6
|
+
const afterBlocks = sourceBlocks(current.bootstrap);
|
|
7
|
+
const before = new Map(beforeBlocks.map((block) => [block.key, block]));
|
|
8
|
+
const after = new Map(afterBlocks.map((block) => [block.key, block]));
|
|
9
|
+
const changedSources = [];
|
|
10
|
+
if (previous !== null)
|
|
11
|
+
for (const key of new Set([...before.keys(), ...after.keys()])) {
|
|
12
|
+
const left = before.get(key);
|
|
13
|
+
const right = after.get(key);
|
|
14
|
+
const change = left === undefined
|
|
15
|
+
? "added"
|
|
16
|
+
: right === undefined
|
|
17
|
+
? "removed"
|
|
18
|
+
: left.markdown !== right.markdown
|
|
19
|
+
? "changed"
|
|
20
|
+
: left.position !== right.position
|
|
21
|
+
? "moved"
|
|
22
|
+
: null;
|
|
23
|
+
if (change !== null)
|
|
24
|
+
changedSources.push({
|
|
25
|
+
source: (right ?? left).source,
|
|
26
|
+
role: (right ?? left).role,
|
|
27
|
+
change,
|
|
28
|
+
previousPosition: left?.position ?? null,
|
|
29
|
+
currentPosition: right?.position ?? null,
|
|
30
|
+
previousBytes: left?.bytes ?? 0,
|
|
31
|
+
currentBytes: right?.bytes ?? 0,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const previousText = previous === null ? "" : promptText(previous.bootstrap);
|
|
35
|
+
const currentText = promptText(current.bootstrap);
|
|
36
|
+
const prefix = commonBytes(previousText, currentText);
|
|
37
|
+
const completeEncoding = previousEncoding != null && currentEncoding != null;
|
|
38
|
+
const previousBody = previousEncoding?.body;
|
|
39
|
+
const currentBody = currentEncoding?.body;
|
|
40
|
+
const beforeKey = cacheKey(previousBody);
|
|
41
|
+
const afterKey = cacheKey(currentBody);
|
|
42
|
+
const suggestions = [];
|
|
43
|
+
for (const changed of changedSources) {
|
|
44
|
+
if (changed.role !== "world_context" ||
|
|
45
|
+
changed.change !== "changed" ||
|
|
46
|
+
changed.currentPosition === null)
|
|
47
|
+
continue;
|
|
48
|
+
const stableLater = afterBlocks.find((block) => block.role === "world_context" &&
|
|
49
|
+
block.position > changed.currentPosition &&
|
|
50
|
+
block.bytes >= changed.currentBytes &&
|
|
51
|
+
before.get(block.key)?.markdown === block.markdown);
|
|
52
|
+
if (stableLater !== undefined)
|
|
53
|
+
suggestions.push({
|
|
54
|
+
source: changed.source,
|
|
55
|
+
afterSource: stableLater.source,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
logical: previous === null
|
|
60
|
+
? null
|
|
61
|
+
: {
|
|
62
|
+
commonPrefixBytes: prefix,
|
|
63
|
+
previousBytes: bytes(previousText),
|
|
64
|
+
currentBytes: bytes(currentText),
|
|
65
|
+
firstChangedSource: firstChangedSource(current.bootstrap, prefix),
|
|
66
|
+
changedSources,
|
|
67
|
+
currentOrder: afterBlocks.map(({ source, role, position, bytes }) => ({
|
|
68
|
+
source,
|
|
69
|
+
role,
|
|
70
|
+
position,
|
|
71
|
+
bytes,
|
|
72
|
+
})),
|
|
73
|
+
},
|
|
74
|
+
encoding: {
|
|
75
|
+
previous: previousEncoding == null ? null : summarizeEncoding(previousEncoding),
|
|
76
|
+
current: currentEncoding == null ? null : summarizeEncoding(currentEncoding),
|
|
77
|
+
commonJsonPrefixBytes: completeEncoding
|
|
78
|
+
? commonBytes(JSON.stringify(previousBody), JSON.stringify(currentBody))
|
|
79
|
+
: null,
|
|
80
|
+
firstChangedPath: completeEncoding
|
|
81
|
+
? firstChangedPath(previousBody, currentBody)
|
|
82
|
+
: null,
|
|
83
|
+
cacheKey: !completeEncoding
|
|
84
|
+
? "unavailable"
|
|
85
|
+
: beforeKey === undefined && afterKey === undefined
|
|
86
|
+
? "absent"
|
|
87
|
+
: beforeKey === afterKey
|
|
88
|
+
? "same"
|
|
89
|
+
: "different",
|
|
90
|
+
},
|
|
91
|
+
providerUsage: {
|
|
92
|
+
previous: previous?.usage ?? null,
|
|
93
|
+
current: current.usage ?? null,
|
|
94
|
+
},
|
|
95
|
+
suggestions,
|
|
96
|
+
cacheBenefit: "not_measured",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function sourceBlocks(bootstrap) {
|
|
100
|
+
const seen = new Map();
|
|
101
|
+
return bootstrap.logicalMessages
|
|
102
|
+
.flatMap(({ role, blocks }) => blocks.map(({ source, markdown }) => ({ role, source, markdown })))
|
|
103
|
+
.map((block, index) => {
|
|
104
|
+
const identity = `${block.role}\0${block.source}`;
|
|
105
|
+
const occurrence = seen.get(identity) ?? 0;
|
|
106
|
+
seen.set(identity, occurrence + 1);
|
|
107
|
+
return {
|
|
108
|
+
...block,
|
|
109
|
+
key: `${identity}\0${occurrence}`,
|
|
110
|
+
position: index + 1,
|
|
111
|
+
bytes: bytes(block.markdown),
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function promptText(bootstrap) {
|
|
116
|
+
return bootstrap.logicalMessages.map(({ markdown }) => markdown).join("\n\n");
|
|
117
|
+
}
|
|
118
|
+
function firstChangedSource(bootstrap, offset) {
|
|
119
|
+
let start = 0;
|
|
120
|
+
for (const message of bootstrap.logicalMessages) {
|
|
121
|
+
let search = 0;
|
|
122
|
+
for (const block of message.blocks) {
|
|
123
|
+
const exact = block.markdown.trim();
|
|
124
|
+
const index = message.markdown.indexOf(exact, search);
|
|
125
|
+
if (index < 0)
|
|
126
|
+
continue;
|
|
127
|
+
const end = start + bytes(message.markdown.slice(0, index + exact.length));
|
|
128
|
+
if (offset < end)
|
|
129
|
+
return block.source;
|
|
130
|
+
search = index + exact.length;
|
|
131
|
+
}
|
|
132
|
+
start += bytes(message.markdown) + 2;
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
function summarizeEncoding(request) {
|
|
137
|
+
const leaves = scalarPaths(request.body);
|
|
138
|
+
return {
|
|
139
|
+
provider: request.provider,
|
|
140
|
+
jsonBytes: bytes(JSON.stringify(request.body)),
|
|
141
|
+
contentBlocks: leaves
|
|
142
|
+
.filter(({ path, value }) => typeof value === "string" &&
|
|
143
|
+
/(?:^|\.)(?:text|content|instructions)$/u.test(path))
|
|
144
|
+
.map(({ path, value }) => ({ path, bytes: bytes(String(value)) })),
|
|
145
|
+
cacheBreakpoints: leaves
|
|
146
|
+
.filter(({ path, value }) => path.endsWith("cache_control.type") && value === "ephemeral")
|
|
147
|
+
.map(({ path }) => path.slice(0, -".type".length)),
|
|
148
|
+
hasCacheKey: cacheKey(request.body) !== undefined,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function scalarPaths(value, path = "") {
|
|
152
|
+
if (Array.isArray(value))
|
|
153
|
+
return value.flatMap((item, index) => scalarPaths(item, `${path}[${index}]`));
|
|
154
|
+
if (value !== null && typeof value === "object")
|
|
155
|
+
return Object.entries(value).flatMap(([key, item]) => scalarPaths(item, path === "" ? key : `${path}.${key}`));
|
|
156
|
+
return [{ path, value }];
|
|
157
|
+
}
|
|
158
|
+
function firstChangedPath(left, right) {
|
|
159
|
+
const before = scalarPaths(left);
|
|
160
|
+
const after = scalarPaths(right);
|
|
161
|
+
for (let index = 0; index < Math.max(before.length, after.length); index += 1)
|
|
162
|
+
if (before[index]?.path !== after[index]?.path ||
|
|
163
|
+
JSON.stringify(before[index]?.value) !==
|
|
164
|
+
JSON.stringify(after[index]?.value))
|
|
165
|
+
return after[index]?.path ?? before[index]?.path ?? null;
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
function cacheKey(value) {
|
|
169
|
+
return value !== null &&
|
|
170
|
+
typeof value === "object" &&
|
|
171
|
+
"prompt_cache_key" in value
|
|
172
|
+
? value.prompt_cache_key
|
|
173
|
+
: undefined;
|
|
174
|
+
}
|
|
175
|
+
function bytes(value) {
|
|
176
|
+
return Buffer.byteLength(value, "utf8");
|
|
177
|
+
}
|
|
178
|
+
function commonBytes(left, right) {
|
|
179
|
+
const a = Buffer.from(left, "utf8");
|
|
180
|
+
const b = Buffer.from(right, "utf8");
|
|
181
|
+
let index = 0;
|
|
182
|
+
while (index < Math.min(a.length, b.length) && a[index] === b[index])
|
|
183
|
+
index += 1;
|
|
184
|
+
return index;
|
|
185
|
+
}
|