narraeon 0.4.0 → 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 +186 -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 +445 -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 +13 -0
- 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-D8YKfmNG.js +0 -1192
- package/dist/web/assets/index-Xa0s35CJ.css +0 -1
|
@@ -0,0 +1,1027 @@
|
|
|
1
|
+
import { validPromptCompilation, validLogicalMessages, validPromptTools, validPlayFollowup, validProviderKind, validCacheStrategy, validToolStrategy, } from "../prompt/PromptCompilationCodec.js";
|
|
2
|
+
import { validAuthoringRequests, } from "../authoring/AuthoringRequestSnapshot.js";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { link, mkdir, open, readFile, readdir, rename, rm, stat, } from "node:fs/promises";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { isDeepStrictEqual } from "node:util";
|
|
7
|
+
import { ContentWorkspace } from "../content/ContentWorkspace.js";
|
|
8
|
+
import { contentTreeFingerprint } from "../content/ContentTreeFingerprint.js";
|
|
9
|
+
import { isPlayPresetBinding, } from "../play/FileNativePlayPresetStore.js";
|
|
10
|
+
import { compileSettingImprovementLegacyCurrentTreeBootstrap, } from "../prompt/FileNativePromptCompiler.js";
|
|
11
|
+
import { settingImprovementToolDefinitions, } from "./SettingAuthoringTransaction.js";
|
|
12
|
+
const sessionFilePattern = /^setting-[0-9a-f-]{36}\.json$/u;
|
|
13
|
+
export class FileNativeSettingImprovementStore {
|
|
14
|
+
#content;
|
|
15
|
+
#sessionsRoot;
|
|
16
|
+
#mutationTail = Promise.resolve();
|
|
17
|
+
#summaryIndex = null;
|
|
18
|
+
#summaryIndexRevision;
|
|
19
|
+
#summariesByPackage = new Map();
|
|
20
|
+
#latestByPackage = new Map();
|
|
21
|
+
#packageRevisions = new Map();
|
|
22
|
+
constructor(dataRoot, options = {}) {
|
|
23
|
+
this.#content = options.content ?? new ContentWorkspace(dataRoot);
|
|
24
|
+
this.#sessionsRoot = join(dataRoot, "setting-improvements", "sessions");
|
|
25
|
+
}
|
|
26
|
+
createId() {
|
|
27
|
+
return `setting-${randomUUID()}`;
|
|
28
|
+
}
|
|
29
|
+
async read(sessionId) {
|
|
30
|
+
await this.#mutationTail;
|
|
31
|
+
return this.#read(sessionId);
|
|
32
|
+
}
|
|
33
|
+
async findLatestByPackage(packageId) {
|
|
34
|
+
const summaries = await this.listSummariesByPackage(packageId);
|
|
35
|
+
return summaries[0] === undefined
|
|
36
|
+
? null
|
|
37
|
+
: this.read(summaries[0].sessionId);
|
|
38
|
+
}
|
|
39
|
+
async findByCreationRequest(packageId, requestId) {
|
|
40
|
+
const summaries = await this.listSummariesByPackage(packageId);
|
|
41
|
+
const match = summaries.find(({ creationRequestId }) => creationRequestId === requestId);
|
|
42
|
+
return match === undefined ? null : this.read(match.sessionId);
|
|
43
|
+
}
|
|
44
|
+
async listSummariesByPackage(packageId) {
|
|
45
|
+
await this.#mutationTail;
|
|
46
|
+
await this.#refreshSummaryIndex();
|
|
47
|
+
return [...(this.#summariesByPackage.get(packageId)?.values() ?? [])]
|
|
48
|
+
.map((summary) => structuredClone(summary))
|
|
49
|
+
.sort(compareSummaries);
|
|
50
|
+
}
|
|
51
|
+
async summaryStatusByPackage(packageId, sessionId) {
|
|
52
|
+
await this.#mutationTail;
|
|
53
|
+
await this.#refreshSummaryIndex();
|
|
54
|
+
const summaries = this.#summariesByPackage.get(packageId);
|
|
55
|
+
const selected = sessionId === undefined
|
|
56
|
+
? this.#latestByPackage.get(packageId)
|
|
57
|
+
: summaries?.get(sessionId);
|
|
58
|
+
return {
|
|
59
|
+
revision: this.#packageRevisions.get(packageId) ?? "empty",
|
|
60
|
+
selected: selected === undefined ? null : structuredClone(selected),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
save(session) {
|
|
64
|
+
return this.#mutate(async () => {
|
|
65
|
+
validateStoredSession(session);
|
|
66
|
+
await publishJson(this.#path(session.sessionId), session);
|
|
67
|
+
await this.#recordSavedSession(session);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
saveIfUnchanged(expected, replacement) {
|
|
71
|
+
return this.#mutate(async () => {
|
|
72
|
+
if (expected.sessionId !== replacement.sessionId)
|
|
73
|
+
throw new Error("Cannot replace a different setting-improvement conversation");
|
|
74
|
+
validateStoredSession(replacement);
|
|
75
|
+
const current = await this.#read(expected.sessionId);
|
|
76
|
+
if (!isDeepStrictEqual(current, expected))
|
|
77
|
+
return false;
|
|
78
|
+
await publishJson(this.#path(replacement.sessionId), replacement);
|
|
79
|
+
await this.#recordSavedSession(replacement);
|
|
80
|
+
return true;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
deleteSession(packageId, sessionId) {
|
|
84
|
+
return this.#mutate(async () => {
|
|
85
|
+
const session = await this.#read(sessionId);
|
|
86
|
+
if (session.packageId !== packageId)
|
|
87
|
+
throw new Error("The setting-improvement conversation does not belong to this content package");
|
|
88
|
+
await rm(this.#path(sessionId));
|
|
89
|
+
await rm(join(this.#sessionsRoot, `${sessionId}.schema-v1.json`), {
|
|
90
|
+
force: true,
|
|
91
|
+
});
|
|
92
|
+
await this.#recordDeletedSession(session);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async #refreshSummaryIndex() {
|
|
96
|
+
for (;;) {
|
|
97
|
+
const revision = await directoryRevision(this.#sessionsRoot);
|
|
98
|
+
if (this.#summaryIndex !== null &&
|
|
99
|
+
this.#summaryIndexRevision === revision)
|
|
100
|
+
return;
|
|
101
|
+
let entries;
|
|
102
|
+
try {
|
|
103
|
+
entries = await readdir(this.#sessionsRoot, { withFileTypes: true });
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
107
|
+
if ((await directoryRevision(this.#sessionsRoot)) !== null)
|
|
108
|
+
continue;
|
|
109
|
+
this.#replaceSummaryIndex(new Map(), null);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
const summaries = new Map();
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
if (!entry.isFile() || !sessionFilePattern.test(entry.name))
|
|
117
|
+
continue;
|
|
118
|
+
const session = await this.#read(entry.name.slice(0, -5));
|
|
119
|
+
summaries.set(session.sessionId, summarizeStoredSettingImprovementSession(session));
|
|
120
|
+
}
|
|
121
|
+
const completedRevision = await directoryRevision(this.#sessionsRoot);
|
|
122
|
+
if (completedRevision !== revision)
|
|
123
|
+
continue;
|
|
124
|
+
this.#replaceSummaryIndex(summaries, completedRevision);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async #recordSavedSession(session) {
|
|
129
|
+
if (this.#summaryIndex !== null)
|
|
130
|
+
this.#cacheSummary(summarizeStoredSettingImprovementSession(session));
|
|
131
|
+
this.#summaryIndexRevision =
|
|
132
|
+
this.#summaryIndex === null
|
|
133
|
+
? undefined
|
|
134
|
+
: await directoryRevision(this.#sessionsRoot);
|
|
135
|
+
}
|
|
136
|
+
async #recordDeletedSession(session) {
|
|
137
|
+
if (this.#summaryIndex === null)
|
|
138
|
+
return;
|
|
139
|
+
const previous = this.#summaryIndex.get(session.sessionId);
|
|
140
|
+
if (previous === undefined) {
|
|
141
|
+
// The directory changed outside this instance after its last scan.
|
|
142
|
+
this.#summaryIndex = null;
|
|
143
|
+
this.#summaryIndexRevision = undefined;
|
|
144
|
+
this.#summariesByPackage.clear();
|
|
145
|
+
this.#latestByPackage.clear();
|
|
146
|
+
this.#packageRevisions.clear();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
this.#summaryIndex.delete(session.sessionId);
|
|
150
|
+
removeFromNestedMap(this.#summariesByPackage, previous.packageId, previous.sessionId);
|
|
151
|
+
if (this.#latestByPackage.get(previous.packageId)?.sessionId ===
|
|
152
|
+
previous.sessionId)
|
|
153
|
+
this.#recomputeLatest(previous.packageId);
|
|
154
|
+
this.#packageRevisions.set(previous.packageId, randomUUID());
|
|
155
|
+
this.#summaryIndexRevision = await directoryRevision(this.#sessionsRoot);
|
|
156
|
+
}
|
|
157
|
+
#replaceSummaryIndex(summaries, revision) {
|
|
158
|
+
this.#summaryIndex = summaries;
|
|
159
|
+
this.#summaryIndexRevision = revision;
|
|
160
|
+
this.#summariesByPackage = new Map();
|
|
161
|
+
this.#latestByPackage = new Map();
|
|
162
|
+
this.#packageRevisions = new Map();
|
|
163
|
+
for (const summary of summaries.values())
|
|
164
|
+
this.#insertSummary(summary);
|
|
165
|
+
for (const packageId of this.#summariesByPackage.keys())
|
|
166
|
+
this.#packageRevisions.set(packageId, randomUUID());
|
|
167
|
+
}
|
|
168
|
+
#cacheSummary(summary) {
|
|
169
|
+
const previous = this.#summaryIndex?.get(summary.sessionId);
|
|
170
|
+
if (previous !== undefined)
|
|
171
|
+
removeFromNestedMap(this.#summariesByPackage, previous.packageId, previous.sessionId);
|
|
172
|
+
if (previous !== undefined &&
|
|
173
|
+
previous.packageId !== summary.packageId &&
|
|
174
|
+
this.#latestByPackage.get(previous.packageId)?.sessionId ===
|
|
175
|
+
previous.sessionId)
|
|
176
|
+
this.#recomputeLatest(previous.packageId);
|
|
177
|
+
this.#summaryIndex?.set(summary.sessionId, summary);
|
|
178
|
+
this.#insertSummary(summary);
|
|
179
|
+
if (previous !== undefined && previous.packageId !== summary.packageId)
|
|
180
|
+
this.#packageRevisions.set(previous.packageId, randomUUID());
|
|
181
|
+
this.#packageRevisions.set(summary.packageId, randomUUID());
|
|
182
|
+
}
|
|
183
|
+
#insertSummary(summary) {
|
|
184
|
+
mapFor(this.#summariesByPackage, summary.packageId).set(summary.sessionId, summary);
|
|
185
|
+
const latest = this.#latestByPackage.get(summary.packageId);
|
|
186
|
+
if (latest === undefined ||
|
|
187
|
+
latest.sessionId === summary.sessionId ||
|
|
188
|
+
compareSummaries(summary, latest) < 0)
|
|
189
|
+
this.#latestByPackage.set(summary.packageId, summary);
|
|
190
|
+
}
|
|
191
|
+
#recomputeLatest(packageId) {
|
|
192
|
+
const latest = [
|
|
193
|
+
...(this.#summariesByPackage.get(packageId)?.values() ?? []),
|
|
194
|
+
].sort(compareSummaries)[0];
|
|
195
|
+
if (latest === undefined)
|
|
196
|
+
this.#latestByPackage.delete(packageId);
|
|
197
|
+
else
|
|
198
|
+
this.#latestByPackage.set(packageId, latest);
|
|
199
|
+
}
|
|
200
|
+
async #read(sessionId) {
|
|
201
|
+
assertSessionId(sessionId);
|
|
202
|
+
let source;
|
|
203
|
+
let value;
|
|
204
|
+
try {
|
|
205
|
+
source = await readFile(this.#path(sessionId), "utf8");
|
|
206
|
+
value = JSON.parse(source);
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
if (isNodeError(error) && error.code === "ENOENT")
|
|
210
|
+
throw new Error("Setting-improvement conversation does not exist", {
|
|
211
|
+
cause: error,
|
|
212
|
+
});
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
if (isRecord(value) && value.schemaVersion === 1)
|
|
216
|
+
return this.#migrateLegacy(sessionId, source, value);
|
|
217
|
+
const session = validateStoredSession(value);
|
|
218
|
+
if (session.sessionId !== sessionId)
|
|
219
|
+
throw new Error("Setting-improvement conversation identity does not match its file");
|
|
220
|
+
return session;
|
|
221
|
+
}
|
|
222
|
+
async #migrateLegacy(sessionId, source, value) {
|
|
223
|
+
if (!validLegacySession(value))
|
|
224
|
+
throw new Error("Legacy setting-improvement conversation is damaged");
|
|
225
|
+
if (value.sessionId !== sessionId)
|
|
226
|
+
throw new Error("Legacy setting-improvement conversation identity does not match its file");
|
|
227
|
+
if (value.applyRequest === null)
|
|
228
|
+
return this.#publishLegacyMigration(sessionId, source, migrateLegacySession(value));
|
|
229
|
+
const lease = await this.#content.beginCurrentTreeContentPackageOperation(value.packageId);
|
|
230
|
+
try {
|
|
231
|
+
const currentFingerprint = contentTreeFingerprint(lease.package.files);
|
|
232
|
+
const applyOutcome = currentFingerprint === value.applyRequest.draftFingerprint
|
|
233
|
+
? "applied"
|
|
234
|
+
: currentFingerprint === value.baseFingerprint
|
|
235
|
+
? "unapplied_dropped"
|
|
236
|
+
: "apply_outcome_unknown";
|
|
237
|
+
return await this.#publishLegacyMigration(sessionId, source, migrateLegacySession(value, applyOutcome));
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
lease.release();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async #publishLegacyMigration(sessionId, source, session) {
|
|
244
|
+
await publishImmutableSource(join(this.#sessionsRoot, `${sessionId}.schema-v1.json`), source);
|
|
245
|
+
// This schema-v2 endpoint is the durable marker; the immutable source is
|
|
246
|
+
// already synced, so an interrupted migration can restart from it.
|
|
247
|
+
await publishJson(this.#path(sessionId), session);
|
|
248
|
+
return session;
|
|
249
|
+
}
|
|
250
|
+
async #mutate(operation) {
|
|
251
|
+
const previous = this.#mutationTail;
|
|
252
|
+
let release = () => undefined;
|
|
253
|
+
this.#mutationTail = new Promise((resolve) => {
|
|
254
|
+
release = resolve;
|
|
255
|
+
});
|
|
256
|
+
await previous;
|
|
257
|
+
try {
|
|
258
|
+
return await operation();
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
release();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
#path(sessionId) {
|
|
265
|
+
assertSessionId(sessionId);
|
|
266
|
+
return join(this.#sessionsRoot, `${sessionId}.json`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function compareSummaries(left, right) {
|
|
270
|
+
return (right.updatedAt - left.updatedAt ||
|
|
271
|
+
right.createdAt - left.createdAt ||
|
|
272
|
+
right.sessionId.localeCompare(left.sessionId));
|
|
273
|
+
}
|
|
274
|
+
function mapFor(packages, packageId) {
|
|
275
|
+
let summaries = packages.get(packageId);
|
|
276
|
+
if (summaries === undefined) {
|
|
277
|
+
summaries = new Map();
|
|
278
|
+
packages.set(packageId, summaries);
|
|
279
|
+
}
|
|
280
|
+
return summaries;
|
|
281
|
+
}
|
|
282
|
+
function removeFromNestedMap(packages, packageId, sessionId) {
|
|
283
|
+
const summaries = packages.get(packageId);
|
|
284
|
+
if (summaries === undefined)
|
|
285
|
+
return;
|
|
286
|
+
summaries.delete(sessionId);
|
|
287
|
+
if (summaries.size === 0)
|
|
288
|
+
packages.delete(packageId);
|
|
289
|
+
}
|
|
290
|
+
export function summarizeStoredSettingImprovementSession(session) {
|
|
291
|
+
const assistants = session.modelItems.filter(({ kind }) => kind === "assistant");
|
|
292
|
+
const changedPaths = new Set([
|
|
293
|
+
...session.modelItems.flatMap((item) => item.kind === "tool" ? item.changes.map(({ path }) => path) : []),
|
|
294
|
+
...(session.legacyDraft?.changes.map(({ path }) => path) ?? []),
|
|
295
|
+
]);
|
|
296
|
+
return {
|
|
297
|
+
sessionId: session.sessionId,
|
|
298
|
+
packageId: session.packageId,
|
|
299
|
+
runStatus: session.runStatus,
|
|
300
|
+
createdAt: session.createdAt,
|
|
301
|
+
updatedAt: session.updatedAt,
|
|
302
|
+
creationRequestId: session.creationRequestId,
|
|
303
|
+
excerpt: settingImprovementExcerpt(session.messages.find(({ role }) => role === "user")?.text ?? ""),
|
|
304
|
+
turnCount: session.modelItems.filter(({ kind }) => kind === "user").length,
|
|
305
|
+
exchangeCount: assistants.length,
|
|
306
|
+
toolCallCount: assistants.reduce((total, item) => total + (item.kind === "assistant" ? item.toolCalls.length : 0), 0),
|
|
307
|
+
changedFileCount: changedPaths.size,
|
|
308
|
+
exchange: session.exchange,
|
|
309
|
+
toolCalls: session.toolCalls,
|
|
310
|
+
needsRecovery: session.pendingSettlement !== null ||
|
|
311
|
+
session.runStatus === "running" ||
|
|
312
|
+
hasPendingToolCalls(session),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function settingImprovementExcerpt(message) {
|
|
316
|
+
return Array.from(message.replace(/\s+/gu, " ").trim())
|
|
317
|
+
.slice(0, 120)
|
|
318
|
+
.join("");
|
|
319
|
+
}
|
|
320
|
+
function hasPendingToolCalls(session) {
|
|
321
|
+
const lastItem = session.modelItems.at(-1);
|
|
322
|
+
return lastItem?.kind === "assistant" && lastItem.toolCalls.length > 0;
|
|
323
|
+
}
|
|
324
|
+
async function directoryRevision(path) {
|
|
325
|
+
try {
|
|
326
|
+
const metadata = await stat(path, { bigint: true });
|
|
327
|
+
return `${metadata.mtimeNs}:${metadata.ctimeNs}:${metadata.size}`;
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
if (isNodeError(error) && error.code === "ENOENT")
|
|
331
|
+
return null;
|
|
332
|
+
throw error;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function assertSessionId(sessionId) {
|
|
336
|
+
if (!/^setting-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(sessionId))
|
|
337
|
+
throw new Error("Invalid setting-improvement conversation ID");
|
|
338
|
+
}
|
|
339
|
+
function validateStoredSession(value) {
|
|
340
|
+
if (!isRecord(value) ||
|
|
341
|
+
!hasExactKeys(value, [
|
|
342
|
+
"schemaVersion",
|
|
343
|
+
"sessionId",
|
|
344
|
+
"packageId",
|
|
345
|
+
"locale",
|
|
346
|
+
"runStatus",
|
|
347
|
+
"createdAt",
|
|
348
|
+
"updatedAt",
|
|
349
|
+
"creationRequestId",
|
|
350
|
+
"bootstrap",
|
|
351
|
+
"modelBinding",
|
|
352
|
+
"modelItems",
|
|
353
|
+
"messages",
|
|
354
|
+
"usage",
|
|
355
|
+
"exchange",
|
|
356
|
+
"toolCalls",
|
|
357
|
+
"activeRequestId",
|
|
358
|
+
"completedRequestIds",
|
|
359
|
+
"lastFailure",
|
|
360
|
+
"authorization",
|
|
361
|
+
"pendingSettlement",
|
|
362
|
+
"legacyDraft",
|
|
363
|
+
], ["contentPackageTitle", "playPreset", "requests"]) ||
|
|
364
|
+
(value.schemaVersion !== 2 && value.schemaVersion !== 3) ||
|
|
365
|
+
(value.schemaVersion === 2
|
|
366
|
+
? value.requests !== undefined
|
|
367
|
+
: !validAuthoringRequests(value.requests, validPromptCompilation, {
|
|
368
|
+
modelItems: value.modelItems,
|
|
369
|
+
activeRequestId: value.activeRequestId,
|
|
370
|
+
completedRequestIds: value.completedRequestIds,
|
|
371
|
+
})) ||
|
|
372
|
+
typeof value.sessionId !== "string" ||
|
|
373
|
+
typeof value.packageId !== "string" ||
|
|
374
|
+
!validOptionalTitle(value.contentPackageTitle) ||
|
|
375
|
+
(value.locale !== "en" && value.locale !== "zh-CN") ||
|
|
376
|
+
!validRunStatus(value.runStatus) ||
|
|
377
|
+
!validFiniteNumber(value.createdAt) ||
|
|
378
|
+
!validFiniteNumber(value.updatedAt) ||
|
|
379
|
+
typeof value.creationRequestId !== "string" ||
|
|
380
|
+
!validPromptCompilation(value.bootstrap) ||
|
|
381
|
+
!validModelBinding(value.modelBinding) ||
|
|
382
|
+
(value.playPreset !== undefined &&
|
|
383
|
+
!isPlayPresetBinding(value.playPreset)) ||
|
|
384
|
+
!Array.isArray(value.modelItems) ||
|
|
385
|
+
!Array.isArray(value.messages) ||
|
|
386
|
+
!validModelUsage(value.usage) ||
|
|
387
|
+
!validCount(value.exchange) ||
|
|
388
|
+
!validCount(value.toolCalls) ||
|
|
389
|
+
(value.activeRequestId !== null &&
|
|
390
|
+
typeof value.activeRequestId !== "string") ||
|
|
391
|
+
!Array.isArray(value.completedRequestIds) ||
|
|
392
|
+
(value.lastFailure !== null && typeof value.lastFailure !== "string") ||
|
|
393
|
+
!validAuthorization(value.authorization) ||
|
|
394
|
+
!validPendingSettlement(value.pendingSettlement) ||
|
|
395
|
+
!validPendingSettlementForTranscript(value.pendingSettlement, value.modelItems, value.runStatus) ||
|
|
396
|
+
!validLegacyDraft(value.legacyDraft))
|
|
397
|
+
throw new Error("Setting-improvement conversation does not match its durable schema");
|
|
398
|
+
assertSessionId(value.sessionId);
|
|
399
|
+
if (!value.completedRequestIds.every((id) => typeof id === "string") ||
|
|
400
|
+
!value.messages.every(validMessage) ||
|
|
401
|
+
!value.modelItems.every(validModelItem))
|
|
402
|
+
throw new Error("Setting-improvement transcript is damaged");
|
|
403
|
+
return structuredClone(value);
|
|
404
|
+
}
|
|
405
|
+
function migrateLegacySession(value, applyOutcome) {
|
|
406
|
+
if (!validLegacySession(value))
|
|
407
|
+
throw new Error("Legacy setting-improvement conversation is damaged");
|
|
408
|
+
assertSessionId(value.sessionId);
|
|
409
|
+
if (value.applyRequest !== null && applyOutcome === undefined)
|
|
410
|
+
throw new Error("Legacy Apply migration requires a current-tree outcome");
|
|
411
|
+
const legacyChanges = value.review.diff.map((diff) => structuredClone(diff));
|
|
412
|
+
const modelItems = value.modelItems.map((item) => {
|
|
413
|
+
const cloned = structuredClone(item);
|
|
414
|
+
return cloned.kind === "tool" ? { ...cloned, changes: [] } : cloned;
|
|
415
|
+
});
|
|
416
|
+
const last = modelItems.at(-1);
|
|
417
|
+
const pendingLegacyTools = last?.kind === "assistant" && last.toolCalls.length > 0;
|
|
418
|
+
if (pendingLegacyTools && last.kind === "assistant") {
|
|
419
|
+
for (const call of last.toolCalls)
|
|
420
|
+
modelItems.push({
|
|
421
|
+
kind: "tool",
|
|
422
|
+
toolCallId: call.id,
|
|
423
|
+
markdown: "# Legacy tool step not replayed\n\nThe isolated draft tool step was not applied during migration. The content package current tree was left unchanged; read the current tree again before editing.",
|
|
424
|
+
isError: true,
|
|
425
|
+
changes: [],
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
const interruptedRequestId = value.activeRequestId;
|
|
429
|
+
const completedRequestIds = [...value.completedRequestIds];
|
|
430
|
+
if ((pendingLegacyTools || value.runStatus === "running") &&
|
|
431
|
+
interruptedRequestId !== null &&
|
|
432
|
+
!completedRequestIds.includes(interruptedRequestId))
|
|
433
|
+
completedRequestIds.push(interruptedRequestId);
|
|
434
|
+
return {
|
|
435
|
+
schemaVersion: 2,
|
|
436
|
+
sessionId: value.sessionId,
|
|
437
|
+
packageId: value.packageId,
|
|
438
|
+
...(value.contentPackageTitle === undefined
|
|
439
|
+
? {}
|
|
440
|
+
: { contentPackageTitle: value.contentPackageTitle }),
|
|
441
|
+
locale: value.locale,
|
|
442
|
+
runStatus: pendingLegacyTools ||
|
|
443
|
+
value.runStatus === "running" ||
|
|
444
|
+
applyOutcome === "apply_outcome_unknown"
|
|
445
|
+
? "interrupted"
|
|
446
|
+
: value.runStatus,
|
|
447
|
+
createdAt: value.createdAt,
|
|
448
|
+
updatedAt: value.updatedAt,
|
|
449
|
+
creationRequestId: `legacy:${value.sessionId}`,
|
|
450
|
+
bootstrap: compileSettingImprovementLegacyCurrentTreeBootstrap(structuredClone(value.bootstrap), value.locale, settingImprovementToolDefinitions(value.locale)),
|
|
451
|
+
modelBinding: structuredClone(value.modelBinding),
|
|
452
|
+
...(value.playPreset === undefined
|
|
453
|
+
? {}
|
|
454
|
+
: { playPreset: structuredClone(value.playPreset) }),
|
|
455
|
+
modelItems,
|
|
456
|
+
messages: structuredClone(value.messages),
|
|
457
|
+
usage: structuredClone(value.usage),
|
|
458
|
+
exchange: value.exchange,
|
|
459
|
+
toolCalls: value.toolCalls,
|
|
460
|
+
activeRequestId: null,
|
|
461
|
+
completedRequestIds,
|
|
462
|
+
lastFailure: pendingLegacyTools || value.runStatus === "running"
|
|
463
|
+
? "This conversation was interrupted while its legacy isolated draft was being processed. That pending step was not applied; continue from the content package current tree."
|
|
464
|
+
: applyOutcome === "apply_outcome_unknown"
|
|
465
|
+
? "The legacy Apply receipt was preserved, but the current tree matches neither its base nor its draft. Whether that old draft was once applied cannot be proven; no content was changed during migration."
|
|
466
|
+
: value.lastFailure,
|
|
467
|
+
authorization: null,
|
|
468
|
+
pendingSettlement: null,
|
|
469
|
+
legacyDraft: {
|
|
470
|
+
outcome: value.lifecycle === "applied"
|
|
471
|
+
? "applied"
|
|
472
|
+
: value.lifecycle === "discarded"
|
|
473
|
+
? "discarded"
|
|
474
|
+
: (applyOutcome ?? "unapplied_dropped"),
|
|
475
|
+
changes: legacyChanges,
|
|
476
|
+
},
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
function validOptionalTitle(value) {
|
|
480
|
+
return (value === undefined ||
|
|
481
|
+
(typeof value === "string" &&
|
|
482
|
+
value.trim().length > 0 &&
|
|
483
|
+
!/[\r\n]/u.test(value)));
|
|
484
|
+
}
|
|
485
|
+
function validRunStatus(value) {
|
|
486
|
+
return value === "ready" || value === "running" || value === "interrupted";
|
|
487
|
+
}
|
|
488
|
+
function validCount(value) {
|
|
489
|
+
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
490
|
+
}
|
|
491
|
+
function validMessage(value) {
|
|
492
|
+
return (isRecord(value) &&
|
|
493
|
+
hasExactKeys(value, ["id", "role", "text", "createdAt"]) &&
|
|
494
|
+
typeof value.id === "string" &&
|
|
495
|
+
(value.role === "user" || value.role === "assistant") &&
|
|
496
|
+
typeof value.text === "string" &&
|
|
497
|
+
validFiniteNumber(value.createdAt));
|
|
498
|
+
}
|
|
499
|
+
function validLegacyModelItem(value) {
|
|
500
|
+
return validModelItemShape(value, false);
|
|
501
|
+
}
|
|
502
|
+
function validModelItem(value) {
|
|
503
|
+
return validModelItemShape(value, true);
|
|
504
|
+
}
|
|
505
|
+
function validModelItemShape(value, storedToolChanges) {
|
|
506
|
+
if (!isRecord(value) || typeof value.kind !== "string")
|
|
507
|
+
return false;
|
|
508
|
+
if (value.kind === "runtime_notice")
|
|
509
|
+
return (hasExactKeys(value, ["kind", "notice", "text"]) &&
|
|
510
|
+
(value.notice === "checkpoint_rounds" ||
|
|
511
|
+
value.notice === "continuation" ||
|
|
512
|
+
value.notice === "tool_step") &&
|
|
513
|
+
typeof value.text === "string");
|
|
514
|
+
if (value.kind === "prompt_delta")
|
|
515
|
+
return (hasExactKeys(value, ["kind", "logicalMessages"]) &&
|
|
516
|
+
validLogicalMessages(value.logicalMessages));
|
|
517
|
+
if (value.kind === "player" || value.kind === "user")
|
|
518
|
+
return (hasExactKeys(value, ["kind", "text"]) && typeof value.text === "string");
|
|
519
|
+
if (value.kind === "assistant")
|
|
520
|
+
return (hasExactKeys(value, ["kind", "text", "toolCalls"], ["reasoningContent", "providerState"]) &&
|
|
521
|
+
typeof value.text === "string" &&
|
|
522
|
+
(value.reasoningContent === undefined ||
|
|
523
|
+
typeof value.reasoningContent === "string") &&
|
|
524
|
+
(value.providerState === undefined ||
|
|
525
|
+
validProviderState(value.providerState)) &&
|
|
526
|
+
Array.isArray(value.toolCalls) &&
|
|
527
|
+
value.toolCalls.every(validModelToolCall));
|
|
528
|
+
if (value.kind !== "tool")
|
|
529
|
+
return false;
|
|
530
|
+
const required = storedToolChanges
|
|
531
|
+
? ["kind", "toolCallId", "markdown", "changes"]
|
|
532
|
+
: ["kind", "toolCallId", "markdown"];
|
|
533
|
+
return (hasExactKeys(value, required, ["isError"]) &&
|
|
534
|
+
typeof value.toolCallId === "string" &&
|
|
535
|
+
typeof value.markdown === "string" &&
|
|
536
|
+
(value.isError === undefined || typeof value.isError === "boolean") &&
|
|
537
|
+
(!storedToolChanges ||
|
|
538
|
+
(Array.isArray(value.changes) && value.changes.every(validDiff))));
|
|
539
|
+
}
|
|
540
|
+
function validLegacySession(value) {
|
|
541
|
+
if (!isRecord(value) ||
|
|
542
|
+
!hasExactKeys(value, [
|
|
543
|
+
"schemaVersion",
|
|
544
|
+
"sessionId",
|
|
545
|
+
"packageId",
|
|
546
|
+
"locale",
|
|
547
|
+
"lifecycle",
|
|
548
|
+
"runStatus",
|
|
549
|
+
"createdAt",
|
|
550
|
+
"updatedAt",
|
|
551
|
+
"baseFingerprint",
|
|
552
|
+
"baseFiles",
|
|
553
|
+
"draftVersion",
|
|
554
|
+
"draft",
|
|
555
|
+
"review",
|
|
556
|
+
"bootstrap",
|
|
557
|
+
"modelBinding",
|
|
558
|
+
"modelItems",
|
|
559
|
+
"messages",
|
|
560
|
+
"usage",
|
|
561
|
+
"exchange",
|
|
562
|
+
"toolCalls",
|
|
563
|
+
"activeRequestId",
|
|
564
|
+
"completedRequestIds",
|
|
565
|
+
"lastFailure",
|
|
566
|
+
"applyRequest",
|
|
567
|
+
"appliedAt",
|
|
568
|
+
], ["contentPackageTitle", "playPreset"]) ||
|
|
569
|
+
value.schemaVersion !== 1 ||
|
|
570
|
+
typeof value.sessionId !== "string" ||
|
|
571
|
+
typeof value.packageId !== "string" ||
|
|
572
|
+
!validOptionalTitle(value.contentPackageTitle) ||
|
|
573
|
+
(value.locale !== "en" && value.locale !== "zh-CN") ||
|
|
574
|
+
(value.lifecycle !== "open" &&
|
|
575
|
+
value.lifecycle !== "applied" &&
|
|
576
|
+
value.lifecycle !== "discarded") ||
|
|
577
|
+
!validRunStatus(value.runStatus) ||
|
|
578
|
+
!validFiniteNumber(value.createdAt) ||
|
|
579
|
+
!validFiniteNumber(value.updatedAt) ||
|
|
580
|
+
typeof value.baseFingerprint !== "string" ||
|
|
581
|
+
!validContentTreeFiles(value.baseFiles) ||
|
|
582
|
+
!validCount(value.draftVersion) ||
|
|
583
|
+
!validLegacyDraftState(value.draft) ||
|
|
584
|
+
!validSettingReview(value.review) ||
|
|
585
|
+
!validPromptCompilation(value.bootstrap) ||
|
|
586
|
+
!validModelBinding(value.modelBinding) ||
|
|
587
|
+
(value.playPreset !== undefined &&
|
|
588
|
+
!isPlayPresetBinding(value.playPreset)) ||
|
|
589
|
+
!Array.isArray(value.modelItems) ||
|
|
590
|
+
!value.modelItems.every(validLegacyModelItem) ||
|
|
591
|
+
!Array.isArray(value.messages) ||
|
|
592
|
+
!value.messages.every(validMessage) ||
|
|
593
|
+
!validModelUsage(value.usage) ||
|
|
594
|
+
!validCount(value.exchange) ||
|
|
595
|
+
!validCount(value.toolCalls) ||
|
|
596
|
+
(value.activeRequestId !== null &&
|
|
597
|
+
typeof value.activeRequestId !== "string") ||
|
|
598
|
+
!validStringArray(value.completedRequestIds) ||
|
|
599
|
+
(value.lastFailure !== null && typeof value.lastFailure !== "string") ||
|
|
600
|
+
!validLegacyApplyRequest(value.applyRequest) ||
|
|
601
|
+
(value.appliedAt !== null && !validFiniteNumber(value.appliedAt)))
|
|
602
|
+
return false;
|
|
603
|
+
if (value.baseFingerprint !== contentTreeFingerprint(value.baseFiles))
|
|
604
|
+
return false;
|
|
605
|
+
if (value.applyRequest !== null &&
|
|
606
|
+
(value.lifecycle !== "open" ||
|
|
607
|
+
value.applyRequest.expectedDraftVersion !== value.draftVersion ||
|
|
608
|
+
value.applyRequest.draftFingerprint !==
|
|
609
|
+
contentTreeFingerprint(value.draft.files)))
|
|
610
|
+
return false;
|
|
611
|
+
return true;
|
|
612
|
+
}
|
|
613
|
+
function validLegacyDraftState(value) {
|
|
614
|
+
return (isRecord(value) &&
|
|
615
|
+
hasExactKeys(value, [
|
|
616
|
+
"files",
|
|
617
|
+
"readWorldDocumentIds",
|
|
618
|
+
"readableDamagedWorldPaths",
|
|
619
|
+
"readOpaquePaths",
|
|
620
|
+
]) &&
|
|
621
|
+
validContentTreeFiles(value.files) &&
|
|
622
|
+
validStringArray(value.readWorldDocumentIds) &&
|
|
623
|
+
validStringArray(value.readableDamagedWorldPaths) &&
|
|
624
|
+
validStringArray(value.readOpaquePaths));
|
|
625
|
+
}
|
|
626
|
+
function validContentTreeFiles(value) {
|
|
627
|
+
return Array.isArray(value) && value.every(validContentTreeFile);
|
|
628
|
+
}
|
|
629
|
+
function validContentTreeFile(value) {
|
|
630
|
+
return (isRecord(value) &&
|
|
631
|
+
hasExactKeys(value, ["path", "contents"], ["encoding"]) &&
|
|
632
|
+
typeof value.path === "string" &&
|
|
633
|
+
typeof value.contents === "string" &&
|
|
634
|
+
(value.encoding === undefined || value.encoding === "base64"));
|
|
635
|
+
}
|
|
636
|
+
function validSettingReview(value) {
|
|
637
|
+
return (isRecord(value) &&
|
|
638
|
+
hasExactKeys(value, ["status", "diff", "diagnostics", "preview"], ["playCoverage"]) &&
|
|
639
|
+
(value.status === "usable" || value.status === "needs_repair") &&
|
|
640
|
+
Array.isArray(value.diff) &&
|
|
641
|
+
value.diff.every(validDiff) &&
|
|
642
|
+
Array.isArray(value.diagnostics) &&
|
|
643
|
+
value.diagnostics.every(validSettingDiagnostic) &&
|
|
644
|
+
(value.preview === null || validPromptPreview(value.preview)) &&
|
|
645
|
+
(value.playCoverage === undefined ||
|
|
646
|
+
value.playCoverage === null ||
|
|
647
|
+
validSettingPlayCoverage(value.playCoverage)));
|
|
648
|
+
}
|
|
649
|
+
function validSettingDiagnostic(value) {
|
|
650
|
+
return (isRecord(value) &&
|
|
651
|
+
hasExactKeys(value, ["code", "path", "message"]) &&
|
|
652
|
+
typeof value.code === "string" &&
|
|
653
|
+
typeof value.path === "string" &&
|
|
654
|
+
typeof value.message === "string");
|
|
655
|
+
}
|
|
656
|
+
function validSettingPlayCoverage(value) {
|
|
657
|
+
if (!isRecord(value) ||
|
|
658
|
+
!hasExactKeys(value, ["totals", "changed"]) ||
|
|
659
|
+
!isRecord(value.totals) ||
|
|
660
|
+
!hasExactKeys(value.totals, [
|
|
661
|
+
"fullInjected",
|
|
662
|
+
"nodeInjected",
|
|
663
|
+
"catalogSummary",
|
|
664
|
+
"referencedFromInjected",
|
|
665
|
+
"onDemand",
|
|
666
|
+
]) ||
|
|
667
|
+
!Object.values(value.totals).every(validCount) ||
|
|
668
|
+
!Array.isArray(value.changed))
|
|
669
|
+
return false;
|
|
670
|
+
return value.changed.every((change) => isRecord(change) &&
|
|
671
|
+
hasExactKeys(change, ["path", "access", "detail"]) &&
|
|
672
|
+
typeof change.path === "string" &&
|
|
673
|
+
validSettingPlayAccess(change.access) &&
|
|
674
|
+
typeof change.detail === "string");
|
|
675
|
+
}
|
|
676
|
+
function validSettingPlayAccess(value) {
|
|
677
|
+
return [
|
|
678
|
+
"full_injected",
|
|
679
|
+
"node_injected",
|
|
680
|
+
"catalog_summary",
|
|
681
|
+
"referenced_from_injected",
|
|
682
|
+
"on_demand",
|
|
683
|
+
"opening_genesis",
|
|
684
|
+
"play_control",
|
|
685
|
+
"unused_control",
|
|
686
|
+
"player_view",
|
|
687
|
+
"removed",
|
|
688
|
+
].includes(String(value));
|
|
689
|
+
}
|
|
690
|
+
function validPromptPreview(value) {
|
|
691
|
+
return (isRecord(value) &&
|
|
692
|
+
hasExactKeys(value, ["diagnosticBinding", "compilation", "leakage"], ["initialAppend", "playPreset"]) &&
|
|
693
|
+
validDiagnosticBinding(value.diagnosticBinding) &&
|
|
694
|
+
validPromptCompilation(value.compilation) &&
|
|
695
|
+
(value.initialAppend === undefined ||
|
|
696
|
+
validInitialAppend(value.initialAppend)) &&
|
|
697
|
+
(value.playPreset === undefined ||
|
|
698
|
+
validPlayPresetPreview(value.playPreset)) &&
|
|
699
|
+
isRecord(value.leakage) &&
|
|
700
|
+
hasExactKeys(value.leakage, ["status", "checkedFields"]) &&
|
|
701
|
+
value.leakage.status === "clean" &&
|
|
702
|
+
validStringArray(value.leakage.checkedFields));
|
|
703
|
+
}
|
|
704
|
+
function validDiagnosticBinding(value) {
|
|
705
|
+
return (isRecord(value) &&
|
|
706
|
+
hasExactKeys(value, ["endpoint", "commit", "hostPresetId", "controlFingerprint", "modelId"], ["playPresetId", "playPresetRevision"]) &&
|
|
707
|
+
typeof value.endpoint === "string" &&
|
|
708
|
+
typeof value.commit === "string" &&
|
|
709
|
+
typeof value.hostPresetId === "string" &&
|
|
710
|
+
typeof value.controlFingerprint === "string" &&
|
|
711
|
+
typeof value.modelId === "string" &&
|
|
712
|
+
(value.playPresetId === undefined ||
|
|
713
|
+
typeof value.playPresetId === "string") &&
|
|
714
|
+
(value.playPresetRevision === undefined ||
|
|
715
|
+
typeof value.playPresetRevision === "string"));
|
|
716
|
+
}
|
|
717
|
+
function validInitialAppend(value) {
|
|
718
|
+
return (isRecord(value) &&
|
|
719
|
+
hasExactKeys(value, ["logical", "provider"], ["beforePlayer"]) &&
|
|
720
|
+
(value.beforePlayer === undefined ||
|
|
721
|
+
(isRecord(value.beforePlayer) &&
|
|
722
|
+
hasExactKeys(value.beforePlayer, ["logical", "provider"]) &&
|
|
723
|
+
validLegacyModelItem(value.beforePlayer.logical) &&
|
|
724
|
+
isRecord(value.beforePlayer.provider) &&
|
|
725
|
+
hasExactKeys(value.beforePlayer.provider, ["role", "content"]) &&
|
|
726
|
+
value.beforePlayer.provider.role === "user" &&
|
|
727
|
+
typeof value.beforePlayer.provider.content === "string")) &&
|
|
728
|
+
isRecord(value.logical) &&
|
|
729
|
+
hasExactKeys(value.logical, ["kind", "text"]) &&
|
|
730
|
+
value.logical.kind === "player" &&
|
|
731
|
+
typeof value.logical.text === "string" &&
|
|
732
|
+
isRecord(value.provider) &&
|
|
733
|
+
hasExactKeys(value.provider, ["role", "content"]) &&
|
|
734
|
+
value.provider.role === "user" &&
|
|
735
|
+
typeof value.provider.content === "string");
|
|
736
|
+
}
|
|
737
|
+
function validPlayPresetPreview(value) {
|
|
738
|
+
return (isRecord(value) &&
|
|
739
|
+
hasExactKeys(value, [
|
|
740
|
+
"id",
|
|
741
|
+
"name",
|
|
742
|
+
"revision",
|
|
743
|
+
"callChainPath",
|
|
744
|
+
"mounts",
|
|
745
|
+
"extensionRefs",
|
|
746
|
+
"toolUniverse",
|
|
747
|
+
"toolStrategy",
|
|
748
|
+
"bootstrap",
|
|
749
|
+
"followups",
|
|
750
|
+
"cache",
|
|
751
|
+
]) &&
|
|
752
|
+
typeof value.id === "string" &&
|
|
753
|
+
typeof value.name === "string" &&
|
|
754
|
+
typeof value.revision === "string" &&
|
|
755
|
+
typeof value.callChainPath === "string" &&
|
|
756
|
+
Array.isArray(value.mounts) &&
|
|
757
|
+
value.mounts.every(validPlayPresetMount) &&
|
|
758
|
+
validStringArray(value.extensionRefs) &&
|
|
759
|
+
validPromptTools(value.toolUniverse) &&
|
|
760
|
+
validToolStrategy(value.toolStrategy) &&
|
|
761
|
+
validPromptCompilation(value.bootstrap) &&
|
|
762
|
+
Array.isArray(value.followups) &&
|
|
763
|
+
value.followups.every(validPlayFollowup) &&
|
|
764
|
+
isRecord(value.cache) &&
|
|
765
|
+
hasExactKeys(value.cache, [
|
|
766
|
+
"stablePrefixFingerprint",
|
|
767
|
+
"toolDefinitionBoundary",
|
|
768
|
+
]) &&
|
|
769
|
+
typeof value.cache.stablePrefixFingerprint === "string" &&
|
|
770
|
+
value.cache.toolDefinitionBoundary === "stable");
|
|
771
|
+
}
|
|
772
|
+
function validPlayPresetMount(value) {
|
|
773
|
+
return (isRecord(value) &&
|
|
774
|
+
hasExactKeys(value, ["channel", "mount"]) &&
|
|
775
|
+
typeof value.channel === "string" &&
|
|
776
|
+
[
|
|
777
|
+
"story",
|
|
778
|
+
"sidebar",
|
|
779
|
+
"composer_above",
|
|
780
|
+
"composer_below",
|
|
781
|
+
"overlay",
|
|
782
|
+
"debug",
|
|
783
|
+
].includes(String(value.mount)));
|
|
784
|
+
}
|
|
785
|
+
function validModelBinding(value) {
|
|
786
|
+
return (isRecord(value) &&
|
|
787
|
+
hasExactKeys(value, [
|
|
788
|
+
"provider",
|
|
789
|
+
"endpointFingerprint",
|
|
790
|
+
"modelId",
|
|
791
|
+
"contextWindowTokens",
|
|
792
|
+
"maxOutputTokens",
|
|
793
|
+
"protocolConfigFingerprint",
|
|
794
|
+
], ["cacheStrategy"]) &&
|
|
795
|
+
validProviderKind(value.provider) &&
|
|
796
|
+
typeof value.endpointFingerprint === "string" &&
|
|
797
|
+
typeof value.modelId === "string" &&
|
|
798
|
+
validCount(value.contextWindowTokens) &&
|
|
799
|
+
validCount(value.maxOutputTokens) &&
|
|
800
|
+
typeof value.protocolConfigFingerprint === "string" &&
|
|
801
|
+
(value.cacheStrategy === undefined ||
|
|
802
|
+
validCacheStrategy(value.cacheStrategy)));
|
|
803
|
+
}
|
|
804
|
+
function validModelToolCall(value) {
|
|
805
|
+
return (isRecord(value) &&
|
|
806
|
+
hasExactKeys(value, ["id", "name", "arguments"]) &&
|
|
807
|
+
typeof value.id === "string" &&
|
|
808
|
+
typeof value.name === "string" &&
|
|
809
|
+
Object.hasOwn(value, "arguments"));
|
|
810
|
+
}
|
|
811
|
+
function validProviderState(value) {
|
|
812
|
+
if (!isRecord(value))
|
|
813
|
+
return false;
|
|
814
|
+
if (value.protocol === "chat_completions")
|
|
815
|
+
return (hasExactKeys(value, ["protocol", "assistantMessage"]) &&
|
|
816
|
+
Object.hasOwn(value, "assistantMessage"));
|
|
817
|
+
if (value.protocol === "openai_responses")
|
|
818
|
+
return (hasExactKeys(value, ["protocol", "output"], ["responseId"]) &&
|
|
819
|
+
Array.isArray(value.output) &&
|
|
820
|
+
(value.responseId === undefined || typeof value.responseId === "string"));
|
|
821
|
+
return (value.protocol === "anthropic_messages" &&
|
|
822
|
+
hasExactKeys(value, ["protocol", "content"], ["responseId", "model", "stopReason"]) &&
|
|
823
|
+
Array.isArray(value.content) &&
|
|
824
|
+
(value.responseId === undefined || typeof value.responseId === "string") &&
|
|
825
|
+
(value.model === undefined || typeof value.model === "string") &&
|
|
826
|
+
(value.stopReason === undefined ||
|
|
827
|
+
value.stopReason === null ||
|
|
828
|
+
typeof value.stopReason === "string"));
|
|
829
|
+
}
|
|
830
|
+
function validModelUsage(value) {
|
|
831
|
+
const fields = [
|
|
832
|
+
"inputTokens",
|
|
833
|
+
"uncachedInputTokens",
|
|
834
|
+
"cacheReadTokens",
|
|
835
|
+
"cacheWriteTokens",
|
|
836
|
+
"reasoningTokens",
|
|
837
|
+
"outputTokens",
|
|
838
|
+
"totalTokens",
|
|
839
|
+
];
|
|
840
|
+
if (!isRecord(value) ||
|
|
841
|
+
!hasExactKeys(value, [...fields, "provenance"]) ||
|
|
842
|
+
!fields.every((field) => validNullableCount(value[field])) ||
|
|
843
|
+
!isRecord(value.provenance) ||
|
|
844
|
+
!hasExactKeys(value.provenance, fields))
|
|
845
|
+
return false;
|
|
846
|
+
const provenance = value.provenance;
|
|
847
|
+
return fields.every((field) => ["provider", "unavailable", "derived_provider_fields"].includes(String(provenance[field])));
|
|
848
|
+
}
|
|
849
|
+
function validLegacyApplyRequest(value) {
|
|
850
|
+
return (value === null ||
|
|
851
|
+
(isRecord(value) &&
|
|
852
|
+
hasExactKeys(value, ["expectedDraftVersion", "draftFingerprint"]) &&
|
|
853
|
+
validCount(value.expectedDraftVersion) &&
|
|
854
|
+
typeof value.draftFingerprint === "string"));
|
|
855
|
+
}
|
|
856
|
+
function validNullableCount(value) {
|
|
857
|
+
return value === null || validCount(value);
|
|
858
|
+
}
|
|
859
|
+
function validFiniteNumber(value) {
|
|
860
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
861
|
+
}
|
|
862
|
+
function validAuthorization(value) {
|
|
863
|
+
return (value === null ||
|
|
864
|
+
(isRecord(value) &&
|
|
865
|
+
hasExactKeys(value, [
|
|
866
|
+
"revision",
|
|
867
|
+
"readWorldDocumentIds",
|
|
868
|
+
"readableDamagedWorldPaths",
|
|
869
|
+
"readOpaquePaths",
|
|
870
|
+
]) &&
|
|
871
|
+
typeof value.revision === "string" &&
|
|
872
|
+
validStringArray(value.readWorldDocumentIds) &&
|
|
873
|
+
validStringArray(value.readableDamagedWorldPaths) &&
|
|
874
|
+
validStringArray(value.readOpaquePaths)));
|
|
875
|
+
}
|
|
876
|
+
function validPendingSettlement(value) {
|
|
877
|
+
if (value === null)
|
|
878
|
+
return true;
|
|
879
|
+
if (!isRecord(value) ||
|
|
880
|
+
!validCount(value.assistantItemIndex) ||
|
|
881
|
+
typeof value.beforeFingerprint !== "string")
|
|
882
|
+
return false;
|
|
883
|
+
if (value.phase === "response_confirmed")
|
|
884
|
+
return hasExactKeys(value, [
|
|
885
|
+
"phase",
|
|
886
|
+
"assistantItemIndex",
|
|
887
|
+
"beforeFingerprint",
|
|
888
|
+
]);
|
|
889
|
+
return (value.phase === "publication_prepared" &&
|
|
890
|
+
hasExactKeys(value, [
|
|
891
|
+
"phase",
|
|
892
|
+
"assistantItemIndex",
|
|
893
|
+
"beforeFingerprint",
|
|
894
|
+
"afterFingerprint",
|
|
895
|
+
"toolResults",
|
|
896
|
+
"authorization",
|
|
897
|
+
]) &&
|
|
898
|
+
typeof value.afterFingerprint === "string" &&
|
|
899
|
+
Array.isArray(value.toolResults) &&
|
|
900
|
+
value.toolResults.every(validToolResult) &&
|
|
901
|
+
validAuthorization(value.authorization) &&
|
|
902
|
+
value.authorization !== null);
|
|
903
|
+
}
|
|
904
|
+
function validPendingSettlementForTranscript(pending, modelItems, runStatus) {
|
|
905
|
+
if (pending === null)
|
|
906
|
+
return true;
|
|
907
|
+
if (!validPendingSettlement(pending) ||
|
|
908
|
+
!Array.isArray(modelItems) ||
|
|
909
|
+
runStatus === "ready" ||
|
|
910
|
+
pending.assistantItemIndex !== modelItems.length - 1)
|
|
911
|
+
return false;
|
|
912
|
+
const assistant = modelItems[pending.assistantItemIndex];
|
|
913
|
+
if (!isRecord(assistant) ||
|
|
914
|
+
assistant.kind !== "assistant" ||
|
|
915
|
+
!Array.isArray(assistant.toolCalls) ||
|
|
916
|
+
assistant.toolCalls.length === 0 ||
|
|
917
|
+
assistant.providerState === undefined ||
|
|
918
|
+
!assistant.toolCalls.every(validModelToolCall))
|
|
919
|
+
return false;
|
|
920
|
+
const callIds = assistant.toolCalls.map((call) => isRecord(call) ? call.id : undefined);
|
|
921
|
+
if (new Set(callIds).size !== callIds.length)
|
|
922
|
+
return false;
|
|
923
|
+
if (pending.phase === "response_confirmed")
|
|
924
|
+
return true;
|
|
925
|
+
return (pending.authorization.revision === pending.afterFingerprint &&
|
|
926
|
+
pending.toolResults.length === callIds.length &&
|
|
927
|
+
pending.toolResults.every((result, index) => result.toolCallId === callIds[index]));
|
|
928
|
+
}
|
|
929
|
+
function validLegacyDraft(value) {
|
|
930
|
+
return (value === null ||
|
|
931
|
+
(isRecord(value) &&
|
|
932
|
+
hasExactKeys(value, ["outcome", "changes"]) &&
|
|
933
|
+
(value.outcome === "applied" ||
|
|
934
|
+
value.outcome === "discarded" ||
|
|
935
|
+
value.outcome === "unapplied_dropped" ||
|
|
936
|
+
value.outcome === "apply_outcome_unknown") &&
|
|
937
|
+
Array.isArray(value.changes) &&
|
|
938
|
+
value.changes.every(validDiff)));
|
|
939
|
+
}
|
|
940
|
+
function validToolResult(value) {
|
|
941
|
+
return (isRecord(value) &&
|
|
942
|
+
hasExactKeys(value, ["toolCallId", "markdown", "isError", "changes"]) &&
|
|
943
|
+
typeof value.toolCallId === "string" &&
|
|
944
|
+
typeof value.markdown === "string" &&
|
|
945
|
+
typeof value.isError === "boolean" &&
|
|
946
|
+
Array.isArray(value.changes) &&
|
|
947
|
+
value.changes.every(validDiff));
|
|
948
|
+
}
|
|
949
|
+
function validDiff(value) {
|
|
950
|
+
return (isRecord(value) &&
|
|
951
|
+
hasExactKeys(value, ["path", "kind", "before", "after"]) &&
|
|
952
|
+
typeof value.path === "string" &&
|
|
953
|
+
(value.kind === "create" ||
|
|
954
|
+
value.kind === "modify" ||
|
|
955
|
+
value.kind === "delete") &&
|
|
956
|
+
(value.before === null || typeof value.before === "string") &&
|
|
957
|
+
(value.after === null || typeof value.after === "string"));
|
|
958
|
+
}
|
|
959
|
+
function validStringArray(value) {
|
|
960
|
+
return (Array.isArray(value) && value.every((item) => typeof item === "string"));
|
|
961
|
+
}
|
|
962
|
+
async function publishJson(path, value) {
|
|
963
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
964
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
965
|
+
try {
|
|
966
|
+
const handle = await open(temporary, "wx", 0o600);
|
|
967
|
+
try {
|
|
968
|
+
await handle.writeFile(`${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
969
|
+
await handle.sync();
|
|
970
|
+
}
|
|
971
|
+
finally {
|
|
972
|
+
await handle.close();
|
|
973
|
+
}
|
|
974
|
+
await rename(temporary, path);
|
|
975
|
+
await syncDirectory(dirname(path));
|
|
976
|
+
}
|
|
977
|
+
finally {
|
|
978
|
+
await rm(temporary, { force: true });
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
async function publishImmutableSource(path, contents) {
|
|
982
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
983
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
984
|
+
try {
|
|
985
|
+
const handle = await open(temporary, "wx", 0o600);
|
|
986
|
+
try {
|
|
987
|
+
await handle.writeFile(contents, "utf8");
|
|
988
|
+
await handle.sync();
|
|
989
|
+
}
|
|
990
|
+
finally {
|
|
991
|
+
await handle.close();
|
|
992
|
+
}
|
|
993
|
+
try {
|
|
994
|
+
await link(temporary, path);
|
|
995
|
+
await syncDirectory(dirname(path));
|
|
996
|
+
}
|
|
997
|
+
catch (error) {
|
|
998
|
+
if (!isNodeError(error) || error.code !== "EEXIST")
|
|
999
|
+
throw error;
|
|
1000
|
+
if ((await readFile(path, "utf8")) !== contents)
|
|
1001
|
+
throw new Error("Legacy setting-improvement migration source does not match its preserved copy", { cause: error });
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
finally {
|
|
1005
|
+
await rm(temporary, { force: true });
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
async function syncDirectory(path) {
|
|
1009
|
+
const handle = await open(path, "r");
|
|
1010
|
+
try {
|
|
1011
|
+
await handle.sync();
|
|
1012
|
+
}
|
|
1013
|
+
finally {
|
|
1014
|
+
await handle.close();
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
function isRecord(value) {
|
|
1018
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1019
|
+
}
|
|
1020
|
+
function hasExactKeys(value, required, optional = []) {
|
|
1021
|
+
const allowed = new Set([...required, ...optional]);
|
|
1022
|
+
return (required.every((key) => Object.hasOwn(value, key)) &&
|
|
1023
|
+
Object.keys(value).every((key) => allowed.has(key)));
|
|
1024
|
+
}
|
|
1025
|
+
function isNodeError(error) {
|
|
1026
|
+
return error instanceof Error && "code" in error;
|
|
1027
|
+
}
|